summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vadim Tryshev <vadimt@google.com> 2015-08-19 22:38:40 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2015-08-19 22:38:40 +0000
commit16ac7c3b1f2eeadab46d019681d99f298d2f345f (patch)
tree28f8722473086a5e830e260886048af02cdf4c8f
parent5afe8f2089a0c3be0e7e54ca609324edac6a1223 (diff)
parent1bec0c472b961e3fe6779f310b601211db9003a6 (diff)
Merge "Checking for unlauncheable apps."
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java175
3 files changed, 329 insertions, 54 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index d74c5b01307b..cb5c1257a831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -20,7 +20,6 @@ import android.animation.LayoutTransition;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.AppGlobals;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -29,13 +28,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
@@ -49,6 +45,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
+import com.android.internal.content.PackageMonitor;
import com.android.systemui.R;
import java.util.List;
@@ -75,6 +72,8 @@ class NavigationBarApps extends LinearLayout {
private final PackageManager mPackageManager;
private final UserManager mUserManager;
private final LayoutInflater mLayoutInflater;
+ private final AppPackageMonitor mAppPackageMonitor;
+
// This view has two roles:
// 1) If the drag started outside the pinned apps list, it is a placeholder icon with a null
@@ -106,6 +105,7 @@ class NavigationBarApps extends LinearLayout {
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mLayoutInflater = LayoutInflater.from(context);
+ mAppPackageMonitor = new AppPackageMonitor();
// Dragging an icon removes and adds back the dragged icon. Use the layout transitions to
// trigger animation. By default all transitions animate, so turn off the unneeded ones.
@@ -121,6 +121,76 @@ class NavigationBarApps extends LinearLayout {
setLayoutTransition(transition);
}
+ // Monitor that catches events like "app uninstalled".
+ private class AppPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ postRemoveIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
+ super.onPackageRemoved(packageName, uid);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ postRemoveIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
+ super.onPackageModified(packageName);
+ }
+
+ @Override
+ public void onPackagesAvailable(String[] packages) {
+ if (isReplacing()) {
+ UserHandle user = new UserHandle(getChangingUserId());
+
+ for (String packageName : packages) {
+ postRemoveIfUnlauncheable(packageName, user);
+ }
+ }
+ super.onPackagesAvailable(packages);
+ }
+
+ @Override
+ public void onPackagesUnavailable(String[] packages) {
+ if (!isReplacing()) {
+ UserHandle user = new UserHandle(getChangingUserId());
+
+ for (String packageName : packages) {
+ postRemoveIfUnlauncheable(packageName, user);
+ }
+ }
+ super.onPackagesUnavailable(packages);
+ }
+ }
+
+ private void postRemoveIfUnlauncheable(final String packageName, final UserHandle user) {
+ // This method doesn't necessarily get called in the main thread. Redirect the call into
+ // the main thread.
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (!isAttachedToWindow()) return;
+ removeIfUnlauncheable(packageName, user);
+ }
+ });
+ }
+
+ private void removeIfUnlauncheable(String packageName, UserHandle user) {
+ long appUserSerialNumber = mUserManager.getSerialNumberForUser(user);
+
+ // Remove icons for all apps that match a package that perhaps became unlauncheable.
+ for(int i = sAppsModel.getAppCount() - 1; i >= 0; --i) {
+ AppInfo appInfo = sAppsModel.getApp(i);
+ if (appInfo.getUserSerialNumber() != appUserSerialNumber) continue;
+
+ ComponentName appComponentName = appInfo.getComponentName();
+ if (!appComponentName.getPackageName().equals(packageName)) continue;
+
+ if (sAppsModel.buildAppLaunchIntent(appComponentName, user) != null) continue;
+
+ removeViewAt(i);
+ sAppsModel.removeApp(i);
+ sAppsModel.savePrefs();
+ }
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -145,12 +215,15 @@ class NavigationBarApps extends LinearLayout {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(mBroadcastReceiver, filter);
+
+ mAppPackageMonitor.register(mContext, null, UserHandle.ALL, true);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mContext.unregisterReceiver(mBroadcastReceiver);
+ mAppPackageMonitor.unregister();
}
/**
@@ -470,7 +543,6 @@ class NavigationBarApps extends LinearLayout {
ComponentName component = appInfo.getComponentName();
long appUserSerialNumber = appInfo.getUserSerialNumber();
-
UserHandle appUser = mUserManager.getUserForSerialNumber(appUserSerialNumber);
if (appUser == null) {
Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -478,7 +550,12 @@ class NavigationBarApps extends LinearLayout {
" because its user doesn't exist.");
return;
}
- int appUserId = appUser.getIdentifier();
+
+ Intent launchIntent = sAppsModel.buildAppLaunchIntent(component, appUser);
+ if (launchIntent == null) {
+ Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ return;
+ }
// Play a scale-up animation while launching the activity.
// TODO: Consider playing a different animation, or no animation, if the activity is
@@ -489,54 +566,9 @@ class NavigationBarApps extends LinearLayout {
ActivityOptions opts =
ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight());
Bundle optsBundle = opts.toBundle();
-
- // Launch the activity. This code is based on LauncherAppsService.startActivityAsUser code.
- Intent launchIntent = new Intent(Intent.ACTION_MAIN);
- launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
launchIntent.setSourceBounds(sourceBounds);
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- launchIntent.setPackage(component.getPackageName());
-
- IPackageManager pm = AppGlobals.getPackageManager();
- try {
- ActivityInfo info = pm.getActivityInfo(component, 0, appUserId);
- if (info == null) {
- Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Can't start activity " + component + " because it's not installed.");
- return;
- }
-
- if (!info.exported) {
- Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Can't start activity " + component + " because it doesn't have 'exported' attribute.");
- return;
- }
- } catch (RemoteException e) {
- Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Failed to get activity info for " + component, e);
- return;
- }
-
- // Check that the component actually has Intent.CATEGORY_LAUCNCHER
- // as calling startActivityAsUser ignores the category and just
- // resolves based on the component if present.
- List<ResolveInfo> apps = getContext().getPackageManager().queryIntentActivitiesAsUser(launchIntent,
- 0 /* flags */, appUserId);
- final int size = apps.size();
- for (int i = 0; i < size; ++i) {
- ActivityInfo activityInfo = apps.get(i).activityInfo;
- if (activityInfo.packageName.equals(component.getPackageName()) &&
- activityInfo.name.equals(component.getClassName())) {
- // Found an activity with category launcher that matches
- // this component so ok to launch.
- launchIntent.setComponent(component);
- mContext.startActivityAsUser(launchIntent, optsBundle, appUser);
- return;
- }
- }
- Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Attempt to launch activity without category Intent.CATEGORY_LAUNCHER " + component);
+ mContext.startActivityAsUser(launchIntent, optsBundle, appUser);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
index b8764cf40efb..c4c31fd6bf80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
@@ -16,16 +16,23 @@
package com.android.systemui.statusbar.phone;
+import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -94,6 +101,58 @@ class NavigationBarAppsModel {
}
}
+ @VisibleForTesting
+ protected IPackageManager getPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
+ // Returns a launch intent for a given component, or null if the component is unlauncheable.
+ public Intent buildAppLaunchIntent(ComponentName component, UserHandle appUser) {
+ int appUserId = appUser.getIdentifier();
+
+ // This code is based on LauncherAppsService.startActivityAsUser code.
+ Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+ launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ launchIntent.setPackage(component.getPackageName());
+
+ try {
+ ActivityInfo info = getPackageManager().getActivityInfo(component, 0, appUserId);
+ if (info == null) {
+ Log.e(TAG, "Activity " + component + " is not installed.");
+ return null;
+ }
+
+ if (!info.exported) {
+ Log.e(TAG, "Activity " + component + " doesn't have 'exported' attribute.");
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get activity info for " + component, e);
+ return null;
+ }
+
+ // Check that the component actually has Intent.CATEGORY_LAUNCHER
+ // as calling startActivityAsUser ignores the category and just
+ // resolves based on the component if present.
+ List<ResolveInfo> apps = mContext.getPackageManager().queryIntentActivitiesAsUser(launchIntent,
+ 0 /* flags */, appUserId);
+ final int size = apps.size();
+ for (int i = 0; i < size; ++i) {
+ ActivityInfo activityInfo = apps.get(i).activityInfo;
+ if (activityInfo.packageName.equals(component.getPackageName()) &&
+ activityInfo.name.equals(component.getClassName())) {
+ // Found an activity with category launcher that matches
+ // this component so ok to launch.
+ launchIntent.setComponent(component);
+ return launchIntent;
+ }
+ }
+
+ Log.e(TAG, "Activity doesn't have category Intent.CATEGORY_LAUNCHER " + component);
+ return null;
+ }
+
/**
* Reinitializes the model for a new user.
*/
@@ -199,6 +258,10 @@ class NavigationBarAppsModel {
/** Loads the list of apps from SharedPreferences. */
private void loadAppsFromPrefs() {
+ UserManager mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ boolean hadUnlauncheableApps = false;
+
int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1);
for (int i = 0; i < appCount; i++) {
String prefValue = mPrefs.getString(prefNameForApp(i), null);
@@ -214,8 +277,15 @@ class NavigationBarAppsModel {
// Couldn't find the saved state. Just skip this item.
continue;
}
- mApps.add(new AppInfo(componentName, userSerialNumber));
+ UserHandle appUser = mUserManager.getUserForSerialNumber(userSerialNumber);
+ if (appUser != null && buildAppLaunchIntent(componentName, appUser) != null) {
+ mApps.add(new AppInfo(componentName, userSerialNumber));
+ } else {
+ hadUnlauncheableApps = true;
+ }
}
+
+ if (hadUnlauncheableApps) savePrefs();
}
/** Adds the first few apps from the owner profile. Used for demo purposes. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
index 62213ab51703..4d0e28bb6d98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
@@ -16,21 +16,26 @@
package com.android.systemui.statusbar.phone;
+import org.mockito.InOrder;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
@@ -45,6 +50,7 @@ import java.util.Map;
/** Tests for the data model for the navigation bar app icons. */
public class NavigationBarAppsModelTest extends AndroidTestCase {
private PackageManager mMockPackageManager;
+ private IPackageManager mMockIPackageManager;
private SharedPreferences mMockPrefs;
private SharedPreferences.Editor mMockEdit;
private UserManager mMockUserManager;
@@ -61,6 +67,7 @@ public class NavigationBarAppsModelTest extends AndroidTestCase {
final Context context = mock(Context.class);
mMockPackageManager = mock(PackageManager.class);
+ mMockIPackageManager = mock(IPackageManager.class);
mMockPrefs = mock(SharedPreferences.class);
mMockEdit = mock(SharedPreferences.Editor.class);
mMockUserManager = mock(UserManager.class);
@@ -78,8 +85,71 @@ public class NavigationBarAppsModelTest extends AndroidTestCase {
when(mMockPrefs.edit()).thenReturn(mMockEdit);
when(mMockUserManager.getSerialNumberForUser(new UserHandle(2))).thenReturn(22L);
+ when(mMockUserManager.getUserForSerialNumber(45L)).thenReturn(new UserHandle(4));
+ when(mMockUserManager.getUserForSerialNumber(239L)).thenReturn(new UserHandle(5));
+
+ mModel = new NavigationBarAppsModel(context) {
+ @Override
+ protected IPackageManager getPackageManager() {
+ return mMockIPackageManager;
+ }
+ };
+ }
+
+ /** Tests buildAppLaunchIntent(). */
+ public void testBuildAppLaunchIntent() {
+ ActivityInfo mockNonExportedActivityInfo = new ActivityInfo();
+ mockNonExportedActivityInfo.exported = false;
+ ActivityInfo mockExportedActivityInfo = new ActivityInfo();
+ mockExportedActivityInfo.exported = true;
+ try {
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package1", "class1"), 0, 4)).
+ thenReturn(mockNonExportedActivityInfo);
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package2", "class2"), 0, 5)).
+ thenThrow(new RemoteException());
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package3", "class3"), 0, 6)).
+ thenReturn(mockExportedActivityInfo);
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package4", "class4"), 0, 7)).
+ thenReturn(mockExportedActivityInfo);
+ } catch (RemoteException e) {
+ fail("RemoteException can't happen in the test, but it happened.");
+ }
- mModel = new NavigationBarAppsModel(context);
+ // Assume some installed activities.
+ ActivityInfo ai0 = new ActivityInfo();
+ ai0.packageName = "package0";
+ ai0.name = "class0";
+ ActivityInfo ai1 = new ActivityInfo();
+ ai1.packageName = "package4";
+ ai1.name = "class4";
+ ResolveInfo ri0 = new ResolveInfo();
+ ri0.activityInfo = ai0;
+ ResolveInfo ri1 = new ResolveInfo();
+ ri1.activityInfo = ai1;
+ when(mMockPackageManager
+ .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+ .thenReturn(Arrays.asList(ri0, ri1));
+
+ // Unlauncheable (for various reasons) apps.
+ assertEquals(null, mModel.buildAppLaunchIntent(
+ new ComponentName("package0", "class0"), new UserHandle(3)));
+ assertEquals(null, mModel.buildAppLaunchIntent(
+ new ComponentName("package1", "class1"), new UserHandle(4)));
+ assertEquals(null, mModel.buildAppLaunchIntent(
+ new ComponentName("package2", "class2"), new UserHandle(5)));
+ assertEquals(null, mModel.buildAppLaunchIntent(
+ new ComponentName("package3", "class3"), new UserHandle(6)));
+
+ // A launcheable app.
+ Intent intent = mModel.buildAppLaunchIntent(
+ new ComponentName("package4", "class4"), new UserHandle(7));
+ assertNotNull(intent);
+ assertEquals(new ComponentName("package4", "class4"), intent.getComponent());
+ assertEquals("package4", intent.getPackage());
}
/** Initializes the model from SharedPreferences for a few app activites. */
@@ -93,6 +163,39 @@ public class NavigationBarAppsModelTest extends AndroidTestCase {
when(mMockPrefs.getString("22|app_2", null)).thenReturn("package2/class2");
when(mMockPrefs.getLong("22|app_user_2", -1)).thenReturn(239L);
+ ActivityInfo mockActivityInfo = new ActivityInfo();
+ mockActivityInfo.exported = true;
+ try {
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package0", "class0"), 0, 5)).thenReturn(mockActivityInfo);
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package1", "class1"), 0, 4)).thenReturn(mockActivityInfo);
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package2", "class2"), 0, 5)).thenReturn(mockActivityInfo);
+ } catch (RemoteException e) {
+ fail("RemoteException can't happen in the test, but it happened.");
+ }
+
+ // Assume some installed activities.
+ ActivityInfo ai0 = new ActivityInfo();
+ ai0.packageName = "package0";
+ ai0.name = "class0";
+ ActivityInfo ai1 = new ActivityInfo();
+ ai1.packageName = "package1";
+ ai1.name = "class1";
+ ActivityInfo ai2 = new ActivityInfo();
+ ai2.packageName = "package2";
+ ai2.name = "class2";
+ ResolveInfo ri0 = new ResolveInfo();
+ ri0.activityInfo = ai0;
+ ResolveInfo ri1 = new ResolveInfo();
+ ri1.activityInfo = ai1;
+ ResolveInfo ri2 = new ResolveInfo();
+ ri2.activityInfo = ai2;
+ when(mMockPackageManager
+ .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+ .thenReturn(Arrays.asList(ri0, ri1, ri2));
+
mModel.setCurrentUser(2);
}
@@ -133,6 +236,15 @@ public class NavigationBarAppsModelTest extends AndroidTestCase {
assertEquals(22L, mModel.getApp(0).getUserSerialNumber());
assertEquals("package2/class2", mModel.getApp(1).getComponentName().flattenToString());
assertEquals(22L, mModel.getApp(1).getUserSerialNumber());
+ InOrder order = inOrder(mMockEdit);
+ order.verify(mMockEdit).apply();
+ order.verify(mMockEdit).putInt("22|app_count", 2);
+ order.verify(mMockEdit).putString("22|app_0", "package1/class1");
+ order.verify(mMockEdit).putLong("22|app_user_0", 22L);
+ order.verify(mMockEdit).putString("22|app_1", "package2/class2");
+ order.verify(mMockEdit).putLong("22|app_user_1", 22L);
+ order.verify(mMockEdit).apply();
+ verifyNoMoreInteractions(mMockEdit);
}
/** Tests initializing the model if one of the prefs is missing. */
@@ -145,11 +257,72 @@ public class NavigationBarAppsModelTest extends AndroidTestCase {
// But assume one pref is missing.
when(mMockPrefs.getString("22|app_1", null)).thenReturn(null);
+ ActivityInfo mockActivityInfo = new ActivityInfo();
+ mockActivityInfo.exported = true;
+ try {
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package0", "class0"), 0, 5)).thenReturn(mockActivityInfo);
+ } catch (RemoteException e) {
+ fail("RemoteException can't happen in the test, but it happened.");
+ }
+
+ ActivityInfo ai0 = new ActivityInfo();
+ ai0.packageName = "package0";
+ ai0.name = "class0";
+ ResolveInfo ri0 = new ResolveInfo();
+ ri0.activityInfo = ai0;
+ when(mMockPackageManager
+ .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+ .thenReturn(Arrays.asList(ri0));
+
// Initializing the model should load from prefs and skip the missing one.
mModel.setCurrentUser(2);
assertEquals(1, mModel.getAppCount());
assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString());
assertEquals(239L, mModel.getApp(0).getUserSerialNumber());
+ verifyNoMoreInteractions(mMockEdit);
+ }
+
+ /** Tests initializing the model if one of the apps is unlauncheable. */
+ public void testInitializeWithUnlauncheableApp() {
+ // Assume two apps are nominally stored.
+ when(mMockPrefs.getInt("22|app_count", -1)).thenReturn(2);
+ when(mMockPrefs.getString("22|app_0", null)).thenReturn("package0/class0");
+ when(mMockPrefs.getLong("22|app_user_0", -1)).thenReturn(239L);
+ when(mMockPrefs.getString("22|app_1", null)).thenReturn("package1/class1");
+ when(mMockPrefs.getLong("22|app_user_1", -1)).thenReturn(45L);
+
+ ActivityInfo mockActivityInfo = new ActivityInfo();
+ mockActivityInfo.exported = true;
+ try {
+ when(mMockIPackageManager.getActivityInfo(
+ new ComponentName("package0", "class0"), 0, 5)).thenReturn(mockActivityInfo);
+ } catch (RemoteException e) {
+ fail("RemoteException can't happen in the test, but it happened.");
+ }
+
+ ActivityInfo ai0 = new ActivityInfo();
+ ai0.packageName = "package0";
+ ai0.name = "class0";
+ ResolveInfo ri0 = new ResolveInfo();
+ ri0.activityInfo = ai0;
+ when(mMockPackageManager
+ .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+ .thenReturn(Arrays.asList(ri0));
+
+ // Initializing the model should load from prefs and skip the unlauncheable one.
+ mModel.setCurrentUser(2);
+ assertEquals(1, mModel.getAppCount());
+ assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString());
+ assertEquals(239L, mModel.getApp(0).getUserSerialNumber());
+
+ // Once an unlauncheable app is detected, the model should save all apps excluding the
+ // unlauncheable one.
+ verify(mMockEdit).putInt("22|app_count", 1);
+ verify(mMockEdit).putString("22|app_0", "package0/class0");
+ verify(mMockEdit).putLong("22|app_user_0", 239L);
+ verify(mMockEdit).apply();
+ verifyNoMoreInteractions(mMockEdit);
}
/** Tests saving the model to SharedPreferences. */