From 343494ece0eb08205b0e940d18e08d4c84bac631 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 09:54:00 +0000 Subject: [PATCH 01/12] Fix notification action receiver --- .../java/com/keyboardcrumbs/hassclient/MessagingService.java | 2 +- .../keyboardcrumbs/hassclient/NotificationActionReceiver.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java index 91b53ec..d6a3649 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java @@ -98,7 +98,7 @@ public class MessagingService extends FirebaseMessagingService { Log.d(TAG, "Putting a tag to the action: " + nTag); broadcastIntent.putExtra("tag", nTag); broadcastIntent.putExtra("actionData", data.get("action" + i + "_data")); - PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, 0); + PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT); notificationBuilder.addAction(R.drawable.mini_icon, data.get("action" + i), actionIntent); } } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java index 451137c..4c0ef69 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java @@ -21,15 +21,14 @@ public class NotificationActionReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String rawActionData = intent.getStringExtra("actionData"); String notificationTag = intent.getStringExtra("tag"); - Log.d(TAG, "Has 'tag': " + intent.hasExtra("tag")); Log.d(TAG, "Canceling notification by tag: " + notificationTag); + Log.d(TAG, "action data: " + rawActionData); NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(notificationTag, 0); SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); String webhookId = prefs.getString("flutter.app-webhook-id", null); if (webhookId != null) { try { - Log.d(TAG, "Got webhook id"); String requestUrl = prefs.getString("flutter.hassio-res-protocol", "") + "://" + prefs.getString("flutter.hassio-domain", "") + From aac0cfbb56f84bc1ff5b31590a44d5781cd9df94 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 10:20:48 +0000 Subject: [PATCH 02/12] Dismiss and auto dismiss for notifications --- .../hassclient/MessagingService.java | 30 +++++++++++++++++-- .../NotificationActionReceiver.java | 11 +++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java index d6a3649..f1efd52 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java @@ -50,6 +50,7 @@ public class MessagingService extends FirebaseMessagingService { private void sendNotification(Map data) { String channelId, messageBody, messageTitle, imageUrl; String nTag; + boolean autoCancel; if (!data.containsKey("channelId")) { channelId = "ha_notify"; } else { @@ -71,6 +72,28 @@ public class MessagingService extends FirebaseMessagingService { nTag = data.get("tag"); } Log.d(TAG, "Notification tag: " + nTag); + if (data.containsKey("dismiss")) { + try { + boolean dismiss = Boolean.parseBoolean(data.get("dismiss")); + if (dismiss) { + NotificationManager notificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(nTag, 0); + return; + } + } catch (Exception e) { + //nope + } + } + if (data.containsKey("autoDismiss")) { + try { + autoCancel = Boolean.parseBoolean(data.get("autoDismiss")); + } catch (Exception e) { + autoCancel = true; + } + } else { + autoCancel = true; + } imageUrl = data.get("image"); Intent intent = new Intent(this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -82,7 +105,7 @@ public class MessagingService extends FirebaseMessagingService { .setSmallIcon(R.drawable.mini_icon) .setContentTitle(messageTitle) .setContentText(messageBody) - .setAutoCancel(true) + .setAutoCancel(autoCancel) .setSound(defaultSoundUri) .setContentIntent(pendingIntent); if (URLUtil.isValidUrl(imageUrl)) { @@ -95,8 +118,9 @@ public class MessagingService extends FirebaseMessagingService { for (int i = 1; i <= 3; i++) { if (data.containsKey("action" + i)) { Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class); - Log.d(TAG, "Putting a tag to the action: " + nTag); - broadcastIntent.putExtra("tag", nTag); + if (autoCancel) { + broadcastIntent.putExtra("tag", nTag); + } broadcastIntent.putExtra("actionData", data.get("action" + i + "_data")); PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT); notificationBuilder.addAction(R.drawable.mini_icon, data.get("action" + i), actionIntent); diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java index 4c0ef69..7030a83 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java @@ -20,11 +20,12 @@ public class NotificationActionReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String rawActionData = intent.getStringExtra("actionData"); - String notificationTag = intent.getStringExtra("tag"); - Log.d(TAG, "Canceling notification by tag: " + notificationTag); - Log.d(TAG, "action data: " + rawActionData); - NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(notificationTag, 0); + if (intent.hasExtra("tag")) { + String notificationTag = intent.getStringExtra("tag"); + Log.d(TAG, "Canceling notification by tag: " + notificationTag); + NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(notificationTag, 0); + } SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); String webhookId = prefs.getString("flutter.app-webhook-id", null); if (webhookId != null) { From 8eb15ab9a4a39852fd156993c8ea16123dfb08c1 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 11:05:16 +0000 Subject: [PATCH 03/12] Notification channel description --- .../hassclient/MessagingService.java | 14 +++++--------- .../hassclient/NotificationActionReceiver.java | 7 ++----- .../com/keyboardcrumbs/hassclient/SendTask.java | 2 -- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java index f1efd52..bcb5a83 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java @@ -15,7 +15,6 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import androidx.core.app.NotificationCompat; -import android.util.Log; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; @@ -31,10 +30,8 @@ public class MessagingService extends FirebaseMessagingService { @Override public void onMessageReceived(RemoteMessage remoteMessage) { - Log.d(TAG, "From: " + remoteMessage.getFrom()); Map data = remoteMessage.getData(); if (data.size() > 0) { - Log.d(TAG, "Message data payload: " + data); if (data.containsKey("body") || data.containsKey("title")) { sendNotification(data); } @@ -43,18 +40,18 @@ public class MessagingService extends FirebaseMessagingService { @Override public void onNewToken(String token) { - Log.d(TAG, "Refreshed token: " + token); //TODO update token } private void sendNotification(Map data) { - String channelId, messageBody, messageTitle, imageUrl; - String nTag; + String channelId, messageBody, messageTitle, imageUrl, nTag, channelDescription; boolean autoCancel; if (!data.containsKey("channelId")) { channelId = "ha_notify"; + channelDescription = "Default notification channel"; } else { channelId = data.get("channelId"); + channelDescription = channelId; } if (!data.containsKey("body")) { messageBody = ""; @@ -71,7 +68,6 @@ public class MessagingService extends FirebaseMessagingService { } else { nTag = data.get("tag"); } - Log.d(TAG, "Notification tag: " + nTag); if (data.containsKey("dismiss")) { try { boolean dismiss = Boolean.parseBoolean(data.get("dismiss")); @@ -132,8 +128,8 @@ public class MessagingService extends FirebaseMessagingService { // Since android Oreo notification channel is needed. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(channelId, - "Home Assistant notifications", - NotificationManager.IMPORTANCE_DEFAULT); + channelDescription, + NotificationManager.IMPORTANCE_HIGH); notificationManager.createNotificationChannel(channel); } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java index 7030a83..949f37f 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java @@ -22,7 +22,6 @@ public class NotificationActionReceiver extends BroadcastReceiver { String rawActionData = intent.getStringExtra("actionData"); if (intent.hasExtra("tag")) { String notificationTag = intent.getStringExtra("tag"); - Log.d(TAG, "Canceling notification by tag: " + notificationTag); NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(notificationTag, 0); } @@ -36,7 +35,6 @@ public class NotificationActionReceiver extends BroadcastReceiver { ":" + prefs.getString("flutter.hassio-port", "") + "/api/webhook/" + webhookId; JSONObject actionData = new JSONObject(rawActionData); - Log.d(TAG, "request url: " + requestUrl); if (URLUtil.isValidUrl(requestUrl)) { JSONObject dataToSend = new JSONObject(); JSONObject requestData = new JSONObject(); @@ -53,17 +51,16 @@ public class NotificationActionReceiver extends BroadcastReceiver { } dataToSend.put("data", requestData); String stringRequest = dataToSend.toString(); - Log.d(TAG, "Data to send home: " + stringRequest); SendTask sendTask = new SendTask(); sendTask.execute(requestUrl, stringRequest); } else { - Log.w(TAG, "Invalid url"); + Log.w(TAG, "Invalid HA url"); } } catch (Exception e) { Log.e(TAG, "Error handling notification action", e); } } else { - Log.d(TAG, "Webhook id not found"); + Log.w(TAG, "Webhook id not found"); } } } \ No newline at end of file diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java index c04192a..76571cd 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java @@ -26,7 +26,6 @@ public class SendTask extends AsyncTask { String data = params[1]; try { - Log.d(TAG, "Connecting and sending..."); URL url = new URL(urlString); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("POST"); @@ -38,7 +37,6 @@ public class SendTask extends AsyncTask { int responseCode = urlConnection.getResponseCode(); - Log.d(TAG, "responseCode: " + responseCode); urlConnection.disconnect(); } catch (Exception e) { Log.e(TAG, "Error sending data", e); From 9608983994dac89b791894e72f278d588f85f280 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 11:06:08 +0000 Subject: [PATCH 04/12] Bump version code --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 249db6b..1417da1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 0.0.0+1146 +version: 0.0.0+1147 environment: From a8efe7dbb655ca68dafad3cb6dc3c61a72792fd6 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 11:16:22 +0000 Subject: [PATCH 05/12] Remove mobile app integration version check --- lib/managers/app_settings.dart | 2 -- .../mobile_app_integration_manager.class.dart | 27 ++----------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/lib/managers/app_settings.dart b/lib/managers/app_settings.dart index 9e7d0e3..a7dc289 100644 --- a/lib/managers/app_settings.dart +++ b/lib/managers/app_settings.dart @@ -23,7 +23,6 @@ class AppSettings { String fcmToken; double haVersion; bool scrollBadges; - int appIntegrationVersion; AppTheme appTheme; final int defaultLocationUpdateIntervalMinutes = 20; Duration locationUpdateInterval; @@ -46,7 +45,6 @@ class AppSettings { _port = prefs.getString('hassio-port'); webhookId = prefs.getString('app-webhook-id'); mobileAppDeviceName = prefs.getString('app-integration-device-name'); - appIntegrationVersion = prefs.getInt('app-integration-version') ?? 0; scrollBadges = prefs.getBool('scroll-badges') ?? true; displayHostname = "$_domain:$_port"; webSocketAPIEndpoint = diff --git a/lib/managers/mobile_app_integration_manager.class.dart b/lib/managers/mobile_app_integration_manager.class.dart index 45dd716..72f6b79 100644 --- a/lib/managers/mobile_app_integration_manager.class.dart +++ b/lib/managers/mobile_app_integration_manager.class.dart @@ -2,8 +2,6 @@ part of '../main.dart'; class MobileAppIntegrationManager { - static const INTEGRATION_VERSION = 3; - static final _appRegistrationData = { "device_name": "", "app_version": "$appVersion", @@ -49,10 +47,8 @@ class MobileAppIntegrationManager { Logger.d("Processing registration responce..."); var responseObject = json.decode(response); AppSettings().webhookId = responseObject["webhook_id"]; - AppSettings().appIntegrationVersion = INTEGRATION_VERSION; AppSettings().save({ - 'app-webhook-id': responseObject["webhook_id"], - 'app-integration-version': INTEGRATION_VERSION + 'app-webhook-id': responseObject["webhook_id"] }).then((prefs) { completer.complete(); eventBus.fire(ShowPopupEvent( @@ -98,12 +94,7 @@ class MobileAppIntegrationManager { Logger.w("No registration data in response. MobileApp integration was removed or broken"); _askToRegisterApp(); } else { - if (INTEGRATION_VERSION > AppSettings().appIntegrationVersion) { - Logger.d('App registration needs to be updated'); - _askToRemoveAndRegisterApp(); - } else { - Logger.d('App registration works fine'); - } + Logger.d('App registration works fine'); } completer.complete(); }).catchError((e) { @@ -137,20 +128,6 @@ class MobileAppIntegrationManager { )); } - static void _askToRemoveAndRegisterApp() { - eventBus.fire(ShowPopupEvent( - popup: Popup( - title: "Mobile app integration needs to be updated", - body: "You need to update HA Client integration to continue using notifications and location tracking. Please remove 'Mobile App' integration for this device from your Home Assistant and restart Home Assistant. Then go back to HA Client to create app integration again.", - positiveText: "Ok", - negativeText: "Report an issue", - onNegative: () { - Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new"); - }, - ) - )); - } - static void _askToRegisterApp() { eventBus.fire(ShowPopupEvent( popup: RegisterAppPopup( From 141a68faf7200b8a2cf6166c90856cee9a1c40cb Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 11:34:55 +0000 Subject: [PATCH 06/12] Update readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 81f149e..d593bf9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # HA Client ## Native Android client for Home Assistant -### With notifications and Lovelace UI support +### With actionable notifications, location tracking and Lovelace UI support Visit [ha-client.app](http://ha-client.app/) for more info. @@ -12,3 +12,7 @@ Discuss it on [Discord](https://discord.gg/u9vq7QE) or at [Home Assistant commun #### Last release build status [![Codemagic build status](https://api.codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/status_badge.svg)](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build) + +#### Special thanks to +- [Crewski](https://github.com/Crewski) for his [HANotify](https://github.com/Crewski/HANotify) +- [Home Assistant](https://github.com/home-assistant) for some support and [Home Assistant](https://www.home-assistant.io/) \ No newline at end of file From 4493975676c476e025694a6f9f5a9c8f3969862c Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 11:58:14 +0000 Subject: [PATCH 07/12] defer fcm token load --- .../hassclient/MainActivity.java | 2 +- lib/managers/app_settings.dart | 7 +- .../mobile_app_integration_manager.class.dart | 171 +++++++++--------- 3 files changed, 92 insertions(+), 88 deletions(-) diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java index e3e25f8..a187a8c 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java @@ -35,7 +35,7 @@ public class MainActivity extends FlutterActivity { Context context = getActivity(); SharedPreferences.Editor editor = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit(); String token = task.getResult().getToken(); - editor.putString("flutter.fcm-token", token); + editor.putString("flutter.push-token", token); editor.commit(); } } diff --git a/lib/managers/app_settings.dart b/lib/managers/app_settings.dart index a7dc289..3b7d030 100644 --- a/lib/managers/app_settings.dart +++ b/lib/managers/app_settings.dart @@ -20,7 +20,6 @@ class AppSettings { String tempToken; String oauthUrl; String webhookId; - String fcmToken; double haVersion; bool scrollBadges; AppTheme appTheme; @@ -40,7 +39,6 @@ class AppSettings { if (full) { Logger.d('Loading settings...'); SharedPreferences prefs = await SharedPreferences.getInstance(); - fcmToken = prefs.getString('fcm-token'); _domain = prefs.getString('hassio-domain'); _port = prefs.getString('hassio-port'); webhookId = prefs.getString('app-webhook-id'); @@ -69,6 +67,11 @@ class AppSettings { } } + Future loadSingle(String key) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.get('$key'); + } + Future save(Map settings) async { if (settings != null && settings.isNotEmpty) { SharedPreferences prefs = await SharedPreferences.getInstance(); diff --git a/lib/managers/mobile_app_integration_manager.class.dart b/lib/managers/mobile_app_integration_manager.class.dart index 72f6b79..488a694 100644 --- a/lib/managers/mobile_app_integration_manager.class.dart +++ b/lib/managers/mobile_app_integration_manager.class.dart @@ -24,94 +24,95 @@ class MobileAppIntegrationManager { static Future checkAppRegistration() { Completer completer = Completer(); _appRegistrationData["device_name"] = AppSettings().mobileAppDeviceName ?? getDefaultDeviceName(); - (_appRegistrationData["app_data"] as Map)["push_token"] = "${AppSettings().fcmToken}"; - if (AppSettings().webhookId == null) { - Logger.d("Mobile app was not registered yet. Registering..."); - var registrationData = Map.from(_appRegistrationData); - registrationData.addAll({ - "app_id": "ha_client", - "app_name": "$appName", - "os_name": DeviceInfoManager().osName, - "supports_encryption": false, - }); - if (AppSettings().haVersion >= 104) { + AppSettings().loadSingle('push-token').then((fcmToken) { + (_appRegistrationData["app_data"] as Map)["push_token"] = "$fcmToken"; + if (AppSettings().webhookId == null) { + Logger.d("Mobile app was not registered yet. Registering..."); + var registrationData = Map.from(_appRegistrationData); registrationData.addAll({ - "device_id": "${DeviceInfoManager().unicDeviceId}" + "app_id": "ha_client", + "app_name": "$appName", + "os_name": DeviceInfoManager().osName, + "supports_encryption": false, + }); + if (AppSettings().haVersion >= 104) { + registrationData.addAll({ + "device_id": "${DeviceInfoManager().unicDeviceId}" + }); + } + ConnectionManager().sendHTTPPost( + endPoint: "/api/mobile_app/registrations", + includeAuthHeader: true, + data: json.encode(registrationData) + ).then((response) { + Logger.d("Processing registration responce..."); + var responseObject = json.decode(response); + AppSettings().webhookId = responseObject["webhook_id"]; + AppSettings().save({ + 'app-webhook-id': responseObject["webhook_id"] + }).then((prefs) { + completer.complete(); + eventBus.fire(ShowPopupEvent( + popup: Popup( + title: "Mobile app Integration was created", + body: "HA Client was registered as MobileApp in your Home Assistant. To start using notifications you need to restart your Home Assistant", + positiveText: "Restart now", + negativeText: "Later", + onPositive: () { + ConnectionManager().callService(domain: "homeassistant", service: "restart"); + }, + ) + )); + }); + }).catchError((e) { + completer.complete(); + if (e is http.Response) { + Logger.e("Error registering the app: ${e.statusCode}: ${e.body}"); + } else { + Logger.e("Error registering the app: ${e?.toString()}"); + } + _showError(); + }); + } else { + Logger.d("App was previously registered. Checking..."); + var updateData = { + "type": "update_registration", + "data": _appRegistrationData + }; + ConnectionManager().sendHTTPPost( + endPoint: "/api/webhook/${AppSettings().webhookId}", + includeAuthHeader: false, + data: json.encode(updateData) + ).then((response) { + var registrationData; + try { + registrationData = json.decode(response); + } catch (e) { + registrationData = null; + } + if (registrationData == null || registrationData.isEmpty) { + Logger.w("No registration data in response. MobileApp integration was removed or broken"); + _askToRegisterApp(); + } else { + Logger.d('App registration works fine'); + } + completer.complete(); + }).catchError((e) { + if (e is http.Response && e.statusCode == 410) { + Logger.w("MobileApp integration was removed"); + _askToRegisterApp(); + } else if (e is http.Response) { + Logger.w("Error updating app registration: ${e.statusCode}: ${e.body}"); + _showError(); + } else { + Logger.w("Error updating app registration: ${e?.toString()}"); + _showError(); + } + completer.complete(); }); } - ConnectionManager().sendHTTPPost( - endPoint: "/api/mobile_app/registrations", - includeAuthHeader: true, - data: json.encode(registrationData) - ).then((response) { - Logger.d("Processing registration responce..."); - var responseObject = json.decode(response); - AppSettings().webhookId = responseObject["webhook_id"]; - AppSettings().save({ - 'app-webhook-id': responseObject["webhook_id"] - }).then((prefs) { - completer.complete(); - eventBus.fire(ShowPopupEvent( - popup: Popup( - title: "Mobile app Integration was created", - body: "HA Client was registered as MobileApp in your Home Assistant. To start using notifications you need to restart your Home Assistant", - positiveText: "Restart now", - negativeText: "Later", - onPositive: () { - ConnectionManager().callService(domain: "homeassistant", service: "restart"); - }, - ) - )); - }); - }).catchError((e) { - completer.complete(); - if (e is http.Response) { - Logger.e("Error registering the app: ${e.statusCode}: ${e.body}"); - } else { - Logger.e("Error registering the app: ${e?.toString()}"); - } - _showError(); - }); - return completer.future; - } else { - Logger.d("App was previously registered. Checking..."); - var updateData = { - "type": "update_registration", - "data": _appRegistrationData - }; - ConnectionManager().sendHTTPPost( - endPoint: "/api/webhook/${AppSettings().webhookId}", - includeAuthHeader: false, - data: json.encode(updateData) - ).then((response) { - var registrationData; - try { - registrationData = json.decode(response); - } catch (e) { - registrationData = null; - } - if (registrationData == null || registrationData.isEmpty) { - Logger.w("No registration data in response. MobileApp integration was removed or broken"); - _askToRegisterApp(); - } else { - Logger.d('App registration works fine'); - } - completer.complete(); - }).catchError((e) { - if (e is http.Response && e.statusCode == 410) { - Logger.w("MobileApp integration was removed"); - _askToRegisterApp(); - } else if (e is http.Response) { - Logger.w("Error updating app registration: ${e.statusCode}: ${e.body}"); - _showError(); - } else { - Logger.w("Error updating app registration: ${e?.toString()}"); - _showError(); - } - completer.complete(); - }); - return completer.future; - } + }); + return completer.future; } static void _showError() { From 9a5e35b024eed80a64e2c96f715101ba4c5767c4 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 12:34:53 +0000 Subject: [PATCH 08/12] Fix notification event data --- .../keyboardcrumbs/hassclient/NotificationActionReceiver.java | 3 +++ lib/main.dart | 4 +--- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java index 949f37f..bc04126 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java @@ -48,6 +48,9 @@ public class NotificationActionReceiver extends BroadcastReceiver { } else { dataToSend.put("type", "fire_event"); requestData.put("event_type", "ha_client_event"); + JSONObject eventData = new JSONObject(); + eventData.put("action", actionData.getString("action")); + requestData.put("event_data", eventData); } dataToSend.put("data", requestData); String stringRequest = dataToSend.toString(); diff --git a/lib/main.dart b/lib/main.dart index 47ea6f3..5d6deb9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,10 +16,8 @@ import 'package:http/http.dart' as http; import 'package:charts_flutter/flutter.dart' as charts; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_custom_tabs/flutter_custom_tabs.dart'; -//import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:device_info/device_info.dart'; -//import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'plugins/dynamic_multi_column_layout.dart'; import 'plugins/spoiler_card.dart'; @@ -161,7 +159,7 @@ EventBus eventBus = new EventBus(); //FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); const String appName = 'HA Client'; const String appVersion = String.fromEnvironment('versionName', defaultValue: '0.0.0'); -const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.1.0.md'; +const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.1.0-b2.md'; Future _reportError(dynamic error, dynamic stackTrace) async { // Print the exception to the console. diff --git a/pubspec.yaml b/pubspec.yaml index 1417da1..b504487 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 0.0.0+1147 +version: 0.0.0+1148 environment: From 80b5763530f3206ceda99785ba40af712c0e3402 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 14:09:45 +0000 Subject: [PATCH 09/12] FCM token update and waiting --- .../hassclient/MainActivity.java | 5 +- .../hassclient/MessagingService.java | 3 +- .../hassclient/UpdateTokenTask.java | 46 +++++ .../mobile_app_integration_manager.class.dart | 192 ++++++++++-------- 4 files changed, 156 insertions(+), 90 deletions(-) create mode 100644 android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java index a187a8c..3952a1f 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java @@ -33,10 +33,9 @@ public class MainActivity extends FlutterActivity { public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { Context context = getActivity(); - SharedPreferences.Editor editor = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit(); String token = task.getResult().getToken(); - editor.putString("flutter.push-token", token); - editor.commit(); + UpdateTokenTask updateTokenTask = new UpdateTokenTask(context); + updateTokenTask.execute(token); } } }); diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java index bcb5a83..0b7f1aa 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java @@ -40,7 +40,8 @@ public class MessagingService extends FirebaseMessagingService { @Override public void onNewToken(String token) { - //TODO update token + UpdateTokenTask updateTokenTask = new UpdateTokenTask(this); + updateTokenTask.execute(token); } private void sendNotification(Map data) { diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java new file mode 100644 index 0000000..ff3c9a8 --- /dev/null +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java @@ -0,0 +1,46 @@ +package com.keyboardcrumbs.hassclient; + +import android.util.Log; +import android.os.AsyncTask; + +import java.net.URL; +import java.net.HttpURLConnection; +import java.io.OutputStream; + +import android.webkit.URLUtil; + +import org.json.JSONObject; +import android.content.SharedPreferences; +import android.content.Context; +import java.lang.ref.WeakReference; + + +public class UpdateTokenTask extends AsyncTask { + + private static final String TAG = "UpdateTokenTask"; + + private WeakReference contextRef; + + public UpdateTokenTask(Context context){ + contextRef = new WeakReference<>(context); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + @Override + protected String doInBackground(String... params) { + Log.d(TAG, "Updating push token"); + Context context = contextRef.get(); + if (context != null) { + String token = params[0]; + SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("flutter.notification-token", token); + editor.commit(); + } + return null; + } +} \ No newline at end of file diff --git a/lib/managers/mobile_app_integration_manager.class.dart b/lib/managers/mobile_app_integration_manager.class.dart index 488a694..ad7fec2 100644 --- a/lib/managers/mobile_app_integration_manager.class.dart +++ b/lib/managers/mobile_app_integration_manager.class.dart @@ -21,97 +21,117 @@ class MobileAppIntegrationManager { return '${HomeAssistant().userName}\'s ${DeviceInfoManager().model}'; } - static Future checkAppRegistration() { + static Future checkAppRegistration() async { + int attempts = 1; + bool done = false; + Logger.d("[MobileAppIntegrationManager] Stratring mobile app integration check..."); + while (attempts <= 5 && !done) { + Logger.d("[MobileAppIntegrationManager] check attempt $attempts"); + String fcmToken = await AppSettings().loadSingle('notification-token'); + if (fcmToken != null) { + Logger.d("[MobileAppIntegrationManager] token exist"); + await _doCheck(fcmToken); + done = true; + } else { + Logger.d("[MobileAppIntegrationManager] no fcm token. Retry in 5 seconds"); + attempts++; + await Future.delayed(Duration(seconds: 5)); + } + } + if (!done) { + Logger.e("[MobileAppIntegrationManager] No FCM token"); + } + } + + static Future _doCheck(String fcmToken) { Completer completer = Completer(); _appRegistrationData["device_name"] = AppSettings().mobileAppDeviceName ?? getDefaultDeviceName(); - AppSettings().loadSingle('push-token').then((fcmToken) { - (_appRegistrationData["app_data"] as Map)["push_token"] = "$fcmToken"; - if (AppSettings().webhookId == null) { - Logger.d("Mobile app was not registered yet. Registering..."); - var registrationData = Map.from(_appRegistrationData); + (_appRegistrationData["app_data"] as Map)["push_token"] = "$fcmToken"; + if (AppSettings().webhookId == null) { + Logger.d("Mobile app was not registered yet. Registering..."); + var registrationData = Map.from(_appRegistrationData); + registrationData.addAll({ + "app_id": "ha_client", + "app_name": "$appName", + "os_name": DeviceInfoManager().osName, + "supports_encryption": false, + }); + if (AppSettings().haVersion >= 104) { registrationData.addAll({ - "app_id": "ha_client", - "app_name": "$appName", - "os_name": DeviceInfoManager().osName, - "supports_encryption": false, - }); - if (AppSettings().haVersion >= 104) { - registrationData.addAll({ - "device_id": "${DeviceInfoManager().unicDeviceId}" - }); - } - ConnectionManager().sendHTTPPost( - endPoint: "/api/mobile_app/registrations", - includeAuthHeader: true, - data: json.encode(registrationData) - ).then((response) { - Logger.d("Processing registration responce..."); - var responseObject = json.decode(response); - AppSettings().webhookId = responseObject["webhook_id"]; - AppSettings().save({ - 'app-webhook-id': responseObject["webhook_id"] - }).then((prefs) { - completer.complete(); - eventBus.fire(ShowPopupEvent( - popup: Popup( - title: "Mobile app Integration was created", - body: "HA Client was registered as MobileApp in your Home Assistant. To start using notifications you need to restart your Home Assistant", - positiveText: "Restart now", - negativeText: "Later", - onPositive: () { - ConnectionManager().callService(domain: "homeassistant", service: "restart"); - }, - ) - )); - }); - }).catchError((e) { - completer.complete(); - if (e is http.Response) { - Logger.e("Error registering the app: ${e.statusCode}: ${e.body}"); - } else { - Logger.e("Error registering the app: ${e?.toString()}"); - } - _showError(); - }); - } else { - Logger.d("App was previously registered. Checking..."); - var updateData = { - "type": "update_registration", - "data": _appRegistrationData - }; - ConnectionManager().sendHTTPPost( - endPoint: "/api/webhook/${AppSettings().webhookId}", - includeAuthHeader: false, - data: json.encode(updateData) - ).then((response) { - var registrationData; - try { - registrationData = json.decode(response); - } catch (e) { - registrationData = null; - } - if (registrationData == null || registrationData.isEmpty) { - Logger.w("No registration data in response. MobileApp integration was removed or broken"); - _askToRegisterApp(); - } else { - Logger.d('App registration works fine'); - } - completer.complete(); - }).catchError((e) { - if (e is http.Response && e.statusCode == 410) { - Logger.w("MobileApp integration was removed"); - _askToRegisterApp(); - } else if (e is http.Response) { - Logger.w("Error updating app registration: ${e.statusCode}: ${e.body}"); - _showError(); - } else { - Logger.w("Error updating app registration: ${e?.toString()}"); - _showError(); - } - completer.complete(); + "device_id": "${DeviceInfoManager().unicDeviceId}" }); } - }); + ConnectionManager().sendHTTPPost( + endPoint: "/api/mobile_app/registrations", + includeAuthHeader: true, + data: json.encode(registrationData) + ).then((response) { + Logger.d("Processing registration responce..."); + var responseObject = json.decode(response); + AppSettings().webhookId = responseObject["webhook_id"]; + AppSettings().save({ + 'app-webhook-id': responseObject["webhook_id"] + }).then((prefs) { + completer.complete(); + eventBus.fire(ShowPopupEvent( + popup: Popup( + title: "Mobile app Integration was created", + body: "HA Client was registered as MobileApp in your Home Assistant. To start using notifications you need to restart your Home Assistant", + positiveText: "Restart now", + negativeText: "Later", + onPositive: () { + ConnectionManager().callService(domain: "homeassistant", service: "restart"); + }, + ) + )); + }); + }).catchError((e) { + completer.complete(); + if (e is http.Response) { + Logger.e("Error registering the app: ${e.statusCode}: ${e.body}"); + } else { + Logger.e("Error registering the app: ${e?.toString()}"); + } + _showError(); + }); + } else { + Logger.d("App was previously registered. Checking..."); + var updateData = { + "type": "update_registration", + "data": _appRegistrationData + }; + ConnectionManager().sendHTTPPost( + endPoint: "/api/webhook/${AppSettings().webhookId}", + includeAuthHeader: false, + data: json.encode(updateData) + ).then((response) { + var registrationData; + try { + registrationData = json.decode(response); + } catch (e) { + registrationData = null; + } + if (registrationData == null || registrationData.isEmpty) { + Logger.w("No registration data in response. MobileApp integration was removed or broken"); + _askToRegisterApp(); + } else { + Logger.d('App registration works fine'); + } + completer.complete(); + }).catchError((e) { + if (e is http.Response && e.statusCode == 410) { + Logger.w("MobileApp integration was removed"); + _askToRegisterApp(); + } else if (e is http.Response) { + Logger.w("Error updating app registration: ${e.statusCode}: ${e.body}"); + _showError(); + } else { + Logger.w("Error updating app registration: ${e?.toString()}"); + _showError(); + } + completer.complete(); + }); + } return completer.future; } From 95ca80949f59519697d98da3ca60408971b444cc Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 14:10:29 +0000 Subject: [PATCH 10/12] bump version code --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b504487..d958509 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 0.0.0+1148 +version: 0.0.0+1149 environment: From 9a09a83dc6442f66467b195961c4591c09d10154 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 15:41:29 +0000 Subject: [PATCH 11/12] Request FCM token from native --- .../hassclient/MainActivity.java | 41 +++++++++++++------ .../hassclient/UpdateTokenTask.java | 2 +- .../mobile_app_integration_manager.class.dart | 28 ++++++------- pubspec.yaml | 2 +- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java index 3952a1f..0bc01b6 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java @@ -11,6 +11,9 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.firebase.iid.FirebaseInstanceId; @@ -18,27 +21,41 @@ import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; public class MainActivity extends FlutterActivity { + + private static final String CHANNEL = "com.keyboardcrumbs.hassclient/native"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); + new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler( + new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + if (call.method.equals("getFCMToken")) { + FirebaseInstanceId.getInstance().getInstanceId() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Context context = getActivity(); + String token = task.getResult().getToken(); + UpdateTokenTask updateTokenTask = new UpdateTokenTask(context); + updateTokenTask.execute(token); + result.success(token); + } else { + result.error("fcm_error", task.getException().getMessage(), task.getException()); + } + } + }); + } + } + } + ); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - FirebaseInstanceId.getInstance().getInstanceId() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Context context = getActivity(); - String token = task.getResult().getToken(); - UpdateTokenTask updateTokenTask = new UpdateTokenTask(context); - updateTokenTask.execute(token); - } - } - }); } } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java index ff3c9a8..91455c0 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java @@ -38,7 +38,7 @@ public class UpdateTokenTask extends AsyncTask { String token = params[0]; SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - editor.putString("flutter.notification-token", token); + editor.putString("flutter.npush-token", token); editor.commit(); } return null; diff --git a/lib/managers/mobile_app_integration_manager.class.dart b/lib/managers/mobile_app_integration_manager.class.dart index ad7fec2..7b2575f 100644 --- a/lib/managers/mobile_app_integration_manager.class.dart +++ b/lib/managers/mobile_app_integration_manager.class.dart @@ -21,26 +21,22 @@ class MobileAppIntegrationManager { return '${HomeAssistant().userName}\'s ${DeviceInfoManager().model}'; } + static const platform = const MethodChannel('com.keyboardcrumbs.hassclient/native'); + static Future checkAppRegistration() async { - int attempts = 1; - bool done = false; - Logger.d("[MobileAppIntegrationManager] Stratring mobile app integration check..."); - while (attempts <= 5 && !done) { - Logger.d("[MobileAppIntegrationManager] check attempt $attempts"); - String fcmToken = await AppSettings().loadSingle('notification-token'); - if (fcmToken != null) { - Logger.d("[MobileAppIntegrationManager] token exist"); + String fcmToken = await AppSettings().loadSingle('npush-token'); + if (fcmToken != null) { + Logger.d("[MobileAppIntegrationManager] token exist"); + await _doCheck(fcmToken); + } else { + Logger.d("[MobileAppIntegrationManager] no fcm token. Requesting..."); + try { + fcmToken = await platform.invokeMethod('getFCMToken'); await _doCheck(fcmToken); - done = true; - } else { - Logger.d("[MobileAppIntegrationManager] no fcm token. Retry in 5 seconds"); - attempts++; - await Future.delayed(Duration(seconds: 5)); + } on PlatformException catch (e) { + Logger.e('[MobileAppIntegrationManager] Failed to get FCM token from native: ${e.message}'); } } - if (!done) { - Logger.e("[MobileAppIntegrationManager] No FCM token"); - } } static Future _doCheck(String fcmToken) { diff --git a/pubspec.yaml b/pubspec.yaml index d958509..04d2d3f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 0.0.0+1149 +version: 0.0.0+1150 environment: From e295a364654e0477e5fc66b4ea1a5cffa935f566 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Mon, 25 May 2020 15:57:57 +0000 Subject: [PATCH 12/12] 1151 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 04d2d3f..9f37468 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 0.0.0+1150 +version: 0.0.0+1151 environment: