diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BackgroundMonitor.java b/briar-android/src/main/java/org/briarproject/briar/android/BackgroundMonitor.java new file mode 100644 index 000000000..114ad3300 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/BackgroundMonitor.java @@ -0,0 +1,54 @@ +package org.briarproject.briar.android; + +import android.app.Activity; +import android.app.Application.ActivityLifecycleCallbacks; +import android.os.Bundle; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +@NotNullByDefault +class BackgroundMonitor implements ActivityLifecycleCallbacks { + + private final AtomicInteger foregroundActivities = new AtomicInteger(0); + + boolean isRunningInBackground() { + return foregroundActivities.get() == 0; + } + + @Override + public void onActivityCreated(Activity a, @Nullable Bundle state) { + } + + @Override + public void onActivityStarted(Activity a) { + foregroundActivities.incrementAndGet(); + } + + @Override + public void onActivityResumed(Activity a) { + } + + @Override + public void onActivityPaused(Activity a) { + } + + @Override + public void onActivityStopped(Activity a) { + foregroundActivities.decrementAndGet(); + } + + @Override + public void onActivitySaveInstanceState(Activity a, + @Nullable Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity a) { + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java index 6ab1c9ee0..b7d3c17a5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java @@ -16,4 +16,6 @@ public interface BriarApplication { AndroidComponent getApplicationComponent(); SharedPreferences getDefaultSharedPreferences(); + + boolean isRunningInBackground(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java index 1a79bc4c2..ba909c75b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java @@ -1,5 +1,7 @@ package org.briarproject.briar.android; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; @@ -30,6 +32,8 @@ import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.logging.Logger; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.os.Build.VERSION.SDK_INT; import static java.util.logging.Level.FINE; import static java.util.logging.Level.INFO; import static org.acra.ReportField.ANDROID_VERSION; @@ -79,6 +83,7 @@ public class BriarApplicationImpl extends Application Logger.getLogger(BriarApplicationImpl.class.getName()); private final CachingLogHandler logHandler = new CachingLogHandler(); + private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor(); private AndroidComponent applicationComponent; private volatile SharedPreferences prefs; @@ -115,6 +120,9 @@ public class BriarApplicationImpl extends Application applicationComponent = createApplicationComponent(); EmojiManager.install(new GoogleEmojiProvider()); + + if (SDK_INT < 16) + registerActivityLifecycleCallbacks(backgroundMonitor); } protected AndroidComponent createApplicationComponent() { @@ -173,4 +181,15 @@ public class BriarApplicationImpl extends Application public SharedPreferences getDefaultSharedPreferences() { return prefs; } + + @Override + public boolean isRunningInBackground() { + if (SDK_INT >= 16) { + RunningAppProcessInfo info = new RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(info); + return (info.importance != IMPORTANCE_FOREGROUND); + } else { + return backgroundMonitor.isRunningInBackground(); + } + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java index 05d9a5012..606a6ba72 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java @@ -1,7 +1,5 @@ package org.briarproject.briar.android; -import android.app.ActivityManager; -import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -35,7 +33,6 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; @@ -74,6 +71,7 @@ public class BriarService extends Service { @Nullable private BroadcastReceiver receiver = null; + private BriarApplication app; @Inject AndroidNotificationManager notificationManager; @@ -93,8 +91,8 @@ public class BriarService extends Service { public void onCreate() { super.onCreate(); - BriarApplication application = (BriarApplication) getApplication(); - application.getApplicationComponent().inject(this); + app = (BriarApplication) getApplication(); + app.getApplicationComponent().inject(this); LOG.info("Created"); if (created.getAndSet(true)) { @@ -220,8 +218,8 @@ public class BriarService extends Service { public void onLowMemory() { super.onLowMemory(); LOG.warning("Memory is low"); - // Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16 - if (SDK_INT < 16) hideUi(); + // If we're not in the foreground, clear the UI to save memory + if (app.isRunningInBackground()) hideUi(); } @Override @@ -235,20 +233,16 @@ public class BriarService extends Service { LOG.info("Trim memory: near middle of LRU list"); } else if (level == TRIM_MEMORY_COMPLETE) { LOG.info("Trim memory: near end of LRU list"); - } else if (SDK_INT >= 16) { - if (level == TRIM_MEMORY_RUNNING_MODERATE) { - LOG.info("Trim memory: running moderately low"); - } else if (level == TRIM_MEMORY_RUNNING_LOW) { - LOG.info("Trim memory: running low"); - } else if (level == TRIM_MEMORY_RUNNING_CRITICAL) { - LOG.info("Trim memory: running critically low"); - // If we're not in the foreground, clear the UI to save memory - RunningAppProcessInfo info = new RunningAppProcessInfo(); - ActivityManager.getMyMemoryState(info); - if (info.importance != IMPORTANCE_FOREGROUND) hideUi(); - } else if (LOG.isLoggable(INFO)) { - LOG.info("Trim memory: unknown level " + level); - } + } else if (level == TRIM_MEMORY_RUNNING_MODERATE) { + LOG.info("Trim memory: running moderately low"); + } else if (level == TRIM_MEMORY_RUNNING_LOW) { + LOG.info("Trim memory: running low"); + } else if (level == TRIM_MEMORY_RUNNING_CRITICAL) { + // This level may be received if SDK_INT < 16, although the + // constant isn't declared until API level 16 + LOG.warning("Trim memory: running critically low"); + // If we're not in the foreground, clear the UI to save memory + if (app.isRunningInBackground()) hideUi(); } else if (LOG.isLoggable(INFO)) { LOG.info("Trim memory: unknown level " + level); } diff --git a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java index 8897136cc..9b7a892f7 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java @@ -63,4 +63,9 @@ public class TestBriarApplication extends Application public SharedPreferences getDefaultSharedPreferences() { return prefs; } + + @Override + public boolean isRunningInBackground() { + return false; + } }