diff --git a/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java b/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java index 9fc1d737..8d3a7e0d 100644 --- a/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java +++ b/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java @@ -227,7 +227,7 @@ public class NymeaAppControlService extends ControlsProviderService { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.putExtra("nymeaId", nymeaId.toString()); intent.putExtra("thingId", thing.id.toString()); - pi = PendingIntent.getActivity(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + pi = PendingIntent.getActivity(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); Log.d(TAG, "Created pendingintent for " + thing.name + " with id " + intentId + " and extra " + thing.id); Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id.toString(), pi) diff --git a/libnymea-app/engine.h b/libnymea-app/engine.h index 2c884994..7fdb9f78 100644 --- a/libnymea-app/engine.h +++ b/libnymea-app/engine.h @@ -36,7 +36,6 @@ #include "thingmanager.h" #include "connection/nymeatransportinterface.h" #include "jsonrpc/jsonrpcclient.h" -#include "wifisetup/bluetoothdiscovery.h" class RuleManager; class ScriptManager; diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index 9f10fac4..c6782827 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -73,6 +73,8 @@ #include "configuration/serverconfigurations.h" #include "configuration/mqttpolicy.h" #include "configuration/mqttpolicies.h" +#include "wifisetup/bluetoothdeviceinfos.h" +#include "wifisetup/bluetoothdiscovery.h" #include "wifisetup/btwifisetup.h" #include "types/wirelessaccesspoint.h" #include "types/wirelessaccesspoints.h" diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index a0fce137..83c6509d 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -45,6 +45,7 @@ #include "nfchelper.h" #include "nfcthingactionwriter.h" #include "platformhelper.h" +#include "platformintegration/platformpermissions.h" #include "dashboard/dashboardmodel.h" #include "dashboard/dashboarditem.h" #include "mouseobserver.h" @@ -169,6 +170,7 @@ int main(int argc, char *argv[]) engine->rootContext()->setContextProperty("styleController", &styleController); qmlRegisterSingletonType("Nymea", 1, 0, "PlatformHelper", PlatformHelper::platformHelperProvider); + qmlRegisterSingletonType("Nymea", 1, 0, "PlatformPermissions", PlatformPermissions::qmlProvider); qmlRegisterSingletonType("Nymea", 1, 0, "NfcHelper", NfcHelper::nfcHelperProvider); qmlRegisterType("Nymea", 1, 0, "NfcThingActionWriter"); diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 05ee1853..57a4450f 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -27,6 +27,7 @@ HEADERS += \ nfchelper.h \ nfcthingactionwriter.h \ platformintegration/generic/screenhelper.h \ + platformintegration/platformpermissions.h \ stylecontroller.h \ pushnotifications.h \ platformhelper.h \ @@ -39,6 +40,7 @@ SOURCES += main.cpp \ mouseobserver.cpp \ nfchelper.cpp \ nfcthingactionwriter.cpp \ + platformintegration/platformpermissions.cpp \ stylecontroller.cpp \ pushnotifications.cpp \ platformhelper.cpp \ @@ -73,11 +75,14 @@ android { include(../3rdParty/android/android_openssl/openssl.pri) ANDROID_MIN_SDK_VERSION = 21 - ANDROID_TARGET_SDK_VERSION = 31 + ANDROID_TARGET_SDK_VERSION = 33 QT += androidextras - HEADERS += platformintegration/android/platformhelperandroid.h - SOURCES += platformintegration/android/platformhelperandroid.cpp + HEADERS += platformintegration/android/platformhelperandroid.h \ + platformintegration/android/platformpermissionsandroid.h \ + + SOURCES += platformintegration/android/platformhelperandroid.cpp \ + platformintegration/android/platformpermissionsandroid.cpp \ # https://bugreports.qt.io/browse/QTBUG-83165 LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH} @@ -131,13 +136,20 @@ macx: { ios: { message("iOS build") - HEADERS += platformintegration/ios/platformhelperios.h - SOURCES += platformintegration/ios/platformhelperios.cpp + HEADERS += platformintegration/ios/platformhelperios.h \ + platformintegration/ios/platformpermissionsios.h \ + + SOURCES += platformintegration/ios/platformhelperios.cpp \ + platformintegration/ios/platformpermissionsios.cpp \ + OBJECTIVE_SOURCES += platformintegration/ios/platformhelperios.mm \ platformintegration/ios/pushnotifications.mm \ + platformintegration/ios/platformpermissionsios.mm \ OTHER_FILES += $${OBJECTIVE_SOURCES} + LIBS += -framework CoreLocation \ + # Add Firebase SDK QMAKE_LFLAGS += -ObjC $(inherited) firebase_files.files += $$files($${IOS_PACKAGE_DIR}/GoogleService-Info.plist) diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp index 8100a737..39fba57c 100644 --- a/nymea-app/platformhelper.cpp +++ b/nymea-app/platformhelper.cpp @@ -99,16 +99,6 @@ PlatformHelper *PlatformHelper::instance(bool create) return s_instance; } -bool PlatformHelper::hasPermissions() const -{ - return true; -} - -void PlatformHelper::requestPermissions() -{ - emit permissionsRequestFinished(); -} - void PlatformHelper::hideSplashScreen() { setSplashVisible(false); diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h index d30cf0bf..e15e4495 100644 --- a/nymea-app/platformhelper.h +++ b/nymea-app/platformhelper.h @@ -43,7 +43,6 @@ class QJSEngine; class PlatformHelper : public QObject { Q_OBJECT - Q_PROPERTY(bool hasPermissions READ hasPermissions NOTIFY permissionsRequestFinished) Q_PROPERTY(QString platform READ platform CONSTANT) Q_PROPERTY(QString deviceSerial READ deviceSerial CONSTANT) Q_PROPERTY(QString device READ device CONSTANT) @@ -70,9 +69,6 @@ public: static PlatformHelper* instance(bool create = true); virtual ~PlatformHelper() = default; - virtual bool hasPermissions() const; - Q_INVOKABLE virtual void requestPermissions(); - virtual QString platform() const; virtual QString machineHostname() const; virtual QString device() const; @@ -113,7 +109,6 @@ public: void notificationActionReceived(const QString &nymeaData); signals: - void permissionsRequestFinished(); void screenTimeoutChanged(); void screenBrightnessChanged(); void topPanelColorChanged(); diff --git a/nymea-app/platformintegration/android/java-firebase/io/guh/nymeaapp/NymeaAppNotificationService.java b/nymea-app/platformintegration/android/java-firebase/io/guh/nymeaapp/NymeaAppNotificationService.java index dfb5c512..bd6ca12c 100644 --- a/nymea-app/platformintegration/android/java-firebase/io/guh/nymeaapp/NymeaAppNotificationService.java +++ b/nymea-app/platformintegration/android/java-firebase/io/guh/nymeaapp/NymeaAppNotificationService.java @@ -50,7 +50,7 @@ public class NymeaAppNotificationService extends FirebaseMessagingService { intent.setAction(Intent.ACTION_SEND); intent.putExtra("notificationData", remoteMessage.getData().get("nymeaData")); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE); // We can't directly access R.drawable.notificationicon from here: // When the package is branded, the package name is not "io.guh.nymeaapp" and resources in @@ -83,7 +83,7 @@ public class NymeaAppNotificationService extends FirebaseMessagingService { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel("notify_001", "Channel human readable title", NotificationManager.IMPORTANCE_HIGH); + NotificationChannel channel = new NotificationChannel("notify_001", "Notifications from your nymea system", NotificationManager.IMPORTANCE_HIGH); notificationManager.createNotificationChannel(channel); } diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp index 1683f82e..c4a88882 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.cpp +++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp @@ -84,11 +84,6 @@ PlatformHelperAndroid::PlatformHelperAndroid(QObject *parent) : PlatformHelper(p } } -void PlatformHelperAndroid::requestPermissions() -{ - // Not using any fancy permissions in android yet... -} - void PlatformHelperAndroid::hideSplashScreen() { // Android's splash will flicker when fading out twice @@ -99,12 +94,6 @@ void PlatformHelperAndroid::hideSplashScreen() } } -bool PlatformHelperAndroid::hasPermissions() const -{ - // Not using any fancy permissions in android yet... - return true; -} - QString PlatformHelperAndroid::machineHostname() const { // QSysInfo::machineHostname always gives "localhost" on android... best we can do here is: @@ -265,14 +254,6 @@ void PlatformHelperAndroid::shareFile(const QString &fileName) ); } -void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result) -{ - foreach (const QString &key, result.keys()) { - qDebug() << "Permission result:" << key << static_cast(result.value(key)); - } - emit m_instance->permissionsRequestFinished(); -} - void PlatformHelperAndroid::darkModeEnabledChangedJNI() { if (m_instance) { diff --git a/nymea-app/platformintegration/android/platformhelperandroid.h b/nymea-app/platformintegration/android/platformhelperandroid.h index 4fd305fe..1420be17 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.h +++ b/nymea-app/platformintegration/android/platformhelperandroid.h @@ -45,11 +45,8 @@ public: explicit PlatformHelperAndroid(QObject *parent = nullptr); - Q_INVOKABLE void requestPermissions() override; - Q_INVOKABLE void hideSplashScreen() override; - bool hasPermissions() const override; QString machineHostname() const override; QString deviceSerial() const override; QString device() const override; diff --git a/nymea-app/platformintegration/android/platformpermissionsandroid.cpp b/nymea-app/platformintegration/android/platformpermissionsandroid.cpp new file mode 100644 index 00000000..cb1bf971 --- /dev/null +++ b/nymea-app/platformintegration/android/platformpermissionsandroid.cpp @@ -0,0 +1,64 @@ +#include "platformpermissionsandroid.h" + +#include +#include + +PlatformPermissionsAndroid * PlatformPermissionsAndroid::s_instance = nullptr; + +QHash permissionMap = { + // TODO: Once QtBluetooth does not request the COARSE_LOCATION for Bluetooth any more, remove it from here. The new Bluetooth permissions would be enough. + {PlatformPermissions::PermissionBluetooth, {"android.permission.BLUETOOTH_SCAN", "android.permission.BLUETOOTH_CONNECT", "android.permission.ACCESS_COARSE_LOCATION"}}, + {PlatformPermissions::PermissionLocation, {"android.permission.ACCESS_FINE_LOCATION"}}, + {PlatformPermissions::PermissionBackgroundLocation, {"android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_BACKGROUND_LOCATION"}}, + {PlatformPermissions::PermissionNotifications, {"android.permission.POST_NOTIFICATIONS"}}, +}; + +PlatformPermissionsAndroid::PlatformPermissionsAndroid(QObject *parent) + : PlatformPermissions{parent} +{ + s_instance = this; + // If the user switches to the settings app and changes permission settings there, we won't get notified + // in any way, so let's just refresh when we become active + connect(qApp, &QApplication::applicationStateChanged, this, [this](Qt::ApplicationState state){ + if (state == Qt::ApplicationActive) { + emit bluetoothPermissionChanged(); + emit locationPermissionChanged(); + emit backgroundLocationPermissionChanged(); + } + }); + +} + +void PlatformPermissionsAndroid::requestPermission(PlatformPermissions::Permission permission) +{ + qWarning() << "****** android permission request" << permission; + QtAndroid::requestPermissions({permissionMap.value(permission)}, &permissionResultCallback); +} + +void PlatformPermissionsAndroid::openPermissionSettings() +{ + QtAndroid::androidActivity().callMethod("openPermissionSettings", "()V"); +} + +PlatformPermissions::PermissionStatus PlatformPermissionsAndroid::checkPermission(Permission permission) const +{ + PermissionStatus status = PermissionStatusGranted; + QStringList androidPermissions = permissionMap.value(permission); + foreach (const QString androidPermission, androidPermissions) { + if (QtAndroid::shouldShowRequestPermissionRationale(androidPermission)) { + return PermissionStatusDenied; + } + if (QtAndroid::checkPermission(androidPermission) == QtAndroid::PermissionResult::Denied) { + status = PermissionStatusNotDetermined; + } + } + return status; +} + +void PlatformPermissionsAndroid::permissionResultCallback(const QtAndroid::PermissionResultMap &/*results*/) +{ + emit s_instance->bluetoothPermissionChanged(); + emit s_instance->locationPermissionChanged(); + emit s_instance->backgroundLocationPermissionChanged(); +} + diff --git a/nymea-app/platformintegration/android/platformpermissionsandroid.h b/nymea-app/platformintegration/android/platformpermissionsandroid.h new file mode 100644 index 00000000..038e8c7f --- /dev/null +++ b/nymea-app/platformintegration/android/platformpermissionsandroid.h @@ -0,0 +1,28 @@ +#ifndef PLATFORMPERMISSIONSANDROID_H +#define PLATFORMPERMISSIONSANDROID_H + +#include "../platformpermissions.h" + +#include + +class PlatformPermissionsAndroid : public PlatformPermissions +{ + Q_OBJECT +public: + explicit PlatformPermissionsAndroid(QObject *parent = nullptr); + + PermissionStatus checkPermission(Permission permission) const override; + + void requestPermission(Permission permission) override; + void openPermissionSettings() override; + +signals: + +private: + + static PlatformPermissionsAndroid *s_instance; + static void permissionResultCallback(const QtAndroid::PermissionResultMap &results); + +}; + +#endif // PLATFORMPERMISSIONSANDROID_H diff --git a/nymea-app/platformintegration/ios/platformhelperios.cpp b/nymea-app/platformintegration/ios/platformhelperios.cpp index 50000556..b1172b3d 100644 --- a/nymea-app/platformintegration/ios/platformhelperios.cpp +++ b/nymea-app/platformintegration/ios/platformhelperios.cpp @@ -44,13 +44,6 @@ PlatformHelperIOS::PlatformHelperIOS(QObject *parent) : PlatformHelper(parent) QObject::connect(screen, &QScreen::orientationChanged, qApp, [this](Qt::ScreenOrientation) { setBottomPanelColor(bottomPanelColor()); }); - - -} - -void PlatformHelperIOS::requestPermissions() -{ - emit permissionsRequestFinished(); } void PlatformHelperIOS::hideSplashScreen() @@ -58,11 +51,6 @@ void PlatformHelperIOS::hideSplashScreen() // Nothing to be done } -bool PlatformHelperIOS::hasPermissions() const -{ - return true; -} - QString PlatformHelperIOS::machineHostname() const { return QSysInfo::machineHostName(); diff --git a/nymea-app/platformintegration/ios/platformhelperios.h b/nymea-app/platformintegration/ios/platformhelperios.h index f28e2e81..ad5ce145 100644 --- a/nymea-app/platformintegration/ios/platformhelperios.h +++ b/nymea-app/platformintegration/ios/platformhelperios.h @@ -41,11 +41,8 @@ class PlatformHelperIOS : public PlatformHelper public: explicit PlatformHelperIOS(QObject *parent = nullptr); - Q_INVOKABLE virtual void requestPermissions() override; - Q_INVOKABLE void hideSplashScreen() override; - virtual bool hasPermissions() const override; virtual QString machineHostname() const override; virtual QString device() const override; virtual QString deviceSerial() const override; diff --git a/nymea-app/platformintegration/ios/platformpermissionsios.cpp b/nymea-app/platformintegration/ios/platformpermissionsios.cpp new file mode 100644 index 00000000..68923523 --- /dev/null +++ b/nymea-app/platformintegration/ios/platformpermissionsios.cpp @@ -0,0 +1,69 @@ +#include "platformpermissionsios.h" + +#include + +PlatformPermissionsIOS *PlatformPermissionsIOS::s_instance = nullptr; + +PlatformPermissionsIOS::PlatformPermissionsIOS(QObject *parent) + : PlatformPermissions{parent} +{ + s_instance = this; + initObjC(); +} + +PlatformPermissionsIOS *PlatformPermissionsIOS::instance() +{ + return s_instance; +} + +PlatformPermissions::PermissionStatus PlatformPermissionsIOS::checkPermission(Permission permission) const +{ + switch (permission) { + case PermissionLocalNetwork: + return checkLocalNetworkPermission(); + case PermissionNotifications: + return m_notificationPermissions; + case PermissionBackgroundLocation: + return checkBackgroundLocationPermission(); + case PermissionLocation: + return checkLocationPermission(); + case PermissionBluetooth: + return checkBluetoothPermission(); + default: + return PermissionStatusGranted; + } +} + +void PlatformPermissionsIOS::requestPermission(Permission permission) +{ + switch (permission) { + case PermissionLocalNetwork: + requestLocalNetworkPermission(); + break; + case PermissionNotifications: + requestNotificationPermission(); + break; + case PermissionBackgroundLocation: + requestBackgroundLocationPermission(); + break; + case PermissionLocation: + requestLocationPermission(); + break; + case PermissionBluetooth: + requestBluetoothPermission(); + break; + } +} + +PlatformPermissions::PermissionStatus PlatformPermissionsIOS::checkLocalNetworkPermission() const +{ + QSettings settings; + return settings.value("askedForLocalNetworkPermission", false).toBool() ? PermissionStatusGranted : PermissionStatusNotDetermined; +} + +void PlatformPermissionsIOS::requestLocalNetworkPermission() +{ + QSettings settings; + settings.setValue("askedForLocalNetworkPermission", true); + emit localNetworkPermissionChanged(); +} diff --git a/nymea-app/platformintegration/ios/platformpermissionsios.h b/nymea-app/platformintegration/ios/platformpermissionsios.h new file mode 100644 index 00000000..b3eb2ce3 --- /dev/null +++ b/nymea-app/platformintegration/ios/platformpermissionsios.h @@ -0,0 +1,51 @@ +#ifndef PLATFORMPERMISSIONSIOS_H +#define PLATFORMPERMISSIONSIOS_H + +#include + +#include "../platformpermissions.h" + +#if __OBJC__ +@class CLLocationManager; +@class CBCentralManager; +#else +typedef void CLLocationManager; +typedef void CBCentralManager; +#endif + +class PlatformPermissionsIOS : public PlatformPermissions +{ + Q_OBJECT +public: + explicit PlatformPermissionsIOS(QObject *parent = nullptr); + static PlatformPermissionsIOS *instance(); + + PermissionStatus checkPermission(Permission permission) const override; + void requestPermission(Permission permission) override; + void openPermissionSettings() override; + +private: + void initObjC(); + void refreshNotificationsPermission(); + + static PlatformPermissionsIOS *s_instance; + + PermissionStatus checkLocalNetworkPermission() const; + PermissionStatus checkBluetoothPermission() const; + PermissionStatus checkLocationPermission() const; + PermissionStatus checkBackgroundLocationPermission() const; + + void requestLocalNetworkPermission(); + void requestNotificationPermission(); + void requestBluetoothPermission(); + void requestLocationPermission(); + void requestBackgroundLocationPermission(); + + PermissionStatus m_notificationPermissions = PermissionStatusNotDetermined; + + + CLLocationManager *m_locationManager = nullptr; + CBCentralManager *m_bluetoothManager = nullptr; +}; + +#endif // PLATFORMPERMISSIONSIOS_H diff --git a/nymea-app/platformintegration/ios/platformpermissionsios.mm b/nymea-app/platformintegration/ios/platformpermissionsios.mm new file mode 100644 index 00000000..4896bf6e --- /dev/null +++ b/nymea-app/platformintegration/ios/platformpermissionsios.mm @@ -0,0 +1,144 @@ +#include "platformpermissionsios.h" + +#import +#import +#import +#import +#import + +@interface LocationManagerPermissionDelegate : NSObject +@end +@implementation LocationManagerPermissionDelegate +- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager { + emit PlatformPermissionsIOS::instance()->locationPermissionChanged(); +} +@end + +@interface BluetoothManagerDelegate: NSObject +@end +@implementation BluetoothManagerDelegate +- (void)centralManagerDidUpdateState:(CBCentralManager *)manager { + emit PlatformPermissionsIOS::instance()->bluetoothPermissionChanged(); +} +@end + +void PlatformPermissionsIOS::initObjC() +{ + m_locationManager = [[CLLocationManager alloc] init]; + m_locationManager.delegate = [[LocationManagerPermissionDelegate alloc] init]; + + // Refresh notification permissions right away as that can be retrieved async only. We wanna be ready when the app requests it. + refreshNotificationsPermission(); +} + +void PlatformPermissionsIOS::refreshNotificationsPermission() +{ + // Notification permissions can be retrieved async only. + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { + PermissionStatus newPermission; + switch (settings.authorizationStatus) { + case UNAuthorizationStatusNotDetermined: + newPermission = PermissionStatusNotDetermined; + break; + case UNAuthorizationStatusDenied: + newPermission = PermissionStatusDenied; + break; + case UNAuthorizationStatusAuthorized: + case UNAuthorizationStatusProvisional: + case UNAuthorizationStatusEphemeral: + newPermission = PermissionStatusGranted; + break; + } + if (newPermission != m_notificationPermissions) { + m_notificationPermissions = newPermission; + emit notificationsPermissionChanged(); + } + }]; +} + +void PlatformPermissionsIOS::requestNotificationPermission() +{ + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionBadge + UNAuthorizationOptionSound) + completionHandler:^(BOOL granted, NSError * _Nullable error) { + m_notificationPermissions = granted ? PermissionStatusGranted : PermissionStatusDenied; + emit notificationsPermissionChanged(); + }]; +} + +PlatformPermissions::PermissionStatus PlatformPermissionsIOS::checkBluetoothPermission() const +{ + // iOS 13.0 would have an api but it's more complicated and also deprecated... Ignoring... + if (@available(iOS 13.1, *)) { + switch (CBCentralManager.authorization) { + case CBManagerAuthorizationAllowedAlways: + case CBManagerAuthorizationRestricted: + return PermissionStatusGranted; + case CBManagerAuthorizationDenied: + return PermissionStatusDenied; + case CBManagerAuthorizationNotDetermined: + return PermissionStatusNotDetermined; + } + } + // Before iOS 13, Bluetooth permissions are not required + return PermissionStatusGranted; +} + +void PlatformPermissionsIOS::requestBluetoothPermission() +{ + // Instantiating a Bluetooth manager just trigger the popup... + if (!m_bluetoothManager) { + BluetoothManagerDelegate *delegate = [[BluetoothManagerDelegate alloc] init]; + m_bluetoothManager = [[CBCentralManager alloc] initWithDelegate:delegate queue:nil]; + } +} + +PlatformPermissions::PermissionStatus PlatformPermissionsIOS::checkLocationPermission() const +{ + switch ([CLLocationManager authorizationStatus]) { + case kCLAuthorizationStatusNotDetermined: + return PermissionStatusNotDetermined; + case kCLAuthorizationStatusAuthorizedAlways: + case kCLAuthorizationStatusAuthorizedWhenInUse: + return PermissionStatusGranted; + case kCLAuthorizationStatusDenied: + case kCLAuthorizationStatusRestricted: + return PermissionStatusDenied; + } + return PermissionStatusGranted; +} + +void PlatformPermissionsIOS::requestLocationPermission() +{ + if ([m_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [m_locationManager requestWhenInUseAuthorization]; + } +} + +PlatformPermissions::PermissionStatus PlatformPermissionsIOS::checkBackgroundLocationPermission() const +{ + switch ([CLLocationManager authorizationStatus]) { + case kCLAuthorizationStatusNotDetermined: + return PermissionStatusNotDetermined; + case kCLAuthorizationStatusAuthorizedAlways: + return PermissionStatusGranted; + case kCLAuthorizationStatusAuthorizedWhenInUse: + case kCLAuthorizationStatusDenied: + case kCLAuthorizationStatusRestricted: + return PermissionStatusDenied; + } + return PermissionStatusGranted; +} + +void PlatformPermissionsIOS::requestBackgroundLocationPermission() +{ + if ([m_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [m_locationManager requestAlwaysAuthorization]; + } +} + +void PlatformPermissionsIOS::openPermissionSettings() +{ + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; +} diff --git a/nymea-app/platformintegration/ios/pushnotifications.mm b/nymea-app/platformintegration/ios/pushnotifications.mm index 5fbfcb2a..24b8f918 100644 --- a/nymea-app/platformintegration/ios/pushnotifications.mm +++ b/nymea-app/platformintegration/ios/pushnotifications.mm @@ -8,9 +8,18 @@ #import "Firebase/Firebase.h" +@interface FirebaseDelegate: NSObject +@end + +@implementation FirebaseDelegate + - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { + qDebug() << "Firebase token received:" << QString::fromNSString(fcmToken); + dynamic_cast(PushNotifications::instance())->setFirebaseRegistrationToken(QString::fromNSString(fcmToken)); + } +@end // This is hidden, so we declare it here to hook into it -@interface QIOSApplicationDelegate: UIResponder +@interface QIOSApplicationDelegate: UIResponder @end //add a category to QIOSApplicationDelegate @@ -23,50 +32,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Use Firebase library to configure APIs - [FIRApp configure]; - [FIRMessaging messaging].delegate = self; - - - // Register to receive notifications from the system UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; - [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error){ - if(!error){ - [[UIApplication sharedApplication] registerForRemoteNotifications]; - } - }]; - - NSLog(@"registering for remote notifications"); - qDebug() << "Registering for remote notifications"; - return YES; } -- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - NSLog(@"Did Register for Remote Notifications with Device Token (%@)", deviceToken); - - const unsigned *tokenBytes = (const unsigned*)[deviceToken bytes]; - NSString *tokenStr = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x", - ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]), - ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]), - ntohl(tokenBytes[6]), ntohl(tokenBytes[7])]; - - // We've switched to firebase... Not emitting the native APNS token - -// qDebug() << "Registering for remote notifications"; -// qDebug() << "Token description:" << QString::fromNSString(deviceToken.description); -// qDebug() << "Parsed token:" << QString::fromNSString(tokenStr); - PushNotifications::instance()->setAPNSRegistrationToken(QString::fromNSString(tokenStr)); -} - -- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - NSLog(@"Did Fail to Register for Remote Notifications"); - NSLog(@"%@, %@", error, error.localizedDescription); - qWarning() << "Failed to register for notifications:" << QString::fromNSString(error.localizedDescription); -} - -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{ NSLog(@"User Info : %@",notification.request.content.userInfo); qDebug() << "willPresentNotification called!"; @@ -91,20 +62,15 @@ } -- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { - NSLog(@"FCM registration token: %@", fcmToken); - // Notify about received token. - NSDictionary *dataDict = [NSDictionary dictionaryWithObject:fcmToken forKey:@"token"]; - [[NSNotificationCenter defaultCenter] postNotificationName: - @"FCMToken" object:nil userInfo:dataDict]; - // Note: This callback is fired at each app startup and whenever a new token is generated. - //qDebug() << "Firebase token received:" << QString::fromNSString(fcmToken); - PushNotifications::instance()->setFirebaseRegistrationToken(QString::fromNSString(fcmToken)); - -} - - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo { qDebug() << "didReceiveRemoteNotification called"; } @end + +void PushNotifications::registerObjC() +{ + [FIRApp configure]; + [FIRMessaging messaging].delegate = [[FirebaseDelegate alloc] init]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; +} diff --git a/nymea-app/platformintegration/platformpermissions.cpp b/nymea-app/platformintegration/platformpermissions.cpp new file mode 100644 index 00000000..5cfae04d --- /dev/null +++ b/nymea-app/platformintegration/platformpermissions.cpp @@ -0,0 +1,64 @@ +#include "platformpermissions.h" + +#ifdef Q_OS_ANDROID +#include "android/platformpermissionsandroid.h" +#elif defined Q_OS_IOS +#include "ios/platformpermissionsios.h" +#endif + +PlatformPermissions *PlatformPermissions::instance() +{ +#ifdef Q_OS_ANDROID + return new PlatformPermissionsAndroid(); +#elif defined Q_OS_IOS + return new PlatformPermissionsIOS(); +#else + return new PlatformPermissions(); +#endif +} + + +PlatformPermissions::PlatformPermissions(QObject *parent) + : QObject{parent} +{ + +} + +QObject *PlatformPermissions::qmlProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return instance(); +} + +PlatformPermissions::PermissionStatus PlatformPermissions::localNetworkPermission() const +{ + return checkPermission(PermissionLocalNetwork); +} + +PlatformPermissions::PermissionStatus PlatformPermissions::bluetoothPermission() const +{ + return checkPermission(PermissionBluetooth); +} + +PlatformPermissions::PermissionStatus PlatformPermissions::locationPermission() const +{ + return checkPermission(PermissionLocation); +} + +PlatformPermissions::PermissionStatus PlatformPermissions::backgroundLocationPermission() const +{ + return checkPermission(PermissionBackgroundLocation); +} + +PlatformPermissions::PermissionStatus PlatformPermissions::notificationsPermission() const +{ + return checkPermission(PermissionNotifications); +} + +PlatformPermissions::PermissionStatus PlatformPermissions::checkPermission(Permission permission) const +{ + Q_UNUSED(permission) + return PermissionStatusGranted; +} + diff --git a/nymea-app/platformintegration/platformpermissions.h b/nymea-app/platformintegration/platformpermissions.h new file mode 100644 index 00000000..2313cdba --- /dev/null +++ b/nymea-app/platformintegration/platformpermissions.h @@ -0,0 +1,65 @@ +#ifndef PLATFORMPERMISSIONS_H +#define PLATFORMPERMISSIONS_H + +#include + +class QQmlEngine; +class QJSEngine; + +class PlatformPermissions : public QObject +{ + Q_OBJECT + + Q_PROPERTY(PermissionStatus localNetworkPermission READ localNetworkPermission NOTIFY localNetworkPermissionChanged) + Q_PROPERTY(PermissionStatus bluetoothPermission READ bluetoothPermission NOTIFY bluetoothPermissionChanged) + Q_PROPERTY(PermissionStatus locationPermission READ locationPermission NOTIFY locationPermissionChanged) + Q_PROPERTY(PermissionStatus backgroundLocationPermission READ backgroundLocationPermission NOTIFY backgroundLocationPermissionChanged) + Q_PROPERTY(PermissionStatus notificationsPermission READ notificationsPermission NOTIFY notificationsPermissionChanged) + +public: + enum Permission { + PermissionNone = 0x00, + PermissionLocalNetwork = 0x01, + PermissionBluetooth = 0x02, + PermissionLocation = 0x04, + PermissionBackgroundLocation = 0x08, + PermissionNotifications = 0x10 + }; + Q_ENUM(Permission) + Q_DECLARE_FLAGS(Permissions, Permission) + Q_FLAG(Permissions) + + enum PermissionStatus { + PermissionStatusNotDetermined, + PermissionStatusGranted, + PermissionStatusDenied, + }; + Q_ENUM(PermissionStatus) + + static PlatformPermissions* instance(); + static QObject *qmlProvider(QQmlEngine *engine, QJSEngine *scriptEngine); + virtual ~PlatformPermissions() = default; + + PermissionStatus localNetworkPermission() const; + PermissionStatus bluetoothPermission() const; + PermissionStatus locationPermission() const; + PermissionStatus backgroundLocationPermission() const; + PermissionStatus notificationsPermission() const; + + Q_INVOKABLE virtual PermissionStatus checkPermission(Permission permission) const; + Q_INVOKABLE virtual void requestPermission(Permission permission) { Q_UNUSED(permission) } + Q_INVOKABLE virtual void openPermissionSettings() {} + +signals: + void localNetworkPermissionChanged(); + void bluetoothPermissionChanged(); + void locationPermissionChanged(); + void backgroundLocationPermissionChanged(); + void notificationsPermissionChanged(); + +protected: + explicit PlatformPermissions(QObject *parent = nullptr); + +}; + +#endif // PLATFORMPERMISSIONS_H diff --git a/nymea-app/pushnotifications.cpp b/nymea-app/pushnotifications.cpp index fc66a5bf..47682648 100644 --- a/nymea-app/pushnotifications.cpp +++ b/nymea-app/pushnotifications.cpp @@ -42,33 +42,7 @@ static PushNotifications *m_client_pointer; PushNotifications::PushNotifications(QObject *parent) : QObject(parent) { -#if defined Q_OS_ANDROID && defined WITH_FIREBASE - qDebug() << "Checking for play services"; - jboolean playServicesAvailable = QAndroidJniObject::callStaticMethod("io.guh.nymeaapp.NymeaAppNotificationService", "checkPlayServices", "()Z"); - if (playServicesAvailable) { - qDebug() << "Setting up firebase"; - m_client_pointer = this; - m_firebaseApp = ::firebase::App::Create(::firebase::AppOptions(), QAndroidJniEnvironment(), QtAndroid::androidActivity().object()); - m_firebase_initializer.Initialize(m_firebaseApp, nullptr, [](::firebase::App * fapp, void *) { - return ::firebase::messaging::Initialize( *fapp, (::firebase::messaging::Listener *)m_client_pointer); - }); - } else { - qDebug() << "Google Play Services not available. Cannot connect to push client."; - } -#endif - -#ifdef UBPORTS - m_pushClient = new PushClient(this); - m_pushClient->setAppId("io.guh.nymeaapp_nymea-app"); - connect(m_pushClient, &PushClient::tokenChanged, this, [this](const QString &token) { - // On UBPorts, core and cloud use the same token - m_coreToken = token; - emit coreTokenChanged(); - m_cloudToken = m_coreToken; - emit cloudTokenChanged(); - }); -#endif } PushNotifications::~PushNotifications() @@ -91,6 +65,57 @@ PushNotifications *PushNotifications::instance() return pushNotifications; } + +bool PushNotifications::enabled() const +{ + return m_enabled; +} + +void PushNotifications::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + + m_enabled = enabled; + + if (enabled) { + registerForPush(); + } +} + +void PushNotifications::registerForPush() +{ +#if defined Q_OS_ANDROID && defined WITH_FIREBASE + qDebug() << "Checking for play services"; + jboolean playServicesAvailable = QAndroidJniObject::callStaticMethod("io.guh.nymeaapp.NymeaAppNotificationService", "checkPlayServices", "()Z"); + if (playServicesAvailable) { + qDebug() << "Setting up firebase"; + m_client_pointer = this; + m_firebaseApp = ::firebase::App::Create(::firebase::AppOptions(), QAndroidJniEnvironment(), QtAndroid::androidActivity().object()); + m_firebase_initializer.Initialize(m_firebaseApp, nullptr, [](::firebase::App * fapp, void *) { + return ::firebase::messaging::Initialize( *fapp, (::firebase::messaging::Listener *)m_client_pointer); + }); + } else { + qDebug() << "Google Play Services not available. Cannot connect to push client."; + } +#endif + + +#ifdef UBPORTS + m_pushClient = new PushClient(this); + m_pushClient->setAppId("io.guh.nymeaapp_nymea-app"); + connect(m_pushClient, &PushClient::tokenChanged, this, [this](const QString &token) { + m_token = token; + emit tokenChanged(); + }); +#endif + +#ifdef Q_OS_IOS + registerObjC(); +#endif +} + QString PushNotifications::service() const { #if defined Q_OS_ANDROID @@ -108,28 +133,16 @@ QString PushNotifications::clientId() const return PlatformHelper::instance()->deviceSerial(); } -QString PushNotifications::coreToken() const +QString PushNotifications::token() const { - return m_coreToken; -} - -QString PushNotifications::cloudToken() const -{ - return m_cloudToken; -} - -void PushNotifications::setAPNSRegistrationToken(const QString &apnsRegistrationToken) -{ - qDebug() << "Received APNS push notification token:" << apnsRegistrationToken; - m_cloudToken = apnsRegistrationToken; - emit cloudTokenChanged(); + return m_token; } void PushNotifications::setFirebaseRegistrationToken(const QString &firebaseRegistrationToken) { qDebug() << "Received Firebase/APNS push notification token:" << firebaseRegistrationToken; - m_coreToken = firebaseRegistrationToken; - emit coreTokenChanged(); + m_token = firebaseRegistrationToken; + emit tokenChanged(); } #if defined Q_OS_ANDROID && defined WITH_FIREBASE @@ -140,11 +153,8 @@ void PushNotifications::OnMessage(const firebase::messaging::Message &message) void PushNotifications::OnTokenReceived(const char *token) { - qDebug() << "Firebase token received:" << token; - // On Android, both, core and cloud use the same token - m_coreToken = QString(token); - emit coreTokenChanged(); - m_cloudToken = m_coreToken; - emit cloudTokenChanged(); + m_token = QString(token); + qDebug() << "Firebase token received:" << m_token; + emit tokenChanged(); } #endif diff --git a/nymea-app/pushnotifications.h b/nymea-app/pushnotifications.h index a7916b7e..b3ba142a 100644 --- a/nymea-app/pushnotifications.h +++ b/nymea-app/pushnotifications.h @@ -51,10 +51,10 @@ class PushNotifications : public QObject #endif { Q_OBJECT + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(QString service READ service CONSTANT) Q_PROPERTY(QString clientId READ clientId CONSTANT) - Q_PROPERTY(QString cloudToken READ cloudToken NOTIFY cloudTokenChanged) - Q_PROPERTY(QString coreToken READ coreToken NOTIFY coreTokenChanged) + Q_PROPERTY(QString token READ token NOTIFY tokenChanged) public: explicit PushNotifications(QObject *parent = nullptr); @@ -63,18 +63,19 @@ public: static QObject* pushNotificationsProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static PushNotifications* instance(); + bool enabled() const; + void setEnabled(bool enabled); + QString service() const; QString clientId() const; - QString coreToken() const; - QString cloudToken() const; + QString token() const; // Called by Objective-C++ on iOS - void setAPNSRegistrationToken(const QString &apnsRegistrationToken); void setFirebaseRegistrationToken(const QString &firebaseRegistrationToken); signals: - void coreTokenChanged(); - void cloudTokenChanged(); + void enabledChanged(); + void tokenChanged(); protected: @@ -92,10 +93,16 @@ private: #endif private: - // For nymea:core plugin based push notifications - QString m_coreToken; - // for nymea:cloud based push notifications (deprecated) - QString m_cloudToken; + + void registerForPush(); + + +#ifdef Q_OS_IOS + void registerObjC(); +#endif + + bool m_enabled = false; + QString m_token; }; #endif // PUSHNOTIFICATIONS_H diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 16a176fa..73adeba7 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -106,6 +106,12 @@ ApplicationWindow { value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment } + Binding { + target: PushNotifications + property: "enabled" + value: PlatformPermissions.notificationsPermission === PlatformPermissions.PermissionStatusGranted + } + ConfiguredHostsModel { id: configuredHostsModel } @@ -136,7 +142,9 @@ ApplicationWindow { property NymeaDiscovery nymeaDiscovery: NymeaDiscovery { objectName: "discovery" awsClient: AWSClient + bluetoothDiscoveryEnabled: PlatformPermissions.bluetoothPermission === PlatformPermissions.PermissionStatusGranted // discovering: pageStack.currentItem.objectName === "discoveryPage" + Component.onCompleted: console.warn("****************** local net perm", PlatformPermissions.localNetworkPermission, discovering, PlatformPermissions.localNetworkPermission === PlatformPermissions.PermissionStatusGranted, PlatformPermissions.PermissionStatusGranted) } property var supportedInterfaces: [ diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 2d856ccf..69dad3af 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -126,6 +126,12 @@ Item { target: nymeaDiscovery property: "discovering" value: engine.jsonRpcClient.currentHost === null + && (PlatformPermissions.localNetworkPermission === PlatformPermissions.PermissionStatusGranted + // This OR wouldn't be needed but we introduced the permission handling later and the localNetworkPerm can't be read on iOS. + // If there are configured hosts, it means that we actally already have the permission even though PlatformPermissions thinks we wouldn't... + // So skipping the check in that case for now (1.6) + || configuredHostsModel.count > 0) + } readonly property alias pageStack: _pageStack @@ -137,7 +143,6 @@ Item { } Component.onCompleted: { - setupPushNotifications(); if (configuredHost.uuid.toString() !== "{00000000-0000-0000-0000-000000000000}") { print("Configured host id is", configuredHost.uuid) var cachedHost = nymeaDiscovery.nymeaHosts.find(configuredHost.uuid); @@ -149,6 +154,7 @@ Item { } else if (autoConnectHost.length > 0 && index === 0) { var host = nymeaDiscovery.nymeaHosts.createLanHost(Configuration.systemName, autoConnectHost); engine.jsonRpcClient.connectToHost(host) + return; } else { // Only hide the splash right away if we're not trying to connect to something @@ -223,43 +229,13 @@ Item { return false; } - // Old nymea:cloud based push notifications... - function setupPushNotifications(askForPermissions) { - if (askForPermissions === undefined) { - askForPermissions = true; - } - - if (!AWSClient.isLoggedIn) { - print("AWS not logged in. Cannot register for push"); - return; - } - - if (PushNotifications.cloudToken.length === 0) { - print("Don't have a token yet. Cannot register for push"); - return; - } - - if (!PlatformHelper.hasPermissions) { - if (askForPermissions) { - PlatformHelper.requestPermissions(); - } - } else { - AWSClient.registerPushNotificationEndpoint( - PushNotifications.cloudToken, - PlatformHelper.machineHostname, - PushNotifications.clientId, - PlatformHelper.deviceManufacturer, - PlatformHelper.deviceModel); - } - } - // New, nymea thing based push notifactions function updatePushNotificationThings() { if (PushNotifications.service == "") { print("This platform does not support push notifications") return; } - if (!PushNotifications.coreToken) { + if (!PushNotifications.token) { print("No push notification token available at this time. Not updating..."); return; } @@ -268,7 +244,7 @@ Item { print("Updating push notifications") print("Own push service:", PushNotifications.service); print("Own client ID:", clientId); - print("Current token:", PushNotifications.coreToken); + print("Current token:", PushNotifications.token); for (var i = 0; i < engine.thingManager.things.count; i++) { @@ -279,11 +255,15 @@ Item { var tokenParam = thing.paramByName("token") print("Found a push notification thing for client id:", clientIdParam.value) if (clientIdParam.value === clientId) { - if (tokenParam.value !== PushNotifications.coreToken) { + if (PlatformPermissions.notificationsPermission !== PlatformPermissions.PermissionStatusGranted) { + PlatformPermissions.requestPermission(PlatformPermissions.PermissionNotifications) + } + + if (tokenParam.value !== PushNotifications.token) { var params = [ { "paramTypeId": serviceParam.paramTypeId, "value": PushNotifications.service }, { "paramTypeId": clientIdParam.paramTypeId, "value": clientId }, - { "paramTypeId": tokenParam.paramTypeId, "value": PushNotifications.coreToken } + { "paramTypeId": tokenParam.paramTypeId, "value": PushNotifications.token } ]; print("Reconfiguring PushNotifications for", thing.name) engine.thingManager.reconfigureThing(thing.id, params); @@ -401,27 +381,6 @@ Item { } } - Connections { - target: PlatformHelper - onHasPermissionsChanged: { - setupPushNotifications(false) - } - } - - Connections { - target: PushNotifications - onCloudTokenChanged: { - setupPushNotifications(); - } - } - - Connections { - target: AWSClient - onIsLoggedInChanged: { - setupPushNotifications() - } - } - Connections { target: engine.thingManager onFetchingDataChanged: { diff --git a/nymea-app/ui/connection/ConnectionWizard.qml b/nymea-app/ui/connection/ConnectionWizard.qml index 7d6c11c4..7b12fc7d 100644 --- a/nymea-app/ui/connection/ConnectionWizard.qml +++ b/nymea-app/ui/connection/ConnectionWizard.qml @@ -12,7 +12,10 @@ WizardPageBase { showExtraButton: true extraButtonText: qsTr("Demo mode") - onNext: pageStack.push(connectionSelectionComponent) + onNext: { + PlatformPermissions.requestPermission(PlatformPermissions.PermissionLocalNetwork) + pageStack.push(connectionSelectionComponent) + } onExtraButtonPressed: { var host = nymeaDiscovery.nymeaHosts.createWanHost("Demo server", "nymea://nymea.nymea.io:2222") engine.jsonRpcClient.connectToHost(host) @@ -120,7 +123,13 @@ WizardPageBase { BigTile { Layout.fillWidth: true - onClicked: pageStack.push(wirelessInstructionsComponent) + onClicked: { + if (PlatformPermissions.bluetoothPermission != PlatformPermissions.PermissionStatusGranted) { + PlatformPermissions.requestPermission(PlatformPermissions.PermissionBluetooth) + } else { + } + pageStack.push(wirelessInstructionsComponent) + } contentItem: RowLayout { spacing: Style.margins diff --git a/nymea-app/ui/thingconfiguration/NewThingPage.qml b/nymea-app/ui/thingconfiguration/NewThingPage.qml index fb3b814f..8c77ae53 100644 --- a/nymea-app/ui/thingconfiguration/NewThingPage.qml +++ b/nymea-app/ui/thingconfiguration/NewThingPage.qml @@ -46,6 +46,18 @@ Page { } } + function startWizard(thingClass) { + var page = pageStack.push(Qt.resolvedUrl("SetupWizard.qml"), {thingClass: thingClass}); + page.done.connect(function() { + pageStack.pop(root, StackView.Immediate); + pageStack.pop(); + }) + page.aborted.connect(function() { + pageStack.pop(); + }) + + } + Pane { id: filterPane anchors { left: parent.left; top: parent.top; right: parent.right } @@ -167,14 +179,7 @@ Page { property ThingClass thingClass: thingClassesProxy.get(index) onClicked: { - var page = pageStack.push(Qt.resolvedUrl("SetupWizard.qml"), {thingClass: thingClassesProxy.get(index)}); - page.done.connect(function() { - pageStack.pop(root, StackView.Immediate); - pageStack.pop(); - }) - page.aborted.connect(function() { - pageStack.pop(); - }) + root.startWizard(thingClass) } } } diff --git a/nymea-app/ui/thingconfiguration/SetupWizard.qml b/nymea-app/ui/thingconfiguration/SetupWizard.qml index f621d518..01c496a8 100644 --- a/nymea-app/ui/thingconfiguration/SetupWizard.qml +++ b/nymea-app/ui/thingconfiguration/SetupWizard.qml @@ -375,6 +375,16 @@ Page { visible: paramRepeater.count > 0 } + Component.onCompleted: { + if (root.thingClass.id.toString().match(/\{?f0dd4c03-0aca-42cc-8f34-9902457b05de\}?/)) { + console.warn("checking Notification permission!") + if (PlatformPermissions.notificationsPermission != PlatformPermissions.PermissionStatusGranted) { + console.warn("Notification permission missing!") + PlatformPermissions.requestPermission(PlatformPermissions.PermissionNotifications) + } + } + } + Repeater { id: paramRepeater model: engine.jsonRpcClient.ensureServerVersion("1.12") || d.thingDescriptor == null ? root.thingClass.paramTypes : null @@ -396,7 +406,7 @@ Page { return PushNotifications.service; } if (paramType.id.toString().match(/\{?12ec06b2-44e7-486a-9169-31c684b91c8f\}?/)) { - return PushNotifications.coreToken; + return PushNotifications.token; } if (paramType.id.toString().match(/\{?d76da367-64e3-4b7d-aa84-c96b3acfb65e\}?/)) { return PushNotifications.clientId + "+" + Configuration.appId; diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index ab7fb800..6f259537 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -1,8 +1,14 @@ + + + + + + - + @@ -63,7 +69,7 @@ - + @@ -99,7 +105,7 @@ - + @@ -130,7 +136,7 @@ - + @@ -163,6 +169,4 @@ - -