diff options
3 files changed, 562 insertions, 143 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 3a0ae9f532bb..093b1bd2b8f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -16,6 +16,7 @@ package com.android.settingslib.applications; +import android.annotation.IntDef; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.Application; @@ -59,6 +60,8 @@ import com.android.internal.util.ArrayUtils; import java.io.File; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.text.Collator; import java.text.Normalizer; import java.text.Normalizer.Form; @@ -135,6 +138,38 @@ public class ApplicationsState { final BackgroundHandler mBackgroundHandler; final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper()); + /** Requests that the home app is loaded. */ + public static final int FLAG_SESSION_REQUEST_HOME_APP = 1 << 0; + + /** Requests that icons are loaded. */ + public static final int FLAG_SESSION_REQUEST_ICONS = 1 << 1; + + /** Requests that sizes are loaded. */ + public static final int FLAG_SESSION_REQUEST_SIZES = 1 << 2; + + /** Requests that launcher intents are resolved. */ + public static final int FLAG_SESSION_REQUEST_LAUNCHER = 1 << 3; + + /** Requests that leanback launcher intents are resolved. */ + public static final int FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER = 1 << 4; + + /** + * Flags to configure the session to request various types of info. + */ + @IntDef(prefix = { "FLAG_SESSION_" }, value = { + FLAG_SESSION_REQUEST_HOME_APP, + FLAG_SESSION_REQUEST_ICONS, + FLAG_SESSION_REQUEST_SIZES, + FLAG_SESSION_REQUEST_LAUNCHER, + FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SessionFlags {} + + public static final @SessionFlags int DEFAULT_SESSION_FLAGS = + FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS | + FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER; + private ApplicationsState(Application app) { mContext = app; mPm = mContext.getPackageManager(); @@ -265,7 +300,8 @@ public class ApplicationsState { } } - private void clearEntries() { + @VisibleForTesting + void clearEntries() { for (int i = 0; i < mEntriesMap.size(); i++) { mEntriesMap.valueAt(i).clear(); } @@ -346,7 +382,7 @@ public class ApplicationsState { if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock..."); synchronized (mEntriesMap) { AppEntry entry = mEntriesMap.get(userId).get(packageName); - if (entry != null && (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { + if (entry != null && hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED)) { mBackgroundHandler.post( () -> { try { @@ -595,6 +631,7 @@ public class ApplicationsState { } public class Session implements LifecycleObserver { + final Callbacks mCallbacks; boolean mResumed; @@ -609,6 +646,7 @@ public class ApplicationsState { boolean mRebuildForeground; private final boolean mHasLifecycle; + @SessionFlags private int mFlags = DEFAULT_SESSION_FLAGS; Session(Callbacks callbacks, Lifecycle lifecycle) { mCallbacks = callbacks; @@ -620,6 +658,14 @@ public class ApplicationsState { } } + public @SessionFlags int getSessionFlags() { + return mFlags; + } + + public void setSessionFlags(@SessionFlags int flags) { + mFlags = flags; + } + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); @@ -833,10 +879,11 @@ public class ApplicationsState { private class BackgroundHandler extends Handler { static final int MSG_REBUILD_LIST = 1; static final int MSG_LOAD_ENTRIES = 2; - static final int MSG_LOAD_ICONS = 3; - static final int MSG_LOAD_SIZES = 4; - static final int MSG_LOAD_LAUNCHER = 5; - static final int MSG_LOAD_HOME_APP = 6; + static final int MSG_LOAD_HOME_APP = 3; + static final int MSG_LOAD_LAUNCHER = 4; + static final int MSG_LOAD_LEANBACK_LAUNCHER = 5; + static final int MSG_LOAD_ICONS = 6; + static final int MSG_LOAD_SIZES = 7; boolean mRunning; @@ -860,6 +907,8 @@ public class ApplicationsState { } } + int flags = getCombinedSessionFlags(mSessions); + switch (msg.what) { case MSG_REBUILD_LIST: { } break; @@ -889,8 +938,8 @@ public class ApplicationsState { // happens because of the way we generate the list in // doResumeIfNeededLocked. AppEntry entry = mEntriesMap.get(0).get(info.packageName); - if (entry != null && - (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + if (entry != null && !hasFlag(entry.info.flags, + ApplicationInfo.FLAG_INSTALLED)) { mEntriesMap.get(0).remove(info.packageName); mAppEntries.remove(entry); } @@ -909,166 +958,206 @@ public class ApplicationsState { } } break; case MSG_LOAD_HOME_APP: { - final List<ResolveInfo> homeActivities = new ArrayList<>(); - mPm.getHomeActivities(homeActivities); - synchronized (mEntriesMap) { - final int entryCount = mEntriesMap.size(); - for (int i = 0; i < entryCount; i++) { - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock"); - final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i); - for (ResolveInfo activity : homeActivities) { - String packageName = activity.activityInfo.packageName; - AppEntry entry = userEntries.get(packageName); - if (entry != null) { - entry.isHomeApp = true; + if (hasFlag(flags, FLAG_SESSION_REQUEST_HOME_APP)) { + final List<ResolveInfo> homeActivities = new ArrayList<>(); + mPm.getHomeActivities(homeActivities); + synchronized (mEntriesMap) { + final int entryCount = mEntriesMap.size(); + for (int i = 0; i < entryCount; i++) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock"); + final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt( + i); + for (ResolveInfo activity : homeActivities) { + String packageName = activity.activityInfo.packageName; + AppEntry entry = userEntries.get(packageName); + if (entry != null) { + entry.isHomeApp = true; + } } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock"); } - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock"); } } sendEmptyMessage(MSG_LOAD_LAUNCHER); - } - break; - case MSG_LOAD_LAUNCHER: { - Intent launchIntent = new Intent(Intent.ACTION_MAIN, null) - .addCategory(Intent.CATEGORY_LAUNCHER); - for (int i = 0; i < mEntriesMap.size(); i++) { - int userId = mEntriesMap.keyAt(i); - // If we do not specify MATCH_DIRECT_BOOT_AWARE or - // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags - // according to the user's lock state. When the user is locked, components - // with ComponentInfo#directBootAware == false will be filtered. We should - // explicitly include both direct boot aware and unaware components here. - List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser( - launchIntent, - PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - userId - ); - synchronized (mEntriesMap) { - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock"); - HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i); - final int N = intents.size(); - for (int j = 0; j < N; j++) { - String packageName = intents.get(j).activityInfo.packageName; - AppEntry entry = userEntries.get(packageName); - if (entry != null) { - entry.hasLauncherEntry = true; - } else { - Log.w(TAG, "Cannot find pkg: " + packageName - + " on user " + userId); + } break; + case MSG_LOAD_LAUNCHER: + case MSG_LOAD_LEANBACK_LAUNCHER: { + if ((msg.what == MSG_LOAD_LAUNCHER && + hasFlag(flags, FLAG_SESSION_REQUEST_LAUNCHER)) + || (msg.what == MSG_LOAD_LEANBACK_LAUNCHER && + hasFlag(flags, FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER))) { + + Intent launchIntent = new Intent(Intent.ACTION_MAIN, null); + launchIntent.addCategory(msg.what == MSG_LOAD_LAUNCHER + ? Intent.CATEGORY_LAUNCHER : Intent.CATEGORY_LEANBACK_LAUNCHER); + for (int i = 0; i < mEntriesMap.size(); i++) { + int userId = mEntriesMap.keyAt(i); + // If we do not specify MATCH_DIRECT_BOOT_AWARE or + // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags + // according to the user's lock state. When the user is locked, + // components + // with ComponentInfo#directBootAware == false will be filtered. We should + // explicitly include both direct boot aware and unaware components here. + List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser( + launchIntent, + PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId + ); + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock"); + HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i); + final int N = intents.size(); + for (int j = 0; j < N; j++) { + ResolveInfo resolveInfo = intents.get(j); + String packageName = resolveInfo.activityInfo.packageName; + AppEntry entry = userEntries.get(packageName); + if (entry != null) { + entry.hasLauncherEntry = true; + entry.launcherEntryEnabled |= + resolveInfo.activityInfo.enabled; + } else { + Log.w(TAG, "Cannot find pkg: " + packageName + + " on user " + userId); + } } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock"); } - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock"); } - } - if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) { - mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED); + if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED); + } + } + if (msg.what == MSG_LOAD_LAUNCHER) { + sendEmptyMessage(MSG_LOAD_LEANBACK_LAUNCHER); + } else { + sendEmptyMessage(MSG_LOAD_ICONS); } - sendEmptyMessage(MSG_LOAD_ICONS); } break; case MSG_LOAD_ICONS: { - int numDone = 0; - synchronized (mEntriesMap) { - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock"); - for (int i=0; i<mAppEntries.size() && numDone<2; i++) { - AppEntry entry = mAppEntries.get(i); - if (entry.icon == null || !entry.mounted) { - synchronized (entry) { - if (entry.ensureIconLocked(mContext, mDrawableFactory)) { - if (!mRunning) { - mRunning = true; - Message m = mMainHandler.obtainMessage( - MainHandler.MSG_RUNNING_STATE_CHANGED, 1); - mMainHandler.sendMessage(m); + if (hasFlag(flags, FLAG_SESSION_REQUEST_ICONS)) { + int numDone = 0; + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock"); + for (int i = 0; i < mAppEntries.size() && numDone < 2; i++) { + AppEntry entry = mAppEntries.get(i); + if (entry.icon == null || !entry.mounted) { + synchronized (entry) { + if (entry.ensureIconLocked(mContext, mDrawableFactory)) { + if (!mRunning) { + mRunning = true; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 1); + mMainHandler.sendMessage(m); + } + numDone++; } - numDone++; } } } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock"); } - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock"); - } - if (numDone > 0) { - if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) { - mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED); + if (numDone > 0) { + if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED); + } + } + if (numDone >= 2) { + sendEmptyMessage(MSG_LOAD_ICONS); + break; } } - if (numDone >= 2) { - sendEmptyMessage(MSG_LOAD_ICONS); - } else { - sendEmptyMessage(MSG_LOAD_SIZES); - } + sendEmptyMessage(MSG_LOAD_SIZES); } break; case MSG_LOAD_SIZES: { - synchronized (mEntriesMap) { - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); - if (mCurComputingSizePkg != null) { - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); - return; - } + if (hasFlag(flags, FLAG_SESSION_REQUEST_SIZES)) { + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); + if (mCurComputingSizePkg != null) { + if (DEBUG_LOCKING) Log.v(TAG, + "MSG_LOAD_SIZES releasing: currently computing"); + return; + } - long now = SystemClock.uptimeMillis(); - for (int i=0; i<mAppEntries.size(); i++) { - AppEntry entry = mAppEntries.get(i); - if ((entry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 - && (entry.size == SIZE_UNKNOWN || entry.sizeStale)) { - if (entry.sizeLoadStart == 0 || - (entry.sizeLoadStart < (now-20*1000))) { - if (!mRunning) { - mRunning = true; - Message m = mMainHandler.obtainMessage( - MainHandler.MSG_RUNNING_STATE_CHANGED, 1); - mMainHandler.sendMessage(m); - } - entry.sizeLoadStart = now; - mCurComputingSizeUuid = entry.info.storageUuid; - mCurComputingSizePkg = entry.info.packageName; - mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid); - - mBackgroundHandler.post(() -> { - try { - final StorageStats stats = mStats.queryStatsForPackage( - mCurComputingSizeUuid, mCurComputingSizePkg, - UserHandle.of(mCurComputingSizeUserId)); - final PackageStats legacy = new PackageStats( - mCurComputingSizePkg, mCurComputingSizeUserId); - legacy.codeSize = stats.getCodeBytes(); - legacy.dataSize = stats.getDataBytes(); - legacy.cacheSize = stats.getCacheBytes(); - try { - mStatsObserver.onGetStatsCompleted(legacy, true); - } catch (RemoteException ignored) { - } - } catch (NameNotFoundException | IOException e) { - Log.w(TAG, "Failed to query stats: " + e); + long now = SystemClock.uptimeMillis(); + for (int i = 0; i < mAppEntries.size(); i++) { + AppEntry entry = mAppEntries.get(i); + if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED) + && (entry.size == SIZE_UNKNOWN || entry.sizeStale)) { + if (entry.sizeLoadStart == 0 || + (entry.sizeLoadStart < (now - 20 * 1000))) { + if (!mRunning) { + mRunning = true; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 1); + mMainHandler.sendMessage(m); + } + entry.sizeLoadStart = now; + mCurComputingSizeUuid = entry.info.storageUuid; + mCurComputingSizePkg = entry.info.packageName; + mCurComputingSizeUserId = UserHandle.getUserId( + entry.info.uid); + + mBackgroundHandler.post(() -> { try { - mStatsObserver.onGetStatsCompleted(null, false); - } catch (RemoteException ignored) { + final StorageStats stats = + mStats.queryStatsForPackage( + mCurComputingSizeUuid, + mCurComputingSizePkg, + UserHandle.of( + mCurComputingSizeUserId)); + final PackageStats legacy = new PackageStats( + mCurComputingSizePkg, + mCurComputingSizeUserId); + legacy.codeSize = stats.getCodeBytes(); + legacy.dataSize = stats.getDataBytes(); + legacy.cacheSize = stats.getCacheBytes(); + try { + mStatsObserver.onGetStatsCompleted(legacy, + true); + } catch (RemoteException ignored) { + } + } catch (NameNotFoundException | IOException e) { + Log.w(TAG, "Failed to query stats: " + e); + try { + mStatsObserver.onGetStatsCompleted(null, false); + } catch (RemoteException ignored) { + } } - } - }); + }); + } + if (DEBUG_LOCKING) Log.v(TAG, + "MSG_LOAD_SIZES releasing: now computing"); + return; } - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); - return; } + if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); + mRunning = false; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 0); + mMainHandler.sendMessage(m); + } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); } - if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { - mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); - mRunning = false; - Message m = mMainHandler.obtainMessage( - MainHandler.MSG_RUNNING_STATE_CHANGED, 0); - mMainHandler.sendMessage(m); - } - if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); } } break; } } + private @SessionFlags int getCombinedSessionFlags(List<Session> sessions) { + synchronized (mEntriesMap) { + int flags = 0; + for (Session session : sessions) { + flags |= session.mFlags; + } + return flags; + } + } + final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { if (!succeeded) { @@ -1257,6 +1346,11 @@ public class ApplicationsState { public boolean hasLauncherEntry; /** + * Whether the component that has a launcher intent filter is enabled. + */ + public boolean launcherEntryEnabled; + + /** * Whether or not it's a Home app. */ public boolean isHomeApp; @@ -1283,7 +1377,7 @@ public class ApplicationsState { // A location where extra info can be placed to be used by custom filters. public Object extraInfo; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public AppEntry(Context context, ApplicationInfo info, long id) { apkFile = new File(info.sourceDir); this.id = id; @@ -1336,6 +1430,10 @@ public class ApplicationsState { } } + private static boolean hasFlag(int flags, int flag) { + return (flags & flag) != 0; + } + /** * Compare by label, then package name, then uid. */ @@ -1449,13 +1547,13 @@ public class ApplicationsState { public boolean filterApp(AppEntry entry) { if (AppUtils.isInstant(entry.info)) { return false; - } else if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { return true; - } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) { return true; } else if (entry.hasLauncherEntry) { return true; - } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && entry.isHomeApp) { + } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp) { return true; } return false; @@ -1486,9 +1584,9 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { - if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { return true; - } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) { return true; } return false; @@ -1547,7 +1645,7 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { return !AppUtils.isInstant(entry.info) - && (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0; + && hasFlag(entry.info.privateFlags, ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS); } }; @@ -1589,7 +1687,7 @@ public class ApplicationsState { // TODO: Update for the new game category. boolean isGame; synchronized (info.info) { - isGame = ((info.info.flags & ApplicationInfo.FLAG_IS_GAME) != 0) + isGame = hasFlag(info.info.flags, ApplicationInfo.FLAG_IS_GAME) || info.info.category == ApplicationInfo.CATEGORY_GAME; } return isGame; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java new file mode 100644 index 000000000000..2dbabe0b76cc --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2017 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.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.shadow.api.Shadow.extract; + +import android.annotation.UserIdInt; +import android.app.ApplicationPackageManager; +import android.app.usage.IStorageStatsManager; +import android.app.usage.StorageStats; +import android.app.usage.StorageStatsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.UserHandle; +import android.util.IconDrawableFactory; + +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.ApplicationsState.Callbacks; +import com.android.settingslib.applications.ApplicationsState.Session; +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.testutils.shadow.ShadowUserManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowContextImpl; +import org.robolectric.shadows.ShadowLooper; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(shadows = {ShadowUserManager.class, + ApplicationsStateRoboTest.ShadowIconDrawableFactory.class, + ApplicationsStateRoboTest.ShadowPackageManager.class}) +public class ApplicationsStateRoboTest { + + private final static String HOME_PACKAGE_NAME = "com.android.home"; + private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable"; + + /** Class under test */ + private ApplicationsState mApplicationsState; + + @Mock + private Callbacks mCallbacks; + @Captor + private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor; + @Mock + private StorageStatsManager mStorageStatsManager; + + @Implements(value = IconDrawableFactory.class, inheritImplementationMethods = true) + public static class ShadowIconDrawableFactory { + + @Implementation + public Drawable getBadgedIcon(ApplicationInfo appInfo) { + return new ColorDrawable(0); + } + } + + @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true) + public static class ShadowPackageManager extends + org.robolectric.shadows.ShadowApplicationPackageManager { + + @Implementation + public ComponentName getHomeActivities(List<ResolveInfo> outActivities) { + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.packageName = HOME_PACKAGE_NAME; + resolveInfo.activityInfo.enabled = true; + outActivities.add(resolveInfo); + return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo"); + } + + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, + @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) { + List<ResolveInfo> resolveInfos = new ArrayList<>(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.packageName = LAUNCHABLE_PACKAGE_NAME; + resolveInfo.activityInfo.enabled = true; + resolveInfo.filter = new IntentFilter(); + resolveInfo.filter.addCategory(Intent.CATEGORY_LAUNCHER); + resolveInfos.add(resolveInfo); + return resolveInfos; + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Robolectric does not know about the StorageStatsManager as a system service. + // Registering a mock of this service as a replacement. + ShadowContextImpl shadowContext = Shadow.extract( + RuntimeEnvironment.application.getBaseContext()); + shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager); + StorageStats storageStats = new StorageStats(); + storageStats.codeBytes = 10; + storageStats.dataBytes = 20; + storageStats.cacheBytes = 30; + when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class), + anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats); + + mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application); + mApplicationsState.clearEntries(); + } + + private ApplicationInfo createApplicationInfo(String packageName) { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.sourceDir = "foo"; + appInfo.flags |= ApplicationInfo.FLAG_INSTALLED; + appInfo.storageUuid = UUID.randomUUID(); + appInfo.packageName = packageName; + return appInfo; + } + + private AppEntry createAppEntry(ApplicationInfo appInfo, int id) { + AppEntry appEntry = new AppEntry(RuntimeEnvironment.application, appInfo, id); + appEntry.label = "label"; + appEntry.mounted = true; + return appEntry; + } + + private void addApp(String packageName, int id) { + ApplicationInfo appInfo = createApplicationInfo(packageName); + AppEntry appEntry = createAppEntry(appInfo, id); + mApplicationsState.mAppEntries.add(appEntry); + mApplicationsState.mEntriesMap.get(0).put(appInfo.packageName, appEntry); + } + + private void processAllMessages() { + Handler mainHandler = mApplicationsState.mMainHandler; + Handler bkgHandler = mApplicationsState.mBackgroundHandler; + ShadowLooper shadowBkgLooper = extract(bkgHandler.getLooper()); + ShadowLooper shadowMainLooper = extract(mainHandler.getLooper()); + shadowBkgLooper.idle(); + shadowMainLooper.idle(); + } + + private AppEntry findAppEntry(List<AppEntry> appEntries, long id) { + for (AppEntry appEntry : appEntries) { + if (appEntry.id == id) { + return appEntry; + } + } + return null; + } + + @Test + public void testDefaultSessionLoadsAll() { + Session session = mApplicationsState.newSession(mCallbacks); + session.onResume(); + + addApp(HOME_PACKAGE_NAME,1); + addApp(LAUNCHABLE_PACKAGE_NAME,2); + session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + processAllMessages(); + verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); + + List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); + assertThat(appEntries.size()).isEqualTo(2); + + for (AppEntry appEntry : appEntries) { + assertThat(appEntry.size).isGreaterThan(0L); + assertThat(appEntry.icon).isNotNull(); + } + + AppEntry homeEntry = findAppEntry(appEntries, 1); + assertThat(homeEntry.isHomeApp).isTrue(); + assertThat(homeEntry.hasLauncherEntry).isFalse(); + + AppEntry launchableEntry = findAppEntry(appEntries, 2); + assertThat(launchableEntry.hasLauncherEntry).isTrue(); + assertThat(launchableEntry.launcherEntryEnabled).isTrue(); + session.onDestroy(); + } + + @Test + public void testCustomSessionLoadsIconsOnly() { + Session session = mApplicationsState.newSession(mCallbacks); + session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); + session.onResume(); + + addApp(LAUNCHABLE_PACKAGE_NAME,1); + session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + processAllMessages(); + verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); + + List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); + assertThat(appEntries.size()).isEqualTo(1); + + AppEntry launchableEntry = findAppEntry(appEntries, 1); + assertThat(launchableEntry.icon).isNotNull(); + assertThat(launchableEntry.size).isEqualTo(-1); + assertThat(launchableEntry.hasLauncherEntry).isFalse(); + session.onDestroy(); + } + + @Test + public void testCustomSessionLoadsSizesOnly() { + Session session = mApplicationsState.newSession(mCallbacks); + session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); + session.onResume(); + + addApp(LAUNCHABLE_PACKAGE_NAME,1); + session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + processAllMessages(); + verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); + + List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); + assertThat(appEntries.size()).isEqualTo(1); + + AppEntry launchableEntry = findAppEntry(appEntries, 1); + assertThat(launchableEntry.icon).isNull(); + assertThat(launchableEntry.hasLauncherEntry).isFalse(); + assertThat(launchableEntry.size).isGreaterThan(0L); + session.onDestroy(); + } + + @Test + public void testCustomSessionLoadsHomeOnly() { + Session session = mApplicationsState.newSession(mCallbacks); + session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); + session.onResume(); + + addApp(HOME_PACKAGE_NAME,1); + session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + processAllMessages(); + verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); + + List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); + assertThat(appEntries.size()).isEqualTo(1); + + AppEntry launchableEntry = findAppEntry(appEntries, 1); + assertThat(launchableEntry.icon).isNull(); + assertThat(launchableEntry.hasLauncherEntry).isFalse(); + assertThat(launchableEntry.size).isEqualTo(-1); + assertThat(launchableEntry.isHomeApp).isTrue(); + session.onDestroy(); + } + + @Test + public void testCustomSessionLoadsLeanbackOnly() { + Session session = mApplicationsState.newSession(mCallbacks); + session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); + session.onResume(); + + addApp(LAUNCHABLE_PACKAGE_NAME,1); + session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + processAllMessages(); + verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); + + List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); + assertThat(appEntries.size()).isEqualTo(1); + + AppEntry launchableEntry = findAppEntry(appEntries, 1); + assertThat(launchableEntry.icon).isNull(); + assertThat(launchableEntry.size).isEqualTo(-1); + assertThat(launchableEntry.isHomeApp).isFalse(); + assertThat(launchableEntry.hasLauncherEntry).isTrue(); + assertThat(launchableEntry.launcherEntryEnabled).isTrue(); + session.onDestroy(); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java index a3e1bc8418c7..bbd3a53751a0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java @@ -17,6 +17,7 @@ package com.android.settingslib.testutils.shadow; import android.content.Context; +import android.content.pm.UserInfo; import android.os.UserManager; import org.robolectric.RuntimeEnvironment; @@ -25,6 +26,9 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; +import java.util.ArrayList; +import java.util.List; + @Implements(value = UserManager.class, inheritImplementationMethods = true) public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { @@ -49,6 +53,20 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager return (UserManager) context.getSystemService(Context.USER_SERVICE); } + @Implementation + public int[] getProfileIdsWithDisabled(int userId) { + return new int[] { 0 }; + } + + @Implementation + public List<UserInfo> getProfiles() { + UserInfo userInfo = new UserInfo(); + userInfo.id = 0; + List<UserInfo> userInfos = new ArrayList<>(); + userInfos.add(userInfo); + return userInfos; + } + public static ShadowUserManager getShadow() { return (ShadowUserManager) Shadow.extract( RuntimeEnvironment.application.getSystemService(UserManager.class)); |