Skip to content

Commit

Permalink
Fixed various stability issues caused by NotificationListenerService …
Browse files Browse the repository at this point in the history
…is not yet ready when app tries to check waking conditions and other cases
  • Loading branch information
httpdispatch committed Oct 21, 2017
1 parent becca88 commit 090e5f5
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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))
Expand All @@ -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());
}

Expand All @@ -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)
Expand All @@ -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
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* @author Eugene Popovich
*/
public class BindableObject<T> extends BaseObservable {
T mValue;
volatile T mValue;

/**
* Creates an empty observable object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -109,6 +110,10 @@ public class ReminderNotificationListenerService extends AbstractReminderNotific
* Current ringer mode value holder
*/
BindableObject<Integer> mRingerMode = new BindableObject<>();
/**
* Current ready state value holder
*/
BindableBoolean mReady = new BindableBoolean(false);
/**
* The pending intent used by alarm manager to wake the service
*/
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()));
}

/**
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ public interface ReminderNotificationListenerServiceInterface {
* @return true if notification for at least one package is found, false otherwise
*/
boolean checkNotificationForAtLeastOnePackageExists(Collection<String> packages, boolean ignoreOngoing);

/**
* The method which should be called when a notification listener service is ready
*/
void onReady();
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public abstract class AbstractReminderNotificationListenerService extends Access
super.onCreate();
mNotificationParser = new NotificationParser(getApplicationContext());
mStatusBarWindowUtils = new StatusBarWindowUtils(getPackageManager());
onReady();
}

@Override
Expand Down Expand Up @@ -180,7 +181,7 @@ public boolean checkNotificationForAtLeastOnePackageExists(Collection<String> 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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());
Expand All @@ -31,20 +41,29 @@ public void onNotificationRemoved(StatusBarNotification sbn) {
@Override
public boolean checkNotificationForAtLeastOnePackageExists(Collection<String> 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();
}
}

0 comments on commit 090e5f5

Please sign in to comment.