Merge "Move unused apps count calculation to bg thread" into sc-dev am: 603cd6c44c

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/14795269

Change-Id: I690efa597ac55bb601d7bf2608ee3e8b56d6e045
This commit is contained in:
Kevin Han
2021-06-04 23:44:09 +00:00
committed by Automerger Merge Worker
3 changed files with 116 additions and 11 deletions

View File

@@ -69,6 +69,10 @@ public class AppDashboardFragment extends DashboardFragment {
use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle()); use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle());
mAppsPreferenceController = use(AppsPreferenceController.class); mAppsPreferenceController = use(AppsPreferenceController.class);
mAppsPreferenceController.setFragment(this /* fragment */); mAppsPreferenceController.setFragment(this /* fragment */);
final HibernatedAppsPreferenceController hibernatedAppsPreferenceController =
use(HibernatedAppsPreferenceController.class);
getSettingsLifecycle().addObserver(hibernatedAppsPreferenceController);
} }
@Override @Override

View File

@@ -30,40 +30,111 @@ import android.content.pm.PackageManager;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import android.util.ArrayMap; 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.R;
import com.android.settings.core.BasePreferenceController; import com.android.settings.core.BasePreferenceController;
import com.google.common.annotations.VisibleForTesting;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* A preference controller handling the logic for updating summary of hibernated apps. * 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 TAG = "HibernatedAppsPrefController";
private static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS = private static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
"auto_revoke_unused_threshold_millis2"; "auto_revoke_unused_threshold_millis2";
private static final long DEFAULT_UNUSED_THRESHOLD_MS = TimeUnit.DAYS.toMillis(90); 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) { 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); super(context, preferenceKey);
mBackgroundExecutor = bgExecutor;
mMainExecutor = mainExecutor;
} }
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return isHibernationEnabled() && getNumHibernated() > 0 return isHibernationEnabled() && mUnusedCount > 0
? AVAILABLE : CONDITIONALLY_UNAVAILABLE; ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
} }
@Override @Override
public CharSequence getSummary() { public CharSequence getSummary() {
final int numHibernated = getNumHibernated();
return mContext.getResources().getQuantityString( 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 // TODO(b/187465752): Find a way to export this logic from PermissionController module
final PackageManager pm = mContext.getPackageManager(); final PackageManager pm = mContext.getPackageManager();
final AppHibernationManager ahm = mContext.getSystemService(AppHibernationManager.class); final AppHibernationManager ahm = mContext.getSystemService(AppHibernationManager.class);
@@ -71,6 +142,7 @@ public final class HibernatedAppsPreferenceController extends BasePreferenceCont
int numHibernated = hibernatedPackages.size(); int numHibernated = hibernatedPackages.size();
// Also need to count packages that are auto revoked but not hibernated. // Also need to count packages that are auto revoked but not hibernated.
int numAutoRevoked = 0;
final UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class); final UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class);
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
final long unusedThreshold = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, final long unusedThreshold = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
@@ -97,17 +169,24 @@ public final class HibernatedAppsPreferenceController extends BasePreferenceCont
for (String perm : pi.requestedPermissions) { for (String perm : pi.requestedPermissions) {
if ((pm.getPermissionFlags(perm, packageName, mContext.getUser()) if ((pm.getPermissionFlags(perm, packageName, mContext.getUser())
& PackageManager.FLAG_PERMISSION_AUTO_REVOKED) != 0) { & PackageManager.FLAG_PERMISSION_AUTO_REVOKED) != 0) {
numHibernated++; numAutoRevoked++;
break; break;
} }
} }
} }
} }
return numHibernated; return numHibernated + numAutoRevoked;
} }
private static boolean isHibernationEnabled() { private static boolean isHibernationEnabled() {
return DeviceConfig.getBoolean( return DeviceConfig.getBoolean(
NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, false); 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);
}
} }

View File

@@ -41,9 +41,13 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.provider.DeviceConfig; 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.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -67,12 +71,16 @@ public class HibernatedAppsPreferenceControllerTest {
AppHibernationManager mAppHibernationManager; AppHibernationManager mAppHibernationManager;
@Mock @Mock
IUsageStatsManager mIUsageStatsManager; IUsageStatsManager mIUsageStatsManager;
PreferenceScreen mPreferenceScreen;
private static final String KEY = "key"; private static final String KEY = "key";
private Context mContext; private Context mContext;
private HibernatedAppsPreferenceController mController; private HibernatedAppsPreferenceController mController;
@Before @Before
public void setUp() { public void setUp() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED,
"true", false); "true", false);
@@ -82,7 +90,15 @@ public class HibernatedAppsPreferenceControllerTest {
.thenReturn(mAppHibernationManager); .thenReturn(mAppHibernationManager);
when(mContext.getSystemService(UsageStatsManager.class)).thenReturn( when(mContext.getSystemService(UsageStatsManager.class)).thenReturn(
new UsageStatsManager(mContext, mIUsageStatsManager)); 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 @Test
@@ -100,7 +116,9 @@ public class HibernatedAppsPreferenceControllerTest {
Arrays.asList(hibernatedPkg, new PackageInfo())); Arrays.asList(hibernatedPkg, new PackageInfo()));
when(mContext.getResources()).thenReturn(mock(Resources.class)); when(mContext.getResources()).thenReturn(mock(Resources.class));
mController.getSummary(); mController.displayPreference(mPreferenceScreen);
mController.onResume();
verify(mContext.getResources()).getQuantityString(anyInt(), eq(1), eq(1)); verify(mContext.getResources()).getQuantityString(anyInt(), eq(1), eq(1));
} }
@@ -111,7 +129,9 @@ public class HibernatedAppsPreferenceControllerTest {
Arrays.asList(autoRevokedPkg, new PackageInfo())); Arrays.asList(autoRevokedPkg, new PackageInfo()));
when(mContext.getResources()).thenReturn(mock(Resources.class)); when(mContext.getResources()).thenReturn(mock(Resources.class));
mController.getSummary(); mController.displayPreference(mPreferenceScreen);
mController.onResume();
verify(mContext.getResources()).getQuantityString(anyInt(), eq(1), eq(1)); verify(mContext.getResources()).getQuantityString(anyInt(), eq(1), eq(1));
} }
@@ -123,7 +143,9 @@ public class HibernatedAppsPreferenceControllerTest {
Arrays.asList(usedAutoRevokedPkg, new PackageInfo())); Arrays.asList(usedAutoRevokedPkg, new PackageInfo()));
when(mContext.getResources()).thenReturn(mock(Resources.class)); when(mContext.getResources()).thenReturn(mock(Resources.class));
mController.getSummary(); mController.displayPreference(mPreferenceScreen);
mController.onResume();
verify(mContext.getResources()).getQuantityString(anyInt(), eq(0), eq(0)); verify(mContext.getResources()).getQuantityString(anyInt(), eq(0), eq(0));
} }