DO NOT MERGE sysui: remove shelf
Bug: 26742568 Change-Id: I36bd013c4adb62b76b6f2789aa490c1484a535d7
This commit is contained in:
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.systemui.statusbar.phone;
|
||||
|
||||
import android.app.AppGlobals;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Retrieves the icon for an activity and sets it as the Drawable on an ImageView. The ImageView
|
||||
* is hidden if the activity isn't recognized or if there is no icon.
|
||||
*/
|
||||
class GetActivityIconTask extends AsyncTask<AppButtonData, Void, Drawable> {
|
||||
private final static String TAG = "GetActivityIconTask";
|
||||
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
// The ImageView that will receive the icon.
|
||||
private final ImageView mImageView;
|
||||
|
||||
public GetActivityIconTask(PackageManager packageManager, ImageView imageView) {
|
||||
mPackageManager = packageManager;
|
||||
mImageView = imageView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Drawable doInBackground(AppButtonData... params) {
|
||||
if (params.length != 1) {
|
||||
throw new IllegalArgumentException("Expected one parameter");
|
||||
}
|
||||
AppButtonData buttonData = params[0];
|
||||
AppInfo appInfo = buttonData.appInfo;
|
||||
try {
|
||||
IPackageManager mPM = AppGlobals.getPackageManager();
|
||||
ActivityInfo ai = mPM.getActivityInfo(
|
||||
appInfo.getComponentName(),
|
||||
0,
|
||||
appInfo.getUser().getIdentifier());
|
||||
|
||||
if (ai == null) {
|
||||
Slog.w(TAG, "Icon not found for " + appInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
Drawable unbadgedIcon = ai.loadIcon(mPackageManager);
|
||||
Drawable badgedIcon =
|
||||
mPackageManager.getUserBadgedIcon(unbadgedIcon, appInfo.getUser());
|
||||
|
||||
if (NavigationBarApps.DEBUG) {
|
||||
// Draw pinned indicator and number of running tasks.
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
badgedIcon.getIntrinsicWidth(),
|
||||
badgedIcon.getIntrinsicHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
badgedIcon.setBounds(
|
||||
0, 0, badgedIcon.getIntrinsicWidth(), badgedIcon.getIntrinsicHeight());
|
||||
badgedIcon.draw(canvas);
|
||||
Paint paint = new Paint();
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
if (buttonData.pinned) {
|
||||
paint.setColor(Color.WHITE);
|
||||
canvas.drawCircle(10, 10, 10, paint);
|
||||
}
|
||||
if (buttonData.tasks != null && buttonData.tasks.size() > 0) {
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawCircle(60, 30, 30, paint);
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setTextSize(50);
|
||||
paint.setTypeface(Typeface.create("sans-serif", Typeface.BOLD));
|
||||
canvas.drawText(Integer.toString(buttonData.tasks.size()), 50, 50, paint);
|
||||
}
|
||||
badgedIcon = new BitmapDrawable(null, bitmap);
|
||||
}
|
||||
|
||||
return badgedIcon;
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Icon not found for " + appInfo, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Drawable icon) {
|
||||
mImageView.setImageDrawable(icon);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,364 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.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.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
|
||||
* disk in SharedPreferences. Each icon has a separate pref entry consisting of a flattened
|
||||
* ComponentName.
|
||||
*/
|
||||
class NavigationBarAppsModel {
|
||||
public interface OnAppsChangedListener {
|
||||
void onPinnedAppsChanged();
|
||||
}
|
||||
|
||||
public class ResolvedApp {
|
||||
Intent launchIntent;
|
||||
ResolveInfo ri;
|
||||
}
|
||||
|
||||
private final static String TAG = "NavigationBarAppsModel";
|
||||
|
||||
// Default number of apps to load initially.
|
||||
private final static int NUM_INITIAL_APPS = 4;
|
||||
|
||||
// Preferences file name.
|
||||
private final static String SHARED_PREFERENCES_NAME = "com.android.systemui.navbarapps";
|
||||
|
||||
// Preference name for the version of the other preferences.
|
||||
private final static String VERSION_PREF = "version";
|
||||
|
||||
// Current version number for preferences.
|
||||
private final static int CURRENT_VERSION = 3;
|
||||
|
||||
// Preference name for the number of app icons.
|
||||
private final static String APP_COUNT_PREF = "app_count";
|
||||
|
||||
// Preference name prefix for each app's info. The actual pref has an integer appended to it.
|
||||
private final static String APP_PREF_PREFIX = "app_";
|
||||
|
||||
// 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_";
|
||||
|
||||
// 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 = '|';
|
||||
|
||||
private final Context mContext;
|
||||
private final UserManager mUserManager;
|
||||
private final SharedPreferences mPrefs;
|
||||
|
||||
// Apps are represented as an ordered list of app infos.
|
||||
private List<AppInfo> mApps = new ArrayList<AppInfo>();
|
||||
|
||||
private List<OnAppsChangedListener> mOnAppsChangedListeners =
|
||||
new ArrayList<OnAppsChangedListener>();
|
||||
|
||||
// Id of the current user.
|
||||
private int mCurrentUserId = -1;
|
||||
|
||||
// Serial number of the current user.
|
||||
private long mCurrentUserSerialNumber = -1;
|
||||
|
||||
public NavigationBarAppsModel(Context context) {
|
||||
mContext = context;
|
||||
mPrefs = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected IPackageManager getPackageManager() {
|
||||
return AppGlobals.getPackageManager();
|
||||
}
|
||||
|
||||
// Returns a resolved app info for a given app info, or null if the app info is unlauncheable.
|
||||
public ResolvedApp resolveApp(AppInfo appInfo) {
|
||||
ComponentName component = appInfo.getComponentName();
|
||||
int appUserId = appInfo.getUser().getIdentifier();
|
||||
|
||||
if (mCurrentUserId != appUserId) {
|
||||
// Check if app user is a profile of current user and the app user is enabled.
|
||||
UserInfo appUserInfo = mUserManager.getUserInfo(appUserId);
|
||||
UserInfo currentUserInfo = mUserManager.getUserInfo(mCurrentUserId);
|
||||
if (appUserInfo == null || currentUserInfo == null
|
||||
|| appUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
|
||||
|| appUserInfo.profileGroupId != currentUserInfo.profileGroupId
|
||||
|| !appUserInfo.isEnabled()) {
|
||||
Slog.e(TAG, "User " + appUserId +
|
||||
" is is not a profile of the current user, or is disabled.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Slog.e(TAG, "Activity " + component + " is not installed.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!info.exported) {
|
||||
Slog.e(TAG, "Activity " + component + " doesn't have 'exported' attribute.");
|
||||
return null;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.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) {
|
||||
ResolveInfo ri = apps.get(i);
|
||||
ActivityInfo activityInfo = ri.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);
|
||||
ResolvedApp resolvedApp = new ResolvedApp();
|
||||
resolvedApp.launchIntent = launchIntent;
|
||||
resolvedApp.ri = ri;
|
||||
return resolvedApp;
|
||||
}
|
||||
}
|
||||
|
||||
Slog.i(TAG, "Activity doesn't have category Intent.CATEGORY_LAUNCHER " + component);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addOnAppsChangedListener(OnAppsChangedListener listener) {
|
||||
mOnAppsChangedListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeOnAppsChangedListener(OnAppsChangedListener listener) {
|
||||
mOnAppsChangedListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitializes the model for a new user.
|
||||
*/
|
||||
public void setCurrentUser(int userId) {
|
||||
mCurrentUserId = userId;
|
||||
mCurrentUserSerialNumber = mUserManager.getSerialNumberForUser(new UserHandle(userId));
|
||||
|
||||
mApps.clear();
|
||||
|
||||
int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1);
|
||||
if (appCount >= 0) {
|
||||
loadAppsFromPrefs(appCount);
|
||||
} 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() {
|
||||
// Build a set of string representations of serial numbers of the device users.
|
||||
final List<UserInfo> users = mUserManager.getUsers();
|
||||
final int userCount = users.size();
|
||||
|
||||
final Set<String> userSerials = new HashSet<String> ();
|
||||
|
||||
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<String, ?> allPrefs = mPrefs.getAll();
|
||||
final SharedPreferences.Editor edit = mPrefs.edit();
|
||||
|
||||
for (Map.Entry<String, ?> 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 list of apps. */
|
||||
public List<AppInfo> getApps() {
|
||||
return mApps;
|
||||
}
|
||||
|
||||
/** Sets the list of apps and saves it. */
|
||||
public void setApps(List<AppInfo> apps) {
|
||||
mApps = apps;
|
||||
savePrefs();
|
||||
|
||||
int size = mOnAppsChangedListeners.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
mOnAppsChangedListeners.get(i).onPinnedAppsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/** Saves the current model to disk. */
|
||||
private void savePrefs() {
|
||||
SharedPreferences.Editor edit = mPrefs.edit();
|
||||
int appCount = mApps.size();
|
||||
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();
|
||||
edit.putString(prefNameForApp(i), componentNameString);
|
||||
long userSerialNumber = mUserManager.getSerialNumberForUser(appInfo.getUser());
|
||||
edit.putLong(prefUserForApp(i), userSerialNumber);
|
||||
}
|
||||
// Start an asynchronous disk write.
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
/** Loads AppInfo from prefs. Returns null if something is wrong. */
|
||||
private AppInfo loadAppFromPrefs(int index) {
|
||||
String prefValue = mPrefs.getString(prefNameForApp(index), null);
|
||||
if (prefValue == null) {
|
||||
Slog.w(TAG, "Couldn't find pref " + prefNameForApp(index));
|
||||
return null;
|
||||
}
|
||||
ComponentName componentName = ComponentName.unflattenFromString(prefValue);
|
||||
if (componentName == null) {
|
||||
Slog.w(TAG, "Invalid component name " + prefValue);
|
||||
return null;
|
||||
}
|
||||
long userSerialNumber = mPrefs.getLong(prefUserForApp(index), -1);
|
||||
if (userSerialNumber == -1) {
|
||||
Slog.w(TAG, "Couldn't find pref " + prefUserForApp(index));
|
||||
return null;
|
||||
}
|
||||
UserHandle appUser = mUserManager.getUserForSerialNumber(userSerialNumber);
|
||||
if (appUser == null) {
|
||||
Slog.w(TAG, "No user for serial " + userSerialNumber);
|
||||
return null;
|
||||
}
|
||||
AppInfo appInfo = new AppInfo(componentName, appUser);
|
||||
if (resolveApp(appInfo) == null) {
|
||||
return null;
|
||||
}
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
/** Loads the list of apps from SharedPreferences. */
|
||||
private void loadAppsFromPrefs(int appCount) {
|
||||
for (int i = 0; i < appCount; i++) {
|
||||
AppInfo appInfo = loadAppFromPrefs(i);
|
||||
if (appInfo != null) {
|
||||
mApps.add(appInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (appCount != mApps.size()) savePrefs();
|
||||
}
|
||||
|
||||
/** Adds the first few apps from the owner profile. Used for demo purposes. */
|
||||
private void addDefaultApps() {
|
||||
// Get a list of all app activities.
|
||||
final Intent queryIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||
queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
|
||||
final List<ResolveInfo> apps = mContext.getPackageManager().queryIntentActivitiesAsUser(
|
||||
queryIntent, 0 /* flags */, mCurrentUserId);
|
||||
final int appCount = apps.size();
|
||||
for (int i = 0; i < NUM_INITIAL_APPS && i < appCount; i++) {
|
||||
ResolveInfo ri = apps.get(i);
|
||||
ComponentName componentName = new ComponentName(
|
||||
ri.activityInfo.packageName, ri.activityInfo.name);
|
||||
mApps.add(new AppInfo(componentName, new UserHandle(mCurrentUserId)));
|
||||
}
|
||||
|
||||
savePrefs();
|
||||
}
|
||||
|
||||
/** Returns a pref prefixed with the serial number of the current user. */
|
||||
private String userPrefixed(String pref) {
|
||||
return Long.toString(mCurrentUserSerialNumber) + USER_SEPARATOR + pref;
|
||||
}
|
||||
|
||||
/** Returns the pref name for the app at a given 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 String prefUserForApp(int index) {
|
||||
return userPrefixed(APP_USER_PREFIX + Integer.toString(index));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,431 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
|
||||
private NavigationBarAppsModel mModel;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Mockito setup boilerplate.
|
||||
System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
|
||||
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
||||
|
||||
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);
|
||||
|
||||
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<UserInfo>());
|
||||
// Assume the version pref is present and equal to the current version.
|
||||
when(mMockPrefs.getInt("version", -1)).thenReturn(3);
|
||||
when(mMockPrefs.edit()).thenReturn(mMockEdit);
|
||||
|
||||
when(mMockUserManager.getSerialNumberForUser(new UserHandle(2))).thenReturn(222L);
|
||||
when(mMockUserManager.getSerialNumberForUser(new UserHandle(4))).thenReturn(444L);
|
||||
when(mMockUserManager.getSerialNumberForUser(new UserHandle(5))).thenReturn(555L);
|
||||
when(mMockUserManager.getUserForSerialNumber(222L)).thenReturn(new UserHandle(2));
|
||||
when(mMockUserManager.getUserForSerialNumber(444L)).thenReturn(new UserHandle(4));
|
||||
when(mMockUserManager.getUserForSerialNumber(555L)).thenReturn(new UserHandle(5));
|
||||
|
||||
UserInfo ui2 = new UserInfo();
|
||||
ui2.profileGroupId = 999;
|
||||
UserInfo ui4 = new UserInfo();
|
||||
ui4.profileGroupId = 999;
|
||||
UserInfo ui5 = new UserInfo();
|
||||
ui5.profileGroupId = 999;
|
||||
when(mMockUserManager.getUserInfo(2)).thenReturn(ui2);
|
||||
when(mMockUserManager.getUserInfo(4)).thenReturn(ui4);
|
||||
when(mMockUserManager.getUserInfo(5)).thenReturn(ui5);
|
||||
|
||||
mModel = new NavigationBarAppsModel(context) {
|
||||
@Override
|
||||
protected IPackageManager getPackageManager() {
|
||||
return mMockIPackageManager;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Tests resolveApp(). */
|
||||
public void testResolveApp() {
|
||||
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.");
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
mModel.setCurrentUser(3);
|
||||
// Unlauncheable (for various reasons) apps.
|
||||
assertEquals(null, mModel.resolveApp(
|
||||
new AppInfo(new ComponentName("package0", "class0"), new UserHandle(3))));
|
||||
mModel.setCurrentUser(4);
|
||||
assertEquals(null, mModel.resolveApp(
|
||||
new AppInfo(new ComponentName("package1", "class1"), new UserHandle(4))));
|
||||
mModel.setCurrentUser(5);
|
||||
assertEquals(null, mModel.resolveApp(
|
||||
new AppInfo(new ComponentName("package2", "class2"), new UserHandle(5))));
|
||||
mModel.setCurrentUser(6);
|
||||
assertEquals(null, mModel.resolveApp(
|
||||
new AppInfo(new ComponentName("package3", "class3"), new UserHandle(6))));
|
||||
|
||||
// A launcheable app.
|
||||
mModel.setCurrentUser(7);
|
||||
NavigationBarAppsModel.ResolvedApp resolvedApp = mModel.resolveApp(
|
||||
new AppInfo(new ComponentName("package4", "class4"), new UserHandle(7)));
|
||||
assertNotNull(resolvedApp);
|
||||
Intent intent = resolvedApp.launchIntent;
|
||||
assertEquals(new ComponentName("package4", "class4"), intent.getComponent());
|
||||
assertEquals("package4", intent.getPackage());
|
||||
assertEquals(ri1, resolvedApp.ri);
|
||||
}
|
||||
|
||||
/** Initializes the model from SharedPreferences for a few app activites. */
|
||||
private void initializeModelFromPrefs() {
|
||||
// Assume several apps are stored.
|
||||
when(mMockPrefs.getInt("222|app_count", -1)).thenReturn(2);
|
||||
when(mMockPrefs.getString("222|app_0", null)).thenReturn("package1/class1");
|
||||
when(mMockPrefs.getLong("222|app_user_0", -1)).thenReturn(444L);
|
||||
when(mMockPrefs.getString("222|app_1", null)).thenReturn("package2/class2");
|
||||
when(mMockPrefs.getLong("222|app_user_1", -1)).thenReturn(555L);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/** Tests initializing the model from SharedPreferences. */
|
||||
public void testInitializeFromPrefs() {
|
||||
initializeModelFromPrefs();
|
||||
List<AppInfo> apps = mModel.getApps();
|
||||
assertEquals(2, apps.size());
|
||||
assertEquals("package1/class1", apps.get(0).getComponentName().flattenToString());
|
||||
assertEquals(new UserHandle(4), apps.get(0).getUser());
|
||||
assertEquals("package2/class2", apps.get(1).getComponentName().flattenToString());
|
||||
assertEquals(new UserHandle(5), apps.get(1).getUser());
|
||||
}
|
||||
|
||||
/** Tests initializing the model when the SharedPreferences aren't available. */
|
||||
public void testInitializeDefaultApps() {
|
||||
// Assume the user's app count pref isn't available.
|
||||
when(mMockPrefs.getInt("222|app_count", -1)).thenReturn(-1);
|
||||
|
||||
// Assume some installed activities.
|
||||
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(2)))
|
||||
.thenReturn(Arrays.asList(ri1, ri2));
|
||||
|
||||
// Setting the user should load the installed activities.
|
||||
mModel.setCurrentUser(2);
|
||||
List<AppInfo> apps = mModel.getApps();
|
||||
assertEquals(2, apps.size());
|
||||
assertEquals("package1/class1", apps.get(0).getComponentName().flattenToString());
|
||||
assertEquals(new UserHandle(2), apps.get(0).getUser());
|
||||
assertEquals("package2/class2", apps.get(1).getComponentName().flattenToString());
|
||||
assertEquals(new UserHandle(2), apps.get(1).getUser());
|
||||
InOrder order = inOrder(mMockEdit);
|
||||
order.verify(mMockEdit).apply();
|
||||
order.verify(mMockEdit).putInt("222|app_count", 2);
|
||||
order.verify(mMockEdit).putString("222|app_0", "package1/class1");
|
||||
order.verify(mMockEdit).putLong("222|app_user_0", 222L);
|
||||
order.verify(mMockEdit).putString("222|app_1", "package2/class2");
|
||||
order.verify(mMockEdit).putLong("222|app_user_1", 222L);
|
||||
order.verify(mMockEdit).apply();
|
||||
verifyNoMoreInteractions(mMockEdit);
|
||||
}
|
||||
|
||||
/** Tests initializing the model if one of the prefs is missing. */
|
||||
public void testInitializeWithMissingPref() {
|
||||
// Assume two apps are nominally stored.
|
||||
when(mMockPrefs.getInt("222|app_count", -1)).thenReturn(2);
|
||||
when(mMockPrefs.getString("222|app_0", null)).thenReturn("package0/class0");
|
||||
when(mMockPrefs.getLong("222|app_user_0", -1)).thenReturn(555L);
|
||||
|
||||
// But assume one pref is missing.
|
||||
when(mMockPrefs.getString("222|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);
|
||||
List<AppInfo> apps = mModel.getApps();
|
||||
assertEquals(1, apps.size());
|
||||
assertEquals("package0/class0", apps.get(0).getComponentName().flattenToString());
|
||||
assertEquals(new UserHandle(5), apps.get(0).getUser());
|
||||
InOrder order = inOrder(mMockEdit);
|
||||
order.verify(mMockEdit).putInt("222|app_count", 1);
|
||||
order.verify(mMockEdit).putString("222|app_0", "package0/class0");
|
||||
order.verify(mMockEdit).putLong("222|app_user_0", 555L);
|
||||
order.verify(mMockEdit).apply();
|
||||
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("222|app_count", -1)).thenReturn(2);
|
||||
when(mMockPrefs.getString("222|app_0", null)).thenReturn("package0/class0");
|
||||
when(mMockPrefs.getLong("222|app_user_0", -1)).thenReturn(555L);
|
||||
when(mMockPrefs.getString("222|app_1", null)).thenReturn("package1/class1");
|
||||
when(mMockPrefs.getLong("222|app_user_1", -1)).thenReturn(444L);
|
||||
|
||||
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);
|
||||
List<AppInfo> apps = mModel.getApps();
|
||||
assertEquals(1, apps.size());
|
||||
assertEquals("package0/class0", apps.get(0).getComponentName().flattenToString());
|
||||
assertEquals(new UserHandle(5), apps.get(0).getUser());
|
||||
|
||||
// Once an unlauncheable app is detected, the model should save all apps excluding the
|
||||
// unlauncheable one.
|
||||
verify(mMockEdit).putInt("222|app_count", 1);
|
||||
verify(mMockEdit).putString("222|app_0", "package0/class0");
|
||||
verify(mMockEdit).putLong("222|app_user_0", 555L);
|
||||
verify(mMockEdit).apply();
|
||||
verifyNoMoreInteractions(mMockEdit);
|
||||
}
|
||||
|
||||
/** Tests saving the model to SharedPreferences. */
|
||||
public void testSavePrefs() {
|
||||
initializeModelFromPrefs();
|
||||
|
||||
mModel.setApps(mModel.getApps());
|
||||
verify(mMockEdit).putInt("222|app_count", 2);
|
||||
verify(mMockEdit).putString("222|app_0", "package1/class1");
|
||||
verify(mMockEdit).putLong("222|app_user_0", 444L);
|
||||
verify(mMockEdit).putString("222|app_1", "package2/class2");
|
||||
verify(mMockEdit).putLong("222|app_user_1", 555L);
|
||||
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", 3);
|
||||
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("222|app_count", -1)).thenReturn(-1);
|
||||
|
||||
final Map allPrefs = new HashMap<String, Object>();
|
||||
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(2);
|
||||
verify(mMockEdit).remove("some_strange_pref");
|
||||
verify(mMockEdit).remove("");
|
||||
verify(mMockEdit).remove("|");
|
||||
verify(mMockEdit).remove("1212|app_count");
|
||||
verify(mMockEdit).apply();
|
||||
verifyNoMoreInteractions(mMockEdit);
|
||||
}
|
||||
|
||||
/** Tests the apps-changed listener. */
|
||||
public void testAppsChangedListeners() {
|
||||
NavigationBarAppsModel.OnAppsChangedListener listener =
|
||||
mock(NavigationBarAppsModel.OnAppsChangedListener.class);
|
||||
|
||||
mModel.addOnAppsChangedListener(listener);
|
||||
mModel.setApps(new ArrayList<AppInfo>());
|
||||
verify(listener).onPinnedAppsChanged();
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
mModel.removeOnAppsChangedListener(listener);
|
||||
mModel.setApps(new ArrayList<AppInfo>());
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user