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 71ee4e42f2e4b..41d30c72990d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java @@ -21,11 +21,13 @@ 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; import android.content.ComponentName; 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; @@ -71,6 +73,7 @@ class NavigationBarApps extends LinearLayout { private static NavigationBarAppsModel sAppsModel; private final PackageManager mPackageManager; + private final UserManager mUserManager; private final LayoutInflater mLayoutInflater; // This view has two roles: @@ -82,13 +85,26 @@ class NavigationBarApps extends LinearLayout { // When the user is not dragging this member is null. private View mDragView; + private long mCurrentUserSerialNumber = -1; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + int currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + onUserSwitched(currentUserId); + } + } + }; + public NavigationBarApps(Context context, AttributeSet attrs) { super(context, attrs); if (sAppsModel == null) { sAppsModel = new NavigationBarAppsModel(context); - sAppsModel.initialize(); // Load the saved icons, if any. } mPackageManager = context.getPackageManager(); + mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mLayoutInflater = LayoutInflater.from(context); // Dragging an icon removes and adds back the dragged icon. Use the layout transitions to @@ -108,17 +124,32 @@ class NavigationBarApps extends LinearLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - // When an icon is dragged out of the pinned area this view's width changes, which causes - // the parent container's layout to change and the divider and recents icons to shift left. - // Animate the parent's CHANGING transition. - ViewGroup parent = (ViewGroup) getParent(); - LayoutTransition transition = new LayoutTransition(); - transition.disableTransitionType(LayoutTransition.APPEARING); - transition.disableTransitionType(LayoutTransition.DISAPPEARING); - transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); - transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); - transition.enableTransitionType(LayoutTransition.CHANGING); - parent.setLayoutTransition(transition); + // When an icon is dragged out of the pinned area this view's width changes, which causes + // the parent container's layout to change and the divider and recents icons to shift left. + // Animate the parent's CHANGING transition. + ViewGroup parent = (ViewGroup) getParent(); + LayoutTransition transition = new LayoutTransition(); + transition.disableTransitionType(LayoutTransition.APPEARING); + transition.disableTransitionType(LayoutTransition.DISAPPEARING); + transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); + transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + transition.enableTransitionType(LayoutTransition.CHANGING); + parent.setLayoutTransition(transition); + + mCurrentUserSerialNumber = mUserManager.getSerialNumberForUser( + new UserHandle(ActivityManager.getCurrentUser())); + sAppsModel.setCurrentUser(mCurrentUserSerialNumber); + recreateAppButtons(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiver(mBroadcastReceiver, filter); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContext.unregisterReceiver(mBroadcastReceiver); } /** @@ -433,14 +464,11 @@ class NavigationBarApps extends LinearLayout { AppInfo appInfo = sAppsModel.getApp(indexOfChild(v)); ComponentName component = appInfo.getComponentName(); - UserManager userManager = - (UserManager) getContext().getSystemService(Context.USER_SERVICE); - long appUserSerialNumber = appInfo.getUserSerialNumber(); UserHandle appUser = null; if (appUserSerialNumber != AppInfo.USER_UNSPECIFIED) { - appUser = userManager.getUserForSerialNumber(appUserSerialNumber); + appUser = mUserManager.getUserForSerialNumber(appUserSerialNumber); } int appUserId; @@ -510,4 +538,15 @@ class NavigationBarApps extends LinearLayout { Log.e(TAG, "Attempt to launch activity without category Intent.CATEGORY_LAUNCHER " + component); } } + + private void onUserSwitched(int currentUserId) { + final long newUserSerialNumber = + mUserManager.getSerialNumberForUser(new UserHandle(currentUserId)); + + if (newUserSerialNumber != mCurrentUserSerialNumber) { + mCurrentUserSerialNumber = newUserSerialNumber; + sAppsModel.setCurrentUser(newUserSerialNumber); + recreateAppButtons(); + } + } } 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 9f80d33aed889..39f1304094c5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java @@ -19,16 +19,20 @@ package com.android.systemui.statusbar.phone; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.LauncherActivityInfo; -import android.content.pm.LauncherApps; -import android.os.UserHandle; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.os.UserManager; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Data model and controller for app icons appearing in the navigation bar. The data is stored on @@ -48,7 +52,7 @@ class NavigationBarAppsModel { private final static String VERSION_PREF = "version"; // Current version number for preferences. - private final static int CURRENT_VERSION = 1; + private final static int CURRENT_VERSION = 2; // Preference name for the number of app icons. private final static String APP_COUNT_PREF = "app_count"; @@ -59,42 +63,99 @@ class NavigationBarAppsModel { // User serial number prefix for each app's info. The actual pref has an integer appended to it. private final static String APP_USER_PREFIX = "app_user_"; - private final LauncherApps mLauncherApps; + // Character separating current user serial number from the user-specific part of a pref. + // Example "22|app_user_2" - when logged as user with serial 22, we'll use this pref for the + // user serial of the third app of the logged-in user. + private final static char USER_SEPARATOR = '|'; + + final Context mContext; private final SharedPreferences mPrefs; // Apps are represented as an ordered list of app infos. private final List mApps = new ArrayList(); - public NavigationBarAppsModel(Context context) { - mLauncherApps = (LauncherApps) context.getSystemService("launcherapps"); - mPrefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - } + // Serial number of the current user. + private long mCurrentUserSerialNumber = AppInfo.USER_UNSPECIFIED; - @VisibleForTesting - NavigationBarAppsModel(LauncherApps launcherApps, SharedPreferences prefs) { - mLauncherApps = launcherApps; - mPrefs = prefs; + public NavigationBarAppsModel(Context context) { + mContext = context; + mPrefs = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + + int version = mPrefs.getInt(VERSION_PREF, -1); + if (version != CURRENT_VERSION) { + // Since the data format changed, clean everything. + SharedPreferences.Editor edit = mPrefs.edit(); + edit.clear(); + edit.putInt(VERSION_PREF, CURRENT_VERSION); + edit.apply(); + } } /** - * Initializes the model with a list of apps, either by loading it off disk or by supplying - * a default list. + * Reinitializes the model for a new user. */ - public void initialize() { - if (mApps.size() > 0) { - Slog.e(TAG, "Model already initialized"); - return; - } + public void setCurrentUser(long userSerialNumber) { + mCurrentUserSerialNumber = userSerialNumber; - // Check for an existing list of apps. - int version = mPrefs.getInt(VERSION_PREF, -1); - if (version == CURRENT_VERSION) { + mApps.clear(); + + int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1); + if (appCount >= 0) { loadAppsFromPrefs(); } else { + // We switched to this user for the first time ever. This is a good opportunity to clean + // prefs for users deleted in the past. + removePrefsForDeletedUsers(); + addDefaultApps(); } } + /** + * Removes prefs for users that don't exist on the device. + */ + private void removePrefsForDeletedUsers() { + UserManager userManager = + (UserManager) mContext.getSystemService(Context.USER_SERVICE); + + // Build a set of string representations of serial numbers of the device users. + final List users = userManager.getUsers(); + final int userCount = users.size(); + + final Set userSerials = new HashSet (); + + for (int i = 0; i < userCount; ++i) { + userSerials.add(Long.toString(users.get(i).serialNumber)); + } + + // Walk though all prefs and delete ones which user is not in the string set. + final Map allPrefs = mPrefs.getAll(); + final SharedPreferences.Editor edit = mPrefs.edit(); + + for (Map.Entry pref : allPrefs.entrySet()) { + final String key = pref.getKey(); + if (key.equals(VERSION_PREF)) continue; + + final int userSeparatorPos = key.indexOf(USER_SEPARATOR); + + if (userSeparatorPos < 0) { + // Removing anomalous pref with no user. + edit.remove(key); + continue; + } + + final String prefUserSerial = key.substring(0, userSeparatorPos); + + if (!userSerials.contains(prefUserSerial)) { + // Removes pref for a not existing user. + edit.remove(key); + continue; + } + } + + edit.apply(); + } + /** Returns the number of apps. */ public int getAppCount() { return mApps.size(); @@ -123,11 +184,8 @@ class NavigationBarAppsModel { /** Saves the current model to disk. */ public void savePrefs() { SharedPreferences.Editor edit = mPrefs.edit(); - // The user might have removed icons, so clear all the old prefs. - edit.clear(); - edit.putInt(VERSION_PREF, CURRENT_VERSION); int appCount = mApps.size(); - edit.putInt(APP_COUNT_PREF, appCount); + edit.putInt(userPrefixed(APP_COUNT_PREF), appCount); for (int i = 0; i < appCount; i++) { final AppInfo appInfo = mApps.get(i); String componentNameString = appInfo.getComponentName().flattenToString(); @@ -140,7 +198,7 @@ class NavigationBarAppsModel { /** Loads the list of apps from SharedPreferences. */ private void loadAppsFromPrefs() { - int appCount = mPrefs.getInt(APP_COUNT_PREF, -1); + int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1); for (int i = 0; i < appCount; i++) { String prefValue = mPrefs.getString(prefNameForApp(i), null); if (prefValue == null) { @@ -154,27 +212,48 @@ class NavigationBarAppsModel { } } + @VisibleForTesting + protected int getCurrentUser() { + return ActivityManager.getCurrentUser(); + } + /** Adds the first few apps from the owner profile. Used for demo purposes. */ private void addDefaultApps() { // Get a list of all app activities. - List apps = - mLauncherApps.getActivityList( - null /* packageName */, new UserHandle(ActivityManager.getCurrentUser())); - int appCount = apps.size(); + final Intent queryIntent = new Intent(Intent.ACTION_MAIN, null); + queryIntent.addCategory(Intent.CATEGORY_LAUNCHER); + final int currentUser = getCurrentUser(); + + final List apps = mContext.getPackageManager().queryIntentActivitiesAsUser( + queryIntent, 0 /* flags */, currentUser); + final int appCount = apps.size(); for (int i = 0; i < NUM_INITIAL_APPS && i < appCount; i++) { - LauncherActivityInfo activityInfo = apps.get(i); - mApps.add(new AppInfo(activityInfo.getComponentName(), AppInfo.USER_UNSPECIFIED)); + ResolveInfo ri = apps.get(i); + ComponentName componentName = new ComponentName( + ri.activityInfo.packageName, ri.activityInfo.name); + mApps.add(new AppInfo(componentName, AppInfo.USER_UNSPECIFIED)); } + + savePrefs(); + } + + /** Returns a pref prefixed with the serial number of the current user. */ + private String userPrefixed(String pref) { + if (mCurrentUserSerialNumber == AppInfo.USER_UNSPECIFIED) { + throw new RuntimeException("Current user is not yet set"); + } + + return Long.toString(mCurrentUserSerialNumber) + USER_SEPARATOR + pref; } /** Returns the pref name for the app at a given index. */ - private static String prefNameForApp(int index) { - return APP_PREF_PREFIX + Integer.toString(index); + private String prefNameForApp(int index) { + return userPrefixed(APP_PREF_PREFIX + Integer.toString(index)); } /** Returns the pref name for the app's user at a given index. */ - private static String prefUserForApp(int index) { - return APP_USER_PREFIX + Integer.toString(index); + private String prefUserForApp(int index) { + return userPrefixed(APP_USER_PREFIX + Integer.toString(index)); } } 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 f46e76c701754..31007ee509d9b 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 @@ -18,24 +18,35 @@ package com.android.systemui.statusbar.phone; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; 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.LauncherActivityInfo; -import android.content.pm.LauncherApps; -import android.os.UserHandle; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.os.UserManager; import android.test.AndroidTestCase; +import java.util.Arrays; import java.util.ArrayList; import java.util.List; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** Tests for the data model for the navigation bar app icons. */ public class NavigationBarAppsModelTest extends AndroidTestCase { - private LauncherApps mMockLauncherApps; + private PackageManager mMockPackageManager; private SharedPreferences mMockPrefs; + private SharedPreferences.Editor mMockEdit; + private UserManager mMockUserManager; private NavigationBarAppsModel mModel; @@ -47,26 +58,44 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath()); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - mMockLauncherApps = mock(LauncherApps.class); + final Context context = mock(Context.class); + mMockPackageManager = mock(PackageManager.class); mMockPrefs = mock(SharedPreferences.class); - mModel = new NavigationBarAppsModel(mMockLauncherApps, mMockPrefs); + mMockEdit = mock(SharedPreferences.Editor.class); + mMockUserManager = mock(UserManager.class); + + when (context.getSharedPreferences( + "com.android.systemui.navbarapps", Context.MODE_PRIVATE)).thenReturn(mMockPrefs); + when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); + when(context.getPackageManager()).thenReturn(mMockPackageManager); + + setContext(context); + + when(mMockUserManager.getUsers()).thenReturn(new ArrayList()); + // Assume the version pref is present and equal to the current version. + when(mMockPrefs.getInt("version", -1)).thenReturn(2); + when(mMockPrefs.edit()).thenReturn(mMockEdit); + + mModel = new NavigationBarAppsModel(context) { + @Override + protected int getCurrentUser() { + return 0; + } + }; } /** Initializes the model from SharedPreferences for a few app activites. */ private void initializeModelFromPrefs() { - // Assume the version pref is present. - when(mMockPrefs.getInt("version", -1)).thenReturn(1); - // Assume several apps are stored. - when(mMockPrefs.getInt("app_count", -1)).thenReturn(3); - when(mMockPrefs.getString("app_0", null)).thenReturn("package0/class0"); - when(mMockPrefs.getLong("app_user_0", -1)).thenReturn(-1L); - when(mMockPrefs.getString("app_1", null)).thenReturn("package1/class1"); - when(mMockPrefs.getLong("app_user_1", -1)).thenReturn(45L); - when(mMockPrefs.getString("app_2", null)).thenReturn("package2/class2"); - when(mMockPrefs.getLong("app_user_2", -1)).thenReturn(239L); + when(mMockPrefs.getInt("22|app_count", -1)).thenReturn(3); + when(mMockPrefs.getString("22|app_0", null)).thenReturn("package0/class0"); + when(mMockPrefs.getLong("22|app_user_0", -1)).thenReturn(-1L); + when(mMockPrefs.getString("22|app_1", null)).thenReturn("package1/class1"); + when(mMockPrefs.getLong("22|app_user_1", -1)).thenReturn(45L); + when(mMockPrefs.getString("22|app_2", null)).thenReturn("package2/class2"); + when(mMockPrefs.getLong("22|app_user_2", -1)).thenReturn(239L); - mModel.initialize(); + mModel.setCurrentUser(22L); } /** Tests initializing the model from SharedPreferences. */ @@ -83,22 +112,26 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { /** Tests initializing the model when the SharedPreferences aren't available. */ public void testInitializeDefaultApps() { - // Assume the version pref isn't available. - when(mMockPrefs.getInt("version", -1)).thenReturn(-1); + // Assume the user's app count pref isn't available. + when(mMockPrefs.getInt("0|app_count", -1)).thenReturn(-1); // Assume some installed activities. - LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class); - when(activity1.getComponentName()).thenReturn(new ComponentName("package1", "class1")); - LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class); - when(activity2.getComponentName()).thenReturn(new ComponentName("package2", "class2")); - List apps = new ArrayList(); - apps.add(activity1); - apps.add(activity2); - when(mMockLauncherApps.getActivityList(anyString(), any(UserHandle.class))) - .thenReturn(apps); + ActivityInfo ai1 = new ActivityInfo(); + ai1.packageName = "package1"; + ai1.name = "class1"; + ActivityInfo ai2 = new ActivityInfo(); + ai2.packageName = "package2"; + ai2.name = "class2"; + ResolveInfo ri1 = new ResolveInfo(); + ri1.activityInfo = ai1; + ResolveInfo ri2 = new ResolveInfo(); + ri2.activityInfo = ai2; + when(mMockPackageManager + .queryIntentActivitiesAsUser(any(Intent.class), eq(0), eq(0))) + .thenReturn(Arrays.asList(ri1, ri2)); - // Initializing the model should load the installed activities. - mModel.initialize(); + // Setting the user should load the installed activities. + mModel.setCurrentUser(0L); assertEquals(2, mModel.getAppCount()); assertEquals("package1/class1", mModel.getApp(0).getComponentName().flattenToString()); assertEquals(-1L, mModel.getApp(0).getUserSerialNumber()); @@ -108,19 +141,16 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { /** Tests initializing the model if one of the prefs is missing. */ public void testInitializeWithMissingPref() { - // Assume the version pref is present. - when(mMockPrefs.getInt("version", -1)).thenReturn(1); - // Assume two apps are nominally stored. - when(mMockPrefs.getInt("app_count", -1)).thenReturn(2); - when(mMockPrefs.getString("app_0", null)).thenReturn("package0/class0"); - when(mMockPrefs.getLong("app_user_0", -1)).thenReturn(239L); + 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); // But assume one pref is missing. - when(mMockPrefs.getString("app_1", null)).thenReturn(null); + when(mMockPrefs.getString("22|app_1", null)).thenReturn(null); // Initializing the model should load from prefs and skip the missing one. - mModel.initialize(); + mModel.setCurrentUser(22L); assertEquals(1, mModel.getAppCount()); assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString()); assertEquals(239L, mModel.getApp(0).getUserSerialNumber()); @@ -130,15 +160,64 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { public void testSavePrefs() { initializeModelFromPrefs(); - SharedPreferences.Editor mockEdit = mock(SharedPreferences.Editor.class); - when(mMockPrefs.edit()).thenReturn(mockEdit); - mModel.savePrefs(); - verify(mockEdit).clear(); // Old prefs were removed. - verify(mockEdit).putInt("version", 1); - verify(mockEdit).putInt("app_count", 3); - verify(mockEdit).putString("app_0", "package0/class0"); - verify(mockEdit).putString("app_1", "package1/class1"); - verify(mockEdit).putString("app_2", "package2/class2"); + verify(mMockEdit).putInt("22|app_count", 3); + verify(mMockEdit).putString("22|app_0", "package0/class0"); + verify(mMockEdit).putLong("22|app_user_0", -1L); + verify(mMockEdit).putString("22|app_1", "package1/class1"); + verify(mMockEdit).putLong("22|app_user_1", 45L); + verify(mMockEdit).putString("22|app_2", "package2/class2"); + verify(mMockEdit).putLong("22|app_user_2", 239L); + verify(mMockEdit).apply(); + verifyNoMoreInteractions(mMockEdit); + } + + /** Tests cleaning all prefs on a version change. */ + public void testVersionChange() { + // Assume the version pref changed. + when(mMockPrefs.getInt("version", -1)).thenReturn(1); + + new NavigationBarAppsModel(getContext()); + verify(mMockEdit).clear(); + verify(mMockEdit).putInt("version", 2); + verify(mMockEdit).apply(); + verifyNoMoreInteractions(mMockEdit); + } + + /** Tests cleaning prefs for deleted users. */ + public void testCleaningDeletedUsers() { + // Users on the device. + final UserInfo user1 = new UserInfo(11, "", 0); + user1.serialNumber = 1111; + final UserInfo user2 = new UserInfo(13, "", 0); + user2.serialNumber = 1313; + + when(mMockUserManager.getUsers()).thenReturn(Arrays.asList(user1, user2)); + + when(mMockPrefs.edit()). + thenReturn(mMockEdit). + thenReturn(mock(SharedPreferences.Editor.class)); + + // Assume the user's app count pref isn't available. This will trigger clearing deleted + // users' prefs. + when(mMockPrefs.getInt("0|app_count", -1)).thenReturn(-1); + + final Map allPrefs = new HashMap(); + allPrefs.put("version", null); + allPrefs.put("some_strange_pref", null); + allPrefs.put("", null); + allPrefs.put("|", null); + allPrefs.put("1313|app_count", null); + allPrefs.put("1212|app_count", null); + when(mMockPrefs.getAll()).thenReturn(allPrefs); + + // Setting the user should remove prefs for deleted users. + mModel.setCurrentUser(0L); + verify(mMockEdit).remove("some_strange_pref"); + verify(mMockEdit).remove(""); + verify(mMockEdit).remove("|"); + verify(mMockEdit).remove("1212|app_count"); + verify(mMockEdit).apply(); + verifyNoMoreInteractions(mMockEdit); } }