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:
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user