Merge "Move unused apps count calculation to bg thread" into sc-dev
diff --git a/src/com/android/settings/applications/AppDashboardFragment.java b/src/com/android/settings/applications/AppDashboardFragment.java
index 65f2b61..ff12595 100644
--- a/src/com/android/settings/applications/AppDashboardFragment.java
+++ b/src/com/android/settings/applications/AppDashboardFragment.java
@@ -69,6 +69,10 @@
use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle());
mAppsPreferenceController = use(AppsPreferenceController.class);
mAppsPreferenceController.setFragment(this /* fragment */);
+
+ final HibernatedAppsPreferenceController hibernatedAppsPreferenceController =
+ use(HibernatedAppsPreferenceController.class);
+ getSettingsLifecycle().addObserver(hibernatedAppsPreferenceController);
}
@Override
diff --git a/src/com/android/settings/applications/HibernatedAppsPreferenceController.java b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
index 8d12811..bf12b86 100644
--- a/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
+++ b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
@@ -30,40 +30,111 @@
import android.provider.DeviceConfig;
import android.util.ArrayMap;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.google.common.annotations.VisibleForTesting;
+
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* A preference controller handling the logic for updating summary of hibernated apps.
*/
-public final class HibernatedAppsPreferenceController extends BasePreferenceController {
+public final class HibernatedAppsPreferenceController extends BasePreferenceController
+ implements LifecycleObserver {
private static final String TAG = "HibernatedAppsPrefController";
private static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
"auto_revoke_unused_threshold_millis2";
private static final long DEFAULT_UNUSED_THRESHOLD_MS = TimeUnit.DAYS.toMillis(90);
+ private PreferenceScreen mScreen;
+ private int mUnusedCount = 0;
+ private boolean mLoadingUnusedApps;
+ private final Executor mBackgroundExecutor;
+ private final Executor mMainExecutor;
public HibernatedAppsPreferenceController(Context context, String preferenceKey) {
+ this(context, preferenceKey, Executors.newSingleThreadExecutor(),
+ context.getMainExecutor());
+ }
+
+ @VisibleForTesting
+ HibernatedAppsPreferenceController(Context context, String preferenceKey,
+ Executor bgExecutor, Executor mainExecutor) {
super(context, preferenceKey);
+ mBackgroundExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
}
@Override
public int getAvailabilityStatus() {
- return isHibernationEnabled() && getNumHibernated() > 0
+ return isHibernationEnabled() && mUnusedCount > 0
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public CharSequence getSummary() {
- final int numHibernated = getNumHibernated();
return mContext.getResources().getQuantityString(
- R.plurals.unused_apps_summary, numHibernated, numHibernated);
+ R.plurals.unused_apps_summary, mUnusedCount, mUnusedCount);
}
- private int getNumHibernated() {
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mScreen = screen;
+ }
+
+ /**
+ * On lifecycle resume event.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {
+ updatePreference();
+ }
+
+ private void updatePreference() {
+ if (mScreen == null) {
+ return;
+ }
+ if (!mLoadingUnusedApps) {
+ loadUnusedCount(unusedCount -> {
+ mUnusedCount = unusedCount;
+ mLoadingUnusedApps = false;
+ mMainExecutor.execute(() -> {
+ super.displayPreference(mScreen);
+ Preference pref = mScreen.findPreference(mPreferenceKey);
+ refreshSummary(pref);
+ });
+ });
+ mLoadingUnusedApps = true;
+ }
+ }
+
+ /**
+ * Asynchronously load the count of unused apps.
+ *
+ * @param callback callback to call when the number of unused apps is calculated
+ */
+ private void loadUnusedCount(@NonNull UnusedCountLoadedCallback callback) {
+ mBackgroundExecutor.execute(() -> {
+ final int unusedCount = getUnusedCount();
+ callback.onUnusedCountLoaded(unusedCount);
+ });
+ }
+
+ @WorkerThread
+ private int getUnusedCount() {
// TODO(b/187465752): Find a way to export this logic from PermissionController module
final PackageManager pm = mContext.getPackageManager();
final AppHibernationManager ahm = mContext.getSystemService(AppHibernationManager.class);
@@ -71,6 +142,7 @@
int numHibernated = hibernatedPackages.size();
// Also need to count packages that are auto revoked but not hibernated.
+ int numAutoRevoked = 0;
final UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class);
final long now = System.currentTimeMillis();
final long unusedThreshold = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
@@ -97,17 +169,24 @@
for (String perm : pi.requestedPermissions) {
if ((pm.getPermissionFlags(perm, packageName, mContext.getUser())
& PackageManager.FLAG_PERMISSION_AUTO_REVOKED) != 0) {
- numHibernated++;
+ numAutoRevoked++;
break;
}
}
}
}
- return numHibernated;
+ return numHibernated + numAutoRevoked;
}
private static boolean isHibernationEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, false);
}
+
+ /**
+ * Callback for when we've determined the number of unused apps.
+ */
+ private interface UnusedCountLoadedCallback {
+ void onUnusedCountLoaded(int unusedCount);
+ }
}
diff --git a/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java
index 39c966d..0682983 100644
--- a/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java
@@ -41,9 +41,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
+import android.os.Looper;
import android.os.RemoteException;
import android.provider.DeviceConfig;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -67,12 +71,16 @@
AppHibernationManager mAppHibernationManager;
@Mock
IUsageStatsManager mIUsageStatsManager;
+ PreferenceScreen mPreferenceScreen;
private static final String KEY = "key";
private Context mContext;
private HibernatedAppsPreferenceController mController;
@Before
public void setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
MockitoAnnotations.initMocks(this);
DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED,
"true", false);
@@ -82,7 +90,15 @@
.thenReturn(mAppHibernationManager);
when(mContext.getSystemService(UsageStatsManager.class)).thenReturn(
new UsageStatsManager(mContext, mIUsageStatsManager));
- mController = new HibernatedAppsPreferenceController(mContext, KEY);
+
+ PreferenceManager manager = new PreferenceManager(mContext);
+ mPreferenceScreen = manager.createPreferenceScreen(mContext);
+ Preference preference = mock(Preference.class);
+ when(preference.getKey()).thenReturn(KEY);
+ mPreferenceScreen.addPreference(preference);
+
+ mController = new HibernatedAppsPreferenceController(mContext, KEY,
+ command -> command.run(), command -> command.run());
}
@Test
@@ -100,7 +116,9 @@
Arrays.asList(hibernatedPkg, new PackageInfo()));
when(mContext.getResources()).thenReturn(mock(Resources.class));
- mController.getSummary();
+ mController.displayPreference(mPreferenceScreen);
+ mController.onResume();
+
verify(mContext.getResources()).getQuantityString(anyInt(), eq(1), eq(1));
}
@@ -111,7 +129,9 @@
Arrays.asList(autoRevokedPkg, new PackageInfo()));
when(mContext.getResources()).thenReturn(mock(Resources.class));
- mController.getSummary();
+ mController.displayPreference(mPreferenceScreen);
+ mController.onResume();
+
verify(mContext.getResources()).getQuantityString(anyInt(), eq(1), eq(1));
}
@@ -123,7 +143,9 @@
Arrays.asList(usedAutoRevokedPkg, new PackageInfo()));
when(mContext.getResources()).thenReturn(mock(Resources.class));
- mController.getSummary();
+ mController.displayPreference(mPreferenceScreen);
+ mController.onResume();
+
verify(mContext.getResources()).getQuantityString(anyInt(), eq(0), eq(0));
}