diff --git a/app/src/main/java/com/app/missednotificationsreminder/binding/model/SettingsViewModel.java b/app/src/main/java/com/app/missednotificationsreminder/binding/model/SettingsViewModel.java index 552b70a..fa8f203 100644 --- a/app/src/main/java/com/app/missednotificationsreminder/binding/model/SettingsViewModel.java +++ b/app/src/main/java/com/app/missednotificationsreminder/binding/model/SettingsViewModel.java @@ -1,6 +1,7 @@ package com.app.missednotificationsreminder.binding.model; import android.Manifest; +import android.content.ActivityNotFoundException; import android.content.Context; import android.os.Vibrator; import android.text.TextUtils; @@ -11,7 +12,6 @@ import com.app.missednotificationsreminder.di.qualifiers.ForActivity; import com.app.missednotificationsreminder.service.ReminderNotificationListenerService; import com.app.missednotificationsreminder.service.util.ReminderNotificationListenerServiceUtils; -import com.app.missednotificationsreminder.ui.activity.ApplicationsSelectionActivity; import com.app.missednotificationsreminder.ui.view.SettingsView; import com.app.missednotificationsreminder.util.BatteryUtils; import com.tbruyelle.rxpermissions.RxPermissions; @@ -34,7 +34,7 @@ public class SettingsViewModel extends BaseViewModel { /** * Permissions required by the application */ - static String[] REQUIRED_PERMISSIONS = new String[] { + static String[] REQUIRED_PERMISSIONS = new String[]{ Manifest.permission.WAKE_LOCK, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.VIBRATE, @@ -113,7 +113,7 @@ public void checkBatteryOptimizationDisabled() { /** * Check whether all required permissions are granted */ - public void checkPermissions(){ + public void checkPermissions() { monitor(Observable .from(REQUIRED_PERMISSIONS) .filter(permission -> !RxPermissions.getInstance(context).isGranted(permission)) @@ -125,7 +125,7 @@ public void checkPermissions(){ /** * Check whether the vibration is allowed on device */ - public void checkVibrationAvailable(){ + public void checkVibrationAvailable() { vibrationSettingsVisible.set(mVibrator.hasVibrator()); } @@ -144,7 +144,7 @@ public void onManageAccessButtonPressed(View v) { * * @param v */ - public void onGrantPermissionsPressed(View v){ + public void onGrantPermissionsPressed(View v) { Timber.d("onGrantPermissionsPressed"); monitor(RxPermissions .getInstance(context) @@ -163,7 +163,13 @@ public void onGrantPermissionsPressed(View v){ * @param v */ public void onManageBatteryOptimizationPressed(View v) { - context.startActivity(BatteryUtils.getBatteryOptimizationIntent(context)); + try { + context.startActivity(BatteryUtils.getBatteryOptimizationIntent(context)); + } catch (ActivityNotFoundException ex) { + // possibly Oppo phone + Timber.e(ex); + // TODO notify view + } } /** diff --git a/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindableObject.java b/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindableObject.java index 6289e5b..a662643 100644 --- a/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindableObject.java +++ b/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindableObject.java @@ -27,7 +27,7 @@ * @author Eugene Popovich */ public class BindableObject extends BaseObservable { - T mValue; + volatile T mValue; /** * Creates an empty observable object diff --git a/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindingAdapterUtils.java b/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindingAdapterUtils.java index 5637d30..1bf427d 100644 --- a/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindingAdapterUtils.java +++ b/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindingAdapterUtils.java @@ -263,7 +263,13 @@ public static void loadImage(ImageView view, RequestCreator requestCreator) { view.setImageBitmap(null); } else { // load - requestCreator.into(view); + try { + // load + requestCreator.into(view); + } catch (Exception e) { + // catch unexpected IllegalArgumentException errors + Timber.e(e); + } } } } diff --git a/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerService.java b/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerService.java index 26a8449..bf3ed32 100644 --- a/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerService.java +++ b/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerService.java @@ -19,6 +19,7 @@ import android.text.TextUtils; import android.view.Display; +import com.app.missednotificationsreminder.binding.util.BindableBoolean; import com.app.missednotificationsreminder.binding.util.BindableObject; import com.app.missednotificationsreminder.binding.util.RxBindingUtils; import com.app.missednotificationsreminder.di.Injector; @@ -109,6 +110,10 @@ public class ReminderNotificationListenerService extends AbstractReminderNotific * Current ringer mode value holder */ BindableObject mRingerMode = new BindableObject<>(); + /** + * Current ready state value holder + */ + BindableBoolean mReady = new BindableBoolean(false); /** * The pending intent used by alarm manager to wake the service */ @@ -162,7 +167,7 @@ public void onCreate() { Timber.d("onCreate"); // inject dependencies - ObjectGraph appGraph = Injector.obtain(getApplication()); + ObjectGraph appGraph = Injector.obtain(getApplicationContext()); appGraph.inject(this); // TODO workaround for updated interval measurements if (reminderInterval.get() < reminderIntervalMinimum) { @@ -190,6 +195,7 @@ public void onCreate() { reminderEnabled.asObservable() .skip(1) // skip initial value emitted right after the subscription .filter(enabled -> enabled) // if reminder enabled + .filter(__ -> mReady.get()) .subscribe(b -> sendCheckWakingConditionsCommand())); mSubscriptions.add( reminderEnabled.asObservable() @@ -249,12 +255,17 @@ public void onCreate() { .doOnNext(v -> Timber.d("Ringer mode changed to %d", v)) .filter(__ -> respectRingerMode.get()) .map(__ -> true))) + .filter(__ -> mReady.get()) .subscribe(data -> { // restart alarm with new conditions if necessary stopWaking(); sendCheckWakingConditionsCommand(); })); - sendCheckWakingConditionsCommand(); + // await for the service become ready event to send check waking conditions command + mSubscriptions.add(RxBindingUtils.valueChanged(mReady) + .filter(ready -> ready) + .take(1) + .subscribe(__ -> sendCheckWakingConditionsCommand())); } /** @@ -424,18 +435,24 @@ public void onDestroy() { @Override public void onNotificationPosted() { Timber.d("onNotificationPosted"); - checkWakingConditions(); + if (mReady.get()) { + checkWakingConditions(); + } } @Override public void onNotificationRemoved() { Timber.d("onNotificationRemoved"); - if(mActive.get() && !checkNotificationForAtLeastOnePackageExists(selectedApplications.get(), ignorePersistentNotifications.get())) { + if (mActive.get() && !checkNotificationForAtLeastOnePackageExists(selectedApplications.get(), ignorePersistentNotifications.get())) { // stop alarm if there are no more notifications to update stopWaking(); } } + @Override public void onReady() { + mReady.set(true); + } + /** * The broadcast receiver for ringer mode changed events */ @@ -519,7 +536,7 @@ public void onReceive(Context context, Intent intent) { mMediaPlayer.reset(); // use alternative stream if respect ringer mode is disabled mMediaPlayer.setAudioStreamType(respectRingerMode.get() ? AudioManager.STREAM_NOTIFICATION : AudioManager.STREAM_MUSIC); - if(respectRingerMode.get() && (mRingerMode.get() == AudioManager.RINGER_MODE_VIBRATE || mRingerMode.get() == AudioManager.RINGER_MODE_SILENT)){ + if (respectRingerMode.get() && (mRingerMode.get() == AudioManager.RINGER_MODE_VIBRATE || mRingerMode.get() == AudioManager.RINGER_MODE_SILENT)) { // mute sound explicitly for silent ringer modes because some user claims that sound is not muted on their devices in such cases mMediaPlayer.setVolume(0f, 0f); } else { diff --git a/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerServiceInterface.java b/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerServiceInterface.java index fd0164d..b990a88 100644 --- a/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerServiceInterface.java +++ b/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerServiceInterface.java @@ -27,4 +27,9 @@ public interface ReminderNotificationListenerServiceInterface { * @return true if notification for at least one package is found, false otherwise */ boolean checkNotificationForAtLeastOnePackageExists(Collection packages, boolean ignoreOngoing); + + /** + * The method which should be called when a notification listener service is ready + */ + void onReady(); } diff --git a/app/src/v14/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java b/app/src/v14/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java index a1e3ace..86f94b8 100644 --- a/app/src/v14/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java +++ b/app/src/v14/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java @@ -41,6 +41,7 @@ public abstract class AbstractReminderNotificationListenerService extends Access super.onCreate(); mNotificationParser = new NotificationParser(getApplicationContext()); mStatusBarWindowUtils = new StatusBarWindowUtils(getPackageManager()); + onReady(); } @Override @@ -180,7 +181,7 @@ public boolean checkNotificationForAtLeastOnePackageExists(Collection pa String packageName = notificationData.packageName.toString(); Timber.d("checkNotificationForAtLeastOnePackageExists: checking package %1$s", packageName); boolean contains = packages.contains(packageName); - if(contains && ignoreOngoing && (notificationData.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT){ + if (contains && ignoreOngoing && (notificationData.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { Timber.d("checkNotificationForAtLeastOnePackageExists: found ongoing match which is requested to be skipped"); continue; } diff --git a/app/src/v18/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java b/app/src/v18/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java index 84bb817..5f29fdf 100644 --- a/app/src/v18/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java +++ b/app/src/v18/java/com/app/missednotificationsreminder/service/AbstractReminderNotificationListenerService.java @@ -1,6 +1,8 @@ package com.app.missednotificationsreminder.service; +import android.annotation.TargetApi; import android.app.Notification; +import android.os.Build; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -15,6 +17,14 @@ */ public abstract class AbstractReminderNotificationListenerService extends NotificationListenerService implements ReminderNotificationListenerServiceInterface { + @Override public void onCreate() { + super.onCreate(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // such as onListenerConnected is not called on anroid prior L call onReady method explicitly + onReady(); + } + } + @Override public void onNotificationPosted(StatusBarNotification sbn) { Timber.d("onNotificationPosted: for package %1$s", sbn.getPackageName()); @@ -31,20 +41,29 @@ public void onNotificationRemoved(StatusBarNotification sbn) { @Override public boolean checkNotificationForAtLeastOnePackageExists(Collection packages, boolean ignoreOngoing) { boolean result = false; - for (StatusBarNotification notificationData : getActiveNotifications()) { - String packageName = notificationData.getPackageName(); - Timber.d("checkNotificationForAtLeastOnePackageExists: checking package %1$s", packageName); - boolean contains = packages.contains(packageName); - if (contains && ignoreOngoing && (notificationData.getNotification().flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { - Timber.d("checkNotificationForAtLeastOnePackageExists: found ongoing match which is requested to be skipped"); - continue; - } - result |= contains; - if (result) { - Timber.d("checkNotificationForAtLeastOnePackageExists: found match for package %1$s", packageName); - break; + StatusBarNotification[] activeNotifications = getActiveNotifications(); + if (activeNotifications != null) { + for (StatusBarNotification notificationData : getActiveNotifications()) { + String packageName = notificationData.getPackageName(); + Timber.d("checkNotificationForAtLeastOnePackageExists: checking package %1$s", packageName); + boolean contains = packages.contains(packageName); + if (contains && ignoreOngoing && (notificationData.getNotification().flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { + Timber.d("checkNotificationForAtLeastOnePackageExists: found ongoing match which is requested to be skipped"); + continue; + } + result |= contains; + if (result) { + Timber.d("checkNotificationForAtLeastOnePackageExists: found match for package %1$s", packageName); + break; + } } } return result; } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override public void onListenerConnected() { + super.onListenerConnected(); + onReady(); + } }