Merge "Create a new icon cache mechanism for memory improvement"
This commit is contained in:
committed by
Android (Google) Code Review
commit
828654cc0b
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.applications;
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
|
||||
/**
|
||||
* Cache app icon for management.
|
||||
*/
|
||||
public class AppIconCacheManager {
|
||||
private static final String TAG = "AppIconCacheManager";
|
||||
private static final float CACHE_RATIO = 0.1f;
|
||||
private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
|
||||
private static final String DELIMITER = ":";
|
||||
private static AppIconCacheManager sAppIconCacheManager;
|
||||
private final LruCache<String, Drawable> mDrawableCache;
|
||||
|
||||
private AppIconCacheManager() {
|
||||
mDrawableCache = new LruCache<String, Drawable>(MAX_CACHE_SIZE_IN_KB) {
|
||||
@Override
|
||||
protected int sizeOf(String key, Drawable drawable) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024;
|
||||
}
|
||||
// Rough estimate each pixel will use 4 bytes by default.
|
||||
return drawable.getIntrinsicHeight() * drawable.getIntrinsicWidth() * 4 / 1024;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link AppIconCacheManager} instance.
|
||||
*/
|
||||
public static synchronized AppIconCacheManager getInstance() {
|
||||
if (sAppIconCacheManager == null) {
|
||||
sAppIconCacheManager = new AppIconCacheManager();
|
||||
}
|
||||
return sAppIconCacheManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put app icon to cache
|
||||
*
|
||||
* @param packageName of icon
|
||||
* @param uid of packageName
|
||||
* @param drawable app icon
|
||||
*/
|
||||
public void put(String packageName, int uid, Drawable drawable) {
|
||||
final String key = getKey(packageName, uid);
|
||||
if (key == null || drawable == null || drawable.getIntrinsicHeight() < 0
|
||||
|| drawable.getIntrinsicWidth() < 0) {
|
||||
Log.w(TAG, "Invalid key or drawable.");
|
||||
return;
|
||||
}
|
||||
mDrawableCache.put(key, drawable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app icon from cache.
|
||||
*
|
||||
* @param packageName of icon
|
||||
* @param uid of packageName
|
||||
* @return app icon
|
||||
*/
|
||||
public Drawable get(String packageName, int uid) {
|
||||
final String key = getKey(packageName, uid);
|
||||
if (key == null) {
|
||||
Log.w(TAG, "Invalid key with package or uid.");
|
||||
return null;
|
||||
}
|
||||
final Drawable cachedDrawable = mDrawableCache.get(key);
|
||||
return cachedDrawable != null ? cachedDrawable.mutate() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release cache.
|
||||
*/
|
||||
public static void release() {
|
||||
if (sAppIconCacheManager != null) {
|
||||
sAppIconCacheManager.mDrawableCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getKey(String packageName, int uid) {
|
||||
if (packageName == null || uid < 0) {
|
||||
return null;
|
||||
}
|
||||
return packageName + DELIMITER + UserHandle.getUserId(uid);
|
||||
}
|
||||
|
||||
private static int getMaxCacheInKb() {
|
||||
return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.usb.IUsbManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
@@ -35,7 +36,9 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -212,4 +215,82 @@ public class AppUtils {
|
||||
UserHandle.myUserId());
|
||||
return TextUtils.equals(packageName, defaultBrowserPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app icon by app entry.
|
||||
*
|
||||
* @param context caller's context
|
||||
* @param appEntry AppEntry of ApplicationsState
|
||||
* @return app icon of the app entry
|
||||
*/
|
||||
public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) {
|
||||
if (appEntry == null || appEntry.info == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance();
|
||||
final String packageName = appEntry.info.packageName;
|
||||
final int uid = appEntry.info.uid;
|
||||
|
||||
Drawable icon = appIconCacheManager.get(packageName, uid);
|
||||
if (icon == null) {
|
||||
if (appEntry.apkFile != null && appEntry.apkFile.exists()) {
|
||||
icon = Utils.getBadgedIcon(context, appEntry.info);
|
||||
appIconCacheManager.put(packageName, uid, icon);
|
||||
} else {
|
||||
setAppEntryMounted(appEntry, /* mounted= */ false);
|
||||
icon = context.getDrawable(
|
||||
com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
|
||||
}
|
||||
} else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) {
|
||||
// If the app wasn't mounted but is now mounted, reload its icon.
|
||||
setAppEntryMounted(appEntry, /* mounted= */ true);
|
||||
icon = Utils.getBadgedIcon(context, appEntry.info);
|
||||
appIconCacheManager.put(packageName, uid, icon);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app icon from cache by app entry.
|
||||
*
|
||||
* @param appEntry AppEntry of ApplicationsState
|
||||
* @return app icon of the app entry
|
||||
*/
|
||||
public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) {
|
||||
return appEntry == null || appEntry.info == null ? null
|
||||
: AppIconCacheManager.getInstance().get(
|
||||
appEntry.info.packageName,
|
||||
appEntry.info.uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload the top N icons of app entry list.
|
||||
*
|
||||
* @param context caller's context
|
||||
* @param appEntries AppEntry list of ApplicationsState
|
||||
* @param number the number of Top N icons of the appEntries
|
||||
*/
|
||||
public static void preloadTopIcons(Context context,
|
||||
ArrayList<ApplicationsState.AppEntry> appEntries, int number) {
|
||||
if (appEntries == null || appEntries.isEmpty() || number <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Math.min(appEntries.size(), number); i++) {
|
||||
final ApplicationsState.AppEntry entry = appEntries.get(i);
|
||||
ThreadUtils.postOnBackgroundThread(() -> {
|
||||
getIcon(context, entry);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) {
|
||||
if (appEntry.mounted != mounted) {
|
||||
synchronized (appEntry) {
|
||||
appEntry.mounted = mounted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ public class ApplicationsState {
|
||||
private static final Object sLock = new Object();
|
||||
private static final Pattern REMOVE_DIACRITICALS_PATTERN
|
||||
= Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
|
||||
private static final String SETTING_PKG = "com.android.settings";
|
||||
|
||||
@VisibleForTesting
|
||||
static ApplicationsState sInstance;
|
||||
@@ -492,6 +493,9 @@ public class ApplicationsState {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting Android T, this method will not be used if {@link AppIconCacheManager} is applied.
|
||||
*/
|
||||
public void ensureIcon(AppEntry entry) {
|
||||
if (entry.icon != null) {
|
||||
return;
|
||||
@@ -758,6 +762,10 @@ public class ApplicationsState {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isAppIconCacheEnabled(Context context) {
|
||||
return SETTING_PKG.equals(context.getPackageName());
|
||||
}
|
||||
|
||||
void rebuildActiveSessions() {
|
||||
synchronized (mEntriesMap) {
|
||||
if (!mSessionsChanged) {
|
||||
@@ -806,6 +814,11 @@ public class ApplicationsState {
|
||||
} else {
|
||||
mHasLifecycle = false;
|
||||
}
|
||||
|
||||
if (isAppIconCacheEnabled(mContext)) {
|
||||
// Skip the preloading all icons step to save memory usage.
|
||||
mFlags = mFlags & ~FLAG_SESSION_REQUEST_ICONS;
|
||||
}
|
||||
}
|
||||
|
||||
@SessionFlags
|
||||
@@ -814,7 +827,12 @@ public class ApplicationsState {
|
||||
}
|
||||
|
||||
public void setSessionFlags(@SessionFlags int flags) {
|
||||
mFlags = flags;
|
||||
if (isAppIconCacheEnabled(mContext)) {
|
||||
// Skip the preloading all icons step to save memory usage.
|
||||
mFlags = flags & ~FLAG_SESSION_REQUEST_ICONS;
|
||||
} else {
|
||||
mFlags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
@@ -1576,6 +1594,10 @@ public class ApplicationsState {
|
||||
|
||||
// Need to synchronize on 'this' for the following.
|
||||
public ApplicationInfo info;
|
||||
/**
|
||||
* Starting Android T, this field will not be used if {@link AppIconCacheManager} is
|
||||
* applied.
|
||||
*/
|
||||
public Drawable icon;
|
||||
public String sizeStr;
|
||||
public String internalSizeStr;
|
||||
@@ -1596,15 +1618,11 @@ public class ApplicationsState {
|
||||
this.size = SIZE_UNKNOWN;
|
||||
this.sizeStale = true;
|
||||
ensureLabel(context);
|
||||
// Speed up the cache of the icon and label description if they haven't been created.
|
||||
ThreadUtils.postOnBackgroundThread(() -> {
|
||||
if (this.icon == null) {
|
||||
this.ensureIconLocked(context);
|
||||
}
|
||||
if (this.labelDescription == null) {
|
||||
this.ensureLabelDescriptionLocked(context);
|
||||
}
|
||||
});
|
||||
// Speed up the cache of the label description if they haven't been created.
|
||||
if (this.labelDescription == null) {
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> this.ensureLabelDescriptionLocked(context));
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureLabel(Context context) {
|
||||
@@ -1620,7 +1638,15 @@ public class ApplicationsState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting Android T, this method will not be used if {@link AppIconCacheManager} is
|
||||
* applied.
|
||||
*/
|
||||
boolean ensureIconLocked(Context context) {
|
||||
if (isAppIconCacheEnabled(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.icon == null) {
|
||||
if (this.apkFile.exists()) {
|
||||
this.icon = Utils.getBadgedIcon(context, info);
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AppIconCacheManagerTest {
|
||||
|
||||
private static final String APP_PACKAGE_NAME = "com.test.app";
|
||||
private static final int APP_UID = 9999;
|
||||
|
||||
@Mock
|
||||
private Drawable mIcon;
|
||||
|
||||
private AppIconCacheManager mAppIconCacheManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mAppIconCacheManager = AppIconCacheManager.getInstance();
|
||||
doReturn(10).when(mIcon).getIntrinsicHeight();
|
||||
doReturn(10).when(mIcon).getIntrinsicWidth();
|
||||
doReturn(mIcon).when(mIcon).mutate();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
AppIconCacheManager.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_invalidPackageOrUid_shouldReturnNull() {
|
||||
assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_invalidPackageOrUid_shouldNotCrash() {
|
||||
mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon);
|
||||
// no crash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_invalidIcon_shouldNotCacheIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_invalidIconSize_shouldNotCacheIcon() {
|
||||
doReturn(-1).when(mIcon).getIntrinsicHeight();
|
||||
doReturn(-1).when(mIcon).getIntrinsicWidth();
|
||||
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_shouldCacheIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_noInstance_shouldNotCrash() {
|
||||
mAppIconCacheManager = null;
|
||||
|
||||
AppIconCacheManager.release();
|
||||
// no crash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_existInstance_shouldClearCache() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
AppIconCacheManager.release();
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AppUtilsTest {
|
||||
|
||||
private static final String APP_PACKAGE_NAME = "com.test.app";
|
||||
private static final int APP_UID = 9999;
|
||||
|
||||
@Mock
|
||||
private Drawable mIcon;
|
||||
|
||||
private Context mContext;
|
||||
private AppIconCacheManager mAppIconCacheManager;
|
||||
private ApplicationInfo mAppInfo;
|
||||
private ApplicationsState.AppEntry mAppEntry;
|
||||
private ArrayList<ApplicationsState.AppEntry> mAppEntries;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mAppIconCacheManager = AppIconCacheManager.getInstance();
|
||||
mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID);
|
||||
mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
|
||||
mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
|
||||
doReturn(mIcon).when(mIcon).mutate();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
AppIconCacheManager.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_nullAppEntry_shouldReturnNull() {
|
||||
assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = ShadowUtils.class)
|
||||
public void getIcon_noCachedIcon_shouldNotReturnNull() {
|
||||
assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_existCachedIcon_shouldReturnCachedIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconFromCache_nullAppEntry_shouldReturnNull() {
|
||||
assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconFromCache_shouldReturnCachedIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preloadTopIcons_nullAppEntries_shouldNotCrash() {
|
||||
AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1);
|
||||
// no crash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() {
|
||||
AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = ShadowUtils.class)
|
||||
public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException {
|
||||
AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1);
|
||||
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
|
||||
}
|
||||
|
||||
private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
|
||||
ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
|
||||
appEntry.label = "label";
|
||||
appEntry.mounted = true;
|
||||
final File apkFile = mock(File.class);
|
||||
doReturn(true).when(apkFile).exists();
|
||||
try {
|
||||
Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile");
|
||||
field.setAccessible(true);
|
||||
field.set(appEntry, apkFile);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
fail("Not able to mock apkFile: " + e);
|
||||
}
|
||||
return appEntry;
|
||||
}
|
||||
|
||||
private ApplicationInfo createApplicationInfo(String packageName, int uid) {
|
||||
ApplicationInfo appInfo = new ApplicationInfo();
|
||||
appInfo.sourceDir = "appPath";
|
||||
appInfo.packageName = packageName;
|
||||
appInfo.uid = uid;
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
@Implements(Utils.class)
|
||||
private static class ShadowUtils {
|
||||
@Implementation
|
||||
public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
|
||||
final Drawable icon = mock(Drawable.class);
|
||||
doReturn(10).when(icon).getIntrinsicHeight();
|
||||
doReturn(10).when(icon).getIntrinsicWidth();
|
||||
doReturn(icon).when(icon).mutate();
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.shadow.api.Shadow.extract;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.Application;
|
||||
import android.app.ApplicationPackageManager;
|
||||
import android.app.usage.StorageStats;
|
||||
import android.app.usage.StorageStatsManager;
|
||||
@@ -110,6 +111,7 @@ public class ApplicationsStateRoboTest {
|
||||
private ApplicationsState mApplicationsState;
|
||||
private Session mSession;
|
||||
|
||||
private Application mApplication;
|
||||
|
||||
@Mock
|
||||
private Callbacks mCallbacks;
|
||||
@@ -190,6 +192,7 @@ public class ApplicationsStateRoboTest {
|
||||
ShadowContextImpl shadowContext = Shadow.extract(
|
||||
RuntimeEnvironment.application.getBaseContext());
|
||||
shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
|
||||
mApplication = spy(RuntimeEnvironment.application);
|
||||
StorageStats storageStats = new StorageStats();
|
||||
storageStats.codeBytes = 10;
|
||||
storageStats.cacheBytes = 30;
|
||||
@@ -207,8 +210,7 @@ public class ApplicationsStateRoboTest {
|
||||
anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
|
||||
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState =
|
||||
ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
|
||||
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
|
||||
mApplicationsState.clearEntries();
|
||||
|
||||
mSession = mApplicationsState.newSession(mCallbacks);
|
||||
@@ -703,6 +705,23 @@ public class ApplicationsStateRoboTest {
|
||||
verify(mApplicationsState, never()).clearEntries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() {
|
||||
when(mApplication.getPackageName()).thenReturn("com.android.settings");
|
||||
mSession.onResume();
|
||||
|
||||
addApp(HOME_PACKAGE_NAME, 1);
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
for (AppEntry appEntry : appEntries) {
|
||||
assertThat(appEntry.icon).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps,
|
||||
ArrayList<ApplicationInfo> profileApps)
|
||||
throws RemoteException {
|
||||
|
||||
Reference in New Issue
Block a user