DO NOT MERGE sysui: remove shelf
Bug: 26742568
Change-Id: I36bd013c4adb62b76b6f2789aa490c1484a535d7
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java
deleted file mode 100644
index d2bec7c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
deleted file mode 100644
index ed6d940..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ /dev/null
@@ -1,1123 +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 static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
-import android.animation.LayoutTransition;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo;
-import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
-import android.app.IActivityManager;
-import android.app.ITaskStackListener;
-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.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.AttributeSet;
-import android.util.Slog;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.PopupMenu;
-import android.widget.Toast;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Container for application icons that appear in the navigation bar. Their appearance is similar
- * to the launcher hotseat. Clicking an icon launches or activates the associated activity. A long
- * click will trigger a drag to allow the icons to be reordered. As an icon is dragged the other
- * icons shift to make space for it to be dropped. These layout changes are animated.
- * Navigation bar contains both pinned and unpinned apps: pinned in the left part, unpinned in the
- * right part, with no separator in between.
- */
-class NavigationBarApps extends LinearLayout
- implements NavigationBarAppsModel.OnAppsChangedListener {
- public final static boolean DEBUG = false;
- private final static String TAG = "NavigationBarApps";
-
- /**
- * Intent extra to store user serial number.
- */
- static final String EXTRA_PROFILE = "profile";
-
- // There are separate NavigationBarApps view instances for landscape vs. portrait, but they
- // share the data model.
- private static NavigationBarAppsModel sAppsModel;
-
- private final PackageManager mPackageManager;
- private final UserManager mUserManager;
- private final LayoutInflater mLayoutInflater;
- private final AppPackageMonitor mAppPackageMonitor;
- private final WindowManager mWindowManager;
-
-
- // This view has two roles:
- // 1) If the drag started outside the pinned apps list, it is a placeholder icon with a null
- // tag.
- // 2) If the drag started inside the pinned apps list, it is the icon for the app being dragged
- // with the associated AppInfo tag.
- // The icon is set invisible for the duration of the drag, creating a visual space for a drop.
- // When the user is not dragging this member is null.
- private ImageView mDragView;
-
- 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);
- } else if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- UserHandle removedProfile = intent.getParcelableExtra(Intent.EXTRA_USER);
- onManagedProfileRemoved(removedProfile);
- }
- }
- };
-
- // Layout params for the window that contains the anchor for the popup menus.
- // We need to create a window for a popup menu because the NavBar window is too narrow and can't
- // contain the menu.
- private final WindowManager.LayoutParams mPopupAnchorLayoutParams;
- // View that contains the anchor for popup menus. The view occupies the whole screen, and
- // has a child that will be moved to make the menu to appear where we need it.
- private final ViewGroup mPopupAnchor;
- private final PopupMenu mPopupMenu;
-
- /**
- * True if popup menu code is busy with a popup operation.
- * Attempting to show a popup menu or to add menu items while it's returning true will
- * corrupt/crash the app.
- */
- private boolean mIsPopupInUse = false;
- private final int [] mClickedIconLocation = new int[2];
-
- public NavigationBarApps(Context context, AttributeSet attrs) {
- super(context, attrs);
- if (sAppsModel == null) {
- sAppsModel = new NavigationBarAppsModel(context);
- }
- mPackageManager = context.getPackageManager();
- mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_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.
- LayoutTransition transition = new LayoutTransition();
- // Don't trigger on disappear. Adding the view will trigger the layout animation.
- transition.disableTransitionType(LayoutTransition.DISAPPEARING);
- // Don't animate the dragged icon itself.
- transition.disableTransitionType(LayoutTransition.APPEARING);
- // When an icon is dragged off the shelf, start sliding the other icons over immediately
- // to match the parent view's animation.
- transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
- transition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 0);
- setLayoutTransition(transition);
-
- TaskStackListener taskStackListener = new TaskStackListener();
- IActivityManager iam = ActivityManagerNative.getDefault();
- try {
- iam.registerTaskStackListener(taskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "registerTaskStackListener failed", e);
- }
-
- mPopupAnchorLayoutParams =
- new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
- WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
- WindowManager.LayoutParams.FLAG_FULLSCREEN
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
- PixelFormat.TRANSLUCENT);
- mPopupAnchorLayoutParams.setTitle("ShelfMenuAnchor");
-
- mPopupAnchor = (ViewGroup) mLayoutInflater.inflate(R.layout.shelf_menu_anchor, null);
-
- ImageView anchorButton =
- (ImageView) mPopupAnchor.findViewById(R.id.shelf_menu_anchor_anchor);
- mPopupMenu = new PopupMenu(context, anchorButton);
- }
-
- // Monitor that catches events like "app uninstalled".
- private class AppPackageMonitor extends PackageMonitor {
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- postUnpinIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
- super.onPackageRemoved(packageName, uid);
- }
-
- @Override
- public void onPackageModified(String packageName) {
- postUnpinIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
- super.onPackageModified(packageName);
- }
-
- @Override
- public void onPackagesAvailable(String[] packages) {
- if (isReplacing()) {
- UserHandle user = new UserHandle(getChangingUserId());
-
- for (String packageName : packages) {
- postUnpinIfUnlauncheable(packageName, user);
- }
- }
- super.onPackagesAvailable(packages);
- }
-
- @Override
- public void onPackagesUnavailable(String[] packages) {
- if (!isReplacing()) {
- UserHandle user = new UserHandle(getChangingUserId());
-
- for (String packageName : packages) {
- postUnpinIfUnlauncheable(packageName, user);
- }
- }
- super.onPackagesUnavailable(packages);
- }
- }
-
- private void postUnpinIfUnlauncheable(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;
- unpinIfUnlauncheable(packageName, user);
- }
- });
- }
-
- private void unpinIfUnlauncheable(String packageName, UserHandle user) {
- // Unpin icons for all apps that match a package that perhaps became unlauncheable.
- boolean appsWereUnpinned = false;
- for(int i = getChildCount() - 1; i >= 0; --i) {
- View child = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)child.getTag();
- if (appButtonData == null) continue; // Skip the drag placeholder.
-
- if (!appButtonData.pinned) continue;
-
- AppInfo appInfo = appButtonData.appInfo;
- if (!appInfo.getUser().equals(user)) continue;
-
- ComponentName appComponentName = appInfo.getComponentName();
- if (!appComponentName.getPackageName().equals(packageName)) continue;
-
- if (sAppsModel.resolveApp(appInfo) != null) {
- continue;
- }
-
- appButtonData.pinned = false;
- appsWereUnpinned = true;
-
- if (appButtonData.isEmpty()) {
- removeViewAt(i);
- }
- }
- if (appsWereUnpinned) {
- savePinnedApps();
- }
- }
-
- @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);
-
- sAppsModel.setCurrentUser(ActivityManager.getCurrentUser());
- recreatePinnedAppButtons();
- updateRecentApps();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
- mContext.registerReceiver(mBroadcastReceiver, filter);
-
- mAppPackageMonitor.register(mContext, null, UserHandle.ALL, true);
- sAppsModel.addOnAppsChangedListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mContext.unregisterReceiver(mBroadcastReceiver);
- mAppPackageMonitor.unregister();
- sAppsModel.removeOnAppsChangedListener(this);
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (mIsPopupInUse && !isShown()) {
- // Hide the popup if current view became invisible.
- shutdownPopupMenu();
- }
- }
-
- private void addAppButton(AppButtonData appButtonData) {
- ImageView button = createAppButton();
- updateApp(button, appButtonData);
- addView(button);
- }
-
- private List<AppInfo> getPinnedApps() {
- List<AppInfo> apps = new ArrayList<AppInfo>();
- int childCount = getChildCount();
- for (int i = 0; i != childCount; ++i) {
- View child = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)child.getTag();
- if (appButtonData == null) continue; // Skip the drag placeholder.
- if(!appButtonData.pinned) continue;
- apps.add(appButtonData.appInfo);
- }
- return apps;
- }
-
- /**
- * Creates an ImageView icon for each pinned app. Removes any existing icons. May be called
- * to synchronize the current view with the shared data mode.
- */
- private void recreatePinnedAppButtons() {
- // Remove any existing icon buttons.
- removeAllViews();
-
- List<AppInfo> apps = sAppsModel.getApps();
- int appCount = apps.size();
- for (int i = 0; i < appCount; i++) {
- AppInfo app = apps.get(i);
- addAppButton(new AppButtonData(app, true /* pinned */));
- }
- }
-
- /**
- * Saves pinned apps stored in app icons into the data model.
- */
- private void savePinnedApps() {
- sAppsModel.setApps(getPinnedApps());
- }
-
- /**
- * Creates a new ImageView for an app, inflated from R.layout.navigation_bar_app_item.
- */
- private ImageView createAppButton() {
- ImageView button = (ImageView) mLayoutInflater.inflate(
- R.layout.navigation_bar_app_item, this, false /* attachToRoot */);
- button.setOnHoverListener(new AppHoverListener());
- button.setOnClickListener(new AppClickListener());
- button.setOnContextClickListener(new AppContextClickListener());
- // TODO: Ripple effect. Use either KeyButtonRipple or the default ripple background.
- button.setOnLongClickListener(new AppLongClickListener());
- button.setOnDragListener(new AppIconDragListener());
- return button;
- }
-
- private class AppLongClickListener implements View.OnLongClickListener {
- @Override
- public boolean onLongClick(View v) {
- mDragView = (ImageView) v;
- AppButtonData appButtonData = (AppButtonData) v.getTag();
- startAppDrag(mDragView, appButtonData.appInfo);
- return true;
- }
- }
-
- /**
- * Returns the human-readable name for an activity's package or null.
- * TODO: Cache the labels, perhaps in an LruCache.
- */
- @Nullable
- private CharSequence getAppLabel(AppInfo appInfo) {
- NavigationBarAppsModel.ResolvedApp resolvedApp = sAppsModel.resolveApp(appInfo);
- if (resolvedApp == null) return null;
-
- CharSequence unbadgedLabel = resolvedApp.ri.loadLabel(mPackageManager);
- return mUserManager.getBadgedLabelForUser(unbadgedLabel, appInfo.getUser());
- }
-
- /** Helper function to start dragging an app icon (either pinned or recent). */
- static void startAppDrag(ImageView icon, AppInfo appInfo) {
- // The drag data is an Intent to launch the activity.
- Intent mainIntent = Intent.makeMainActivity(appInfo.getComponentName());
- UserManager userManager =
- (UserManager) icon.getContext().getSystemService(Context.USER_SERVICE);
- long userSerialNumber = userManager.getSerialNumberForUser(appInfo.getUser());
- mainIntent.putExtra(EXTRA_PROFILE, userSerialNumber);
- ClipData dragData = ClipData.newIntent("", mainIntent);
- // Use the ImageView to create the shadow.
- View.DragShadowBuilder shadow = new AppIconDragShadowBuilder(icon);
- // Use a global drag because the icon might be dragged into the launcher.
- icon.startDrag(dragData, shadow, null /* myLocalState */, View.DRAG_FLAG_GLOBAL);
- }
-
- @Override
- public boolean dispatchDragEvent(DragEvent event) {
- // ACTION_DRAG_ENTERED is handled by each individual app icon drag listener.
- boolean childHandled = super.dispatchDragEvent(event);
-
- // Other drag types are handled once per drag by this view. This is handled explicitly
- // because attaching a DragListener to this ViewGroup does not work -- the DragListener in
- // the children consumes the drag events.
- boolean handled = false;
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- handled = onDragStarted(event);
- break;
- case DragEvent.ACTION_DRAG_ENDED:
- handled = onDragEnded();
- break;
- case DragEvent.ACTION_DROP:
- handled = onDrop(event);
- break;
- case DragEvent.ACTION_DRAG_EXITED:
- handled = onDragExited();
- break;
- }
-
- return handled || childHandled;
- }
-
- /** Returns true if a drag should be handled. */
- private static boolean canAcceptDrag(DragEvent event) {
- // Poorly behaved apps might not provide a clip description.
- if (event.getClipDescription() == null) {
- return false;
- }
- // The event must contain an intent.
- return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT);
- }
-
- /**
- * Sets up for a drag. Runs once per drag operation. Returns true if the data represents
- * an app shortcut and will be accepted for a drop.
- */
- private boolean onDragStarted(DragEvent event) {
- if (DEBUG) Slog.d(TAG, "onDragStarted");
-
- // Ensure that an app shortcut is being dragged.
- if (!canAcceptDrag(event)) {
- return false;
- }
-
- // If there are no pinned apps this view will be collapsed, but the user still needs some
- // empty space to use as a drag target.
- if (getChildCount() == 0) {
- mDragView = createPlaceholderDragView(0);
- }
-
- // If this is an existing icon being reordered, hide the app icon. The drag shadow will
- // continue to draw.
- if (mDragView != null) {
- mDragView.setVisibility(View.INVISIBLE);
- }
-
- // Listen for the drag end event.
- return true;
- }
-
- /**
- * Creates a blank icon-sized View to create an empty space during a drag.
- */
- private ImageView createPlaceholderDragView(int index) {
- ImageView button = createAppButton();
- addView(button, index);
- return button;
- }
-
- /**
- * Returns initial index for a new app that doesn't exist in Shelf.
- * Such apps get created by dragging them into Shelf from other apps or by dragging from Shelf
- * and then back, or by removing from shelf as an intermediate step of pinning an app via menu.
- * @param indexHint Initial proposed position for the item.
- * @param isAppPinned True if the app being dragged is pinned.
- */
- int getNewAppIndex(int indexHint, boolean isAppPinned) {
- int i;
- if (isAppPinned) {
- // For a pinned app, find the rightmost position to the left of the target that has a
- // pinned app. We'll insert to the right of that position.
- for (i = indexHint; i > 0; --i) {
- View v = getChildAt(i - 1);
- AppButtonData targetButtonData = (AppButtonData) v.getTag();
- if (targetButtonData.pinned) break;
- }
- } else {
- // For an unpinned app, find the leftmost position to the right of the target that has
- // an unpinned app. We'll insert to the left of that position.
- int childCount = getChildCount();
- for (i = indexHint; i < childCount; ++i) {
- View v = getChildAt(i);
- AppButtonData targetButtonData = (AppButtonData) v.getTag();
- if (!targetButtonData.pinned) break;
- }
- }
- return i;
- }
-
- /**
- * Handles a drag entering an existing icon. Not implemented in the drag listener because it
- * needs to use LinearLayout/ViewGroup methods.
- */
- private void onDragEnteredIcon(View target) {
- if (DEBUG) Slog.d(TAG, "onDragEntered " + indexOfChild(target));
-
- int targetIndex = indexOfChild(target);
-
- // If the drag didn't start from an existing shelf icon, add an invisible placeholder to
- // create empty space for the user to drag into.
- if (mDragView == null) {
- mDragView = createPlaceholderDragView(getNewAppIndex(targetIndex, true));
- return;
- }
-
- // If the user is dragging on top of the original icon location, do nothing.
- if (target == mDragView) {
- return;
- }
-
- // "Move" the dragged app by removing it and adding it back at the target location.
- AppButtonData targetButtonData = (AppButtonData) target.getTag();
- int dragViewIndex = indexOfChild(mDragView);
- AppButtonData dragViewButtonData = (AppButtonData) mDragView.getTag();
- // Calculating whether the dragged app is pinned. If the app came from outside if the shelf,
- // in which case dragViewButtonData == null, it's a new app that we'll pin. Otherwise, the
- // button data is defined, and we look whether that existing app is pinned.
- boolean isAppPinned = dragViewButtonData == null || dragViewButtonData.pinned;
-
- if (dragViewIndex == -1) {
- // Drag view exists, but is not a child, which means that the drag has started at or
- // already visited shelf, then left it, and now is entering it again.
- targetIndex = getNewAppIndex(targetIndex, isAppPinned);
- } else if (dragViewIndex < targetIndex) {
- // The dragged app is currently at the left of the view where the drag is.
- // We shouldn't allow moving a pinned app to the right of the unpinned app.
- if (!targetButtonData.pinned && isAppPinned) return;
- } else {
- // The dragged app is currently at the right of the view where the drag is.
- // We shouldn't allow moving a unpinned app to the left of the pinned app.
- if (targetButtonData.pinned && !isAppPinned) return;
- }
-
- // This works, but is subtle:
- // * If dragViewIndex > targetIndex then the dragged app is moving from right to left and
- // the dragged app will be added in front of the target.
- // * If dragViewIndex < targetIndex then the dragged app is moving from left to right.
- // Removing the drag view will shift the later views one position to the left. Adding
- // the view at targetIndex will therefore place the app *after* the target.
- removeView(mDragView);
- addView(mDragView, targetIndex);
- }
-
- private boolean onDrop(DragEvent event) {
- if (DEBUG) Slog.d(TAG, "onDrop");
-
- // An earlier drag event might have canceled the drag. If so, there is nothing to do.
- if (mDragView == null) {
- return true;
- }
-
- boolean dragResult = true;
- AppInfo appInfo = getAppFromDragEvent(event);
- if (appInfo == null) {
- // This wasn't a valid drop. Clean up the placeholder.
- removePlaceholderDragViewIfNeeded();
- dragResult = false;
- } else if (mDragView.getTag() == null) {
- // This is a drag that adds a new app. Convert the placeholder to a real icon.
- updateApp(mDragView, new AppButtonData(appInfo, true /* pinned */));
- }
- endDrag();
- return dragResult;
- }
-
- /** Cleans up at the end of a drag. */
- private void endDrag() {
- // An earlier drag event might have canceled the drag. If so, there is nothing to do.
- if (mDragView == null) return;
-
- mDragView.setVisibility(View.VISIBLE);
- mDragView = null;
- savePinnedApps();
- // Add recent tasks to the info of the potentially added app.
- updateRecentApps();
- }
-
- /** Returns an app info from a DragEvent, or null if the data wasn't valid. */
- private AppInfo getAppFromDragEvent(DragEvent event) {
- ClipData data = event.getClipData();
- if (data == null) {
- return null;
- }
- if (data.getItemCount() != 1) {
- return null;
- }
- ClipData.Item item = data.getItemAt(0);
- if (item == null) {
- return null;
- }
- Intent intent = item.getIntent();
- if (intent == null) {
- return null;
- }
- long userSerialNumber = intent.getLongExtra(EXTRA_PROFILE, -1);
- if (userSerialNumber == -1) {
- return null;
- }
- UserHandle appUser = mUserManager.getUserForSerialNumber(userSerialNumber);
- if (appUser == null) {
- return null;
- }
- ComponentName componentName = intent.getComponent();
- if (componentName == null) {
- return null;
- }
- AppInfo appInfo = new AppInfo(componentName, appUser);
- if (sAppsModel.resolveApp(appInfo) == null) {
- return null;
- }
- return appInfo;
- }
-
- /** Updates the app at a given view index. */
- private void updateApp(ImageView button, AppButtonData appButtonData) {
- CharSequence appLabel = getAppLabel(appButtonData.appInfo);
- button.setContentDescription(appLabel);
-
- button.setTag(appButtonData);
- new GetActivityIconTask(mPackageManager, button).execute(appButtonData);
- }
-
- /** Removes the empty placeholder view. */
- private void removePlaceholderDragViewIfNeeded() {
- // If the drag has ended already there is nothing to do.
- if (mDragView == null) {
- return;
- }
- removeView(mDragView);
- }
-
- /** Cleans up at the end of the drag. */
- private boolean onDragEnded() {
- if (DEBUG) Slog.d(TAG, "onDragEnded");
- // If the icon wasn't already dropped into the app list then remove the placeholder.
- removePlaceholderDragViewIfNeeded();
- endDrag();
- return true;
- }
-
- /** Handles the dragged icon exiting the bounds of this view during the drag. */
- private boolean onDragExited() {
- if (DEBUG) Slog.d(TAG, "onDragExited");
- // Remove the placeholder. It will be added again if the user drags the icon back over
- // the shelf.
- removePlaceholderDragViewIfNeeded();
- return true;
- }
-
- /** Drag listener for individual app icons. */
- private class AppIconDragListener implements View.OnDragListener {
- @Override
- public boolean onDrag(View v, DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED: {
- // Every button listens for drag events in order to detect enter/exit.
- return canAcceptDrag(event);
- }
- case DragEvent.ACTION_DRAG_ENTERED: {
- // Forward to NavigationBarApps.
- onDragEnteredIcon(v);
- return false;
- }
- }
- return false;
- }
- }
-
- /**
- * Brings the menu popup to closed state.
- * Can be called at any stage of the asynchronous process of showing a menu.
- */
- private void shutdownPopupMenu() {
- mWindowManager.removeView(mPopupAnchor);
- mPopupMenu.dismiss();
- }
-
- /**
- * Shows already prepopulated popup menu using appIcon for anchor location.
- */
- private void showPopupMenu(ImageView appIcon) {
- // Movable view inside the popup anchor view. It serves as the actual anchor for the
- // menu.
- final ImageView anchorButton =
- (ImageView) mPopupAnchor.findViewById(R.id.shelf_menu_anchor_anchor);
- // Set same drawable as for the clicked button to have same size.
- anchorButton.setImageDrawable(appIcon.getDrawable());
-
- // Move the anchor button to the position of the app button.
- appIcon.getLocationOnScreen(mClickedIconLocation);
- anchorButton.setTranslationX(mClickedIconLocation[0]);
- anchorButton.setTranslationY(mClickedIconLocation[1]);
-
- final OnAttachStateChangeListener onAttachStateChangeListener =
- new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mPopupMenu.show();
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {}
- };
- anchorButton.addOnAttachStateChangeListener(onAttachStateChangeListener);
-
- mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
- @Override
- public void onDismiss(PopupMenu menu) {
- // FYU: thorough testing for closing menu either by the user or via
- // shutdownPopupMenu() called at various moments of the menu creation, revealed that
- // 'onDismiss' is guaranteed to be called after each invocation of showPopupMenu.
- mWindowManager.removeView(mPopupAnchor);
- anchorButton.removeOnAttachStateChangeListener(onAttachStateChangeListener);
- mPopupMenu.setOnDismissListener(null);
- mPopupMenu.getMenu().clear();
- mIsPopupInUse = false;
- }
- });
-
- mWindowManager.addView(mPopupAnchor, mPopupAnchorLayoutParams);
- mIsPopupInUse = true;
- }
-
- private void activateTask(int taskPersistentId) {
- // Launch or bring the activity to front.
- final IActivityManager iAm = ActivityManagerNative.getDefault();
- try {
- iAm.startActivityFromRecents(taskPersistentId, null /* options */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception when activating a recent task", e);
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Exception when activating a recent task", e);
- }
- }
-
- /**
- * Adds to the popup menu items for activating each of tasks in the specified list.
- */
- private void populateLaunchMenu(AppButtonData appButtonData) {
- Menu menu = mPopupMenu.getMenu();
- int taskCount = appButtonData.getTaskCount();
- for (int i = 0; i < taskCount; ++i) {
- final RecentTaskInfo taskInfo = appButtonData.tasks.get(i);
- MenuItem item = menu.add(getActivityForTask(taskInfo).flattenToShortString());
- item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- activateTask(taskInfo.persistentId);
- return true;
- }
- });
- }
- }
-
- /**
- * Shows a task selection menu for clicked or hovered-over apps that have more than 1 running
- * tasks.
- */
- void maybeShowLaunchMenu(ImageView appIcon) {
- if (mIsPopupInUse) return;
- AppButtonData appButtonData = (AppButtonData) appIcon.getTag();
- if (appButtonData.getTaskCount() <= 1) return;
-
- populateLaunchMenu(appButtonData);
- showPopupMenu(appIcon);
- }
-
- /**
- * A listener for hovering over an app icon.
- */
- private class AppHoverListener implements View.OnHoverListener {
- private final long DELAY_MILLIS = 1000;
- private Runnable mShowMenuCallback;
-
- @Override
- public boolean onHover(final View v, MotionEvent event) {
- if (mShowMenuCallback == null) {
- mShowMenuCallback = new Runnable() {
- @Override
- public void run() {
- maybeShowLaunchMenu((ImageView) v);
- }
- };
- }
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- postDelayed(mShowMenuCallback, DELAY_MILLIS);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- removeCallbacks(mShowMenuCallback);
- break;
- }
- return false;
- }
- }
-
- /**
- * A click listener that launches an activity.
- */
- private class AppClickListener implements View.OnClickListener {
- private void launchApp(AppInfo appInfo, View anchor) {
- NavigationBarAppsModel.ResolvedApp resolvedApp = sAppsModel.resolveApp(appInfo);
- if (resolvedApp == null) {
- Toast.makeText(
- getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- return;
- }
-
- Intent launchIntent = resolvedApp.launchIntent;
-
- // Play a scale-up animation while launching the activity.
- // TODO: Consider playing a different animation, or no animation, if the activity is
- // already open in a visible window. In that case we should move the task to front
- // with minimal animation, perhaps using ActivityManager.moveTaskToFront().
- Rect sourceBounds = new Rect();
- anchor.getBoundsOnScreen(sourceBounds);
- ActivityOptions opts =
- ActivityOptions.makeScaleUpAnimation(
- anchor, 0, 0, anchor.getWidth(), anchor.getHeight());
- Bundle optsBundle = opts.toBundle();
- launchIntent.setSourceBounds(sourceBounds);
-
- mContext.startActivityAsUser(launchIntent, optsBundle, appInfo.getUser());
- }
-
- @Override
- public void onClick(View v) {
- AppButtonData appButtonData = (AppButtonData) v.getTag();
-
- if (appButtonData.getTaskCount() == 0) {
- launchApp(appButtonData.appInfo, v);
- } else {
- // Activate latest task.
- activateTask(appButtonData.tasks.get(0).persistentId);
-
- maybeShowLaunchMenu((ImageView) v);
- }
- }
- }
-
- /**
- * Context click listener that shows app's context menu.
- */
- private class AppContextClickListener implements View.OnContextClickListener {
- void updateState(ImageView appIcon) {
- savePinnedApps();
- if (DEBUG) {
- AppButtonData appButtonData = (AppButtonData) appIcon.getTag();
- new GetActivityIconTask(mPackageManager, appIcon).execute(appButtonData);
- }
- }
-
- /**
- * Adds to the popup menu items for pinning and unpinning the app in the shelf.
- */
- void populateContextMenu(final ImageView appIcon) {
- final AppButtonData appButtonData = (AppButtonData) appIcon.getTag();
- Menu menu = mPopupMenu.getMenu();
- if (appButtonData.pinned) {
- menu.add("Unpin").
- setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- appButtonData.pinned = false;
- removeView(appIcon);
- if (!appButtonData.isEmpty()) {
- // If the app has running tasks, re-add it to the end of shelf
- // after unpinning.
- addView(appIcon);
- }
- updateState(appIcon);
- return true;
- }
- });
- } else {
- menu.add("Pin").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- appButtonData.pinned = true;
- removeView(appIcon);
- // Re-add the pinned icon to the end of the pinned list.
- addView(appIcon, getNewAppIndex(getChildCount(), true));
- updateState(appIcon);
- return true;
- }
- });
- }
- }
-
- @Override
- public boolean onContextClick(View v) {
- if (mIsPopupInUse) return true;
- ImageView appIcon = (ImageView) v;
- populateContextMenu(appIcon);
- showPopupMenu(appIcon);
- return true;
- }
- }
-
- private void onUserSwitched(int currentUserId) {
- sAppsModel.setCurrentUser(currentUserId);
- recreatePinnedAppButtons();
- }
-
- private void onManagedProfileRemoved(UserHandle removedProfile) {
- // Unpin apps from the removed profile.
- boolean itemsWereUnpinned = false;
- for(int i = getChildCount() - 1; i >= 0; --i) {
- View view = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)view.getTag();
- if (appButtonData == null) return; // Skip the drag placeholder.
- if (!appButtonData.pinned) continue;
- if (!appButtonData.appInfo.getUser().equals(removedProfile)) continue;
-
- appButtonData.pinned = false;
- itemsWereUnpinned = true;
- if (appButtonData.isEmpty()) {
- removeViewAt(i);
- }
- }
- if (itemsWereUnpinned) {
- savePinnedApps();
- }
- }
-
- /**
- * Returns app data for a button that matches the provided app info, if it exists, or null
- * otherwise.
- */
- private AppButtonData findAppButtonData(AppInfo appInfo) {
- int size = getChildCount();
- for (int i = 0; i < size; ++i) {
- View view = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)view.getTag();
- if (appButtonData == null) continue; // Skip the drag placeholder.
- if (appButtonData.appInfo.equals(appInfo)) {
- return appButtonData;
- }
- }
- return null;
- }
-
- private void updateTasks(List<RecentTaskInfo> tasks) {
- // Remove tasks from all app buttons.
- for (int i = getChildCount() - 1; i >= 0; --i) {
- View view = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)view.getTag();
- if (appButtonData == null) return; // Skip the drag placeholder.
- appButtonData.clearTasks();
- }
-
- // Re-add tasks to app buttons, adding new buttons if needed.
- int size = tasks.size();
- for (int i = 0; i != size; ++i) {
- RecentTaskInfo task = tasks.get(i);
- AppInfo taskAppInfo = taskToAppInfo(task);
- if (taskAppInfo == null) continue;
- AppButtonData appButtonData = findAppButtonData(taskAppInfo);
- if (appButtonData == null) {
- appButtonData = new AppButtonData(taskAppInfo, false);
- addAppButton(appButtonData);
- }
- appButtonData.addTask(task);
- }
-
- // Remove unpinned apps that now have no tasks.
- for (int i = getChildCount() - 1; i >= 0; --i) {
- View view = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)view.getTag();
- if (appButtonData == null) return; // Skip the drag placeholder.
- if (appButtonData.isEmpty()) {
- removeViewAt(i);
- }
- }
-
- if (DEBUG) {
- for (int i = getChildCount() - 1; i >= 0; --i) {
- View view = getChildAt(i);
- AppButtonData appButtonData = (AppButtonData)view.getTag();
- if (appButtonData == null) return; // Skip the drag placeholder.
- new GetActivityIconTask(mPackageManager, (ImageView )view).execute(appButtonData);
-
- }
- }
- }
-
- private void updateRecentApps() {
- ActivityManager activityManager =
- (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
- // TODO: Should this be getRunningTasks?
- List<RecentTaskInfo> recentTasks = activityManager.getRecentTasksForUser(
- ActivityManager.getMaxAppRecentsLimitStatic(),
- ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
- ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES,
- UserHandle.USER_CURRENT);
- if (DEBUG) Slog.d(TAG, "Got recents " + recentTasks.size());
- updateTasks(recentTasks);
- }
-
- private static ComponentName getActivityForTask(RecentTaskInfo task) {
- // If the task was started from an alias, return the actual activity component that was
- // initially started.
- if (task.origActivity != null) {
- return task.origActivity;
- }
- // Prefer the first activity of the task.
- if (task.baseActivity != null) {
- return task.baseActivity;
- }
- // Then goes the activity that started the task.
- if (task.realActivity != null) {
- return task.realActivity;
- }
- // This should not happen, but fall back to the base intent's activity component name.
- return task.baseIntent.getComponent();
- }
-
- private ComponentName getLaunchComponentForPackage(String packageName, int userId) {
- // This code is based on ApplicationPackageManager.getLaunchIntentForPackage.
- PackageManager packageManager = mContext.getPackageManager();
-
- // First see if the package has an INFO activity; the existence of
- // such an activity is implied to be the desired front-door for the
- // overall package (such as if it has multiple launcher entries).
- Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
- intentToResolve.addCategory(Intent.CATEGORY_INFO);
- intentToResolve.setPackage(packageName);
- List<ResolveInfo> ris = packageManager.queryIntentActivitiesAsUser(
- intentToResolve, 0, userId);
-
- // Otherwise, try to find a main launcher activity.
- if (ris == null || ris.size() <= 0) {
- // reuse the intent instance
- intentToResolve.removeCategory(Intent.CATEGORY_INFO);
- intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
- intentToResolve.setPackage(packageName);
- ris = packageManager.queryIntentActivitiesAsUser(intentToResolve, 0, userId);
- }
- if (ris == null || ris.size() <= 0) {
- Slog.i(TAG, "Failed to build intent for " + packageName);
- return null;
- }
- return new ComponentName(ris.get(0).activityInfo.packageName,
- ris.get(0).activityInfo.name);
- }
-
- private AppInfo taskToAppInfo(RecentTaskInfo task) {
- ComponentName componentName = getActivityForTask(task);
- UserHandle taskUser = new UserHandle(task.userId);
- AppInfo appInfo = new AppInfo(componentName, taskUser);
-
- if (sAppsModel.resolveApp(appInfo) == null) {
- // If task's activity is not launcheable, fall back to a launch component of the
- // task's package.
- ComponentName component = getLaunchComponentForPackage(
- componentName.getPackageName(), task.userId);
-
- if (component == null) {
- return null;
- }
-
- appInfo = new AppInfo(component, taskUser);
- }
-
- return appInfo;
- }
-
- /**
- * A listener that updates the app buttons whenever the recents task stack changes.
- */
- private class TaskStackListener extends ITaskStackListener.Stub {
- @Override
- public void onTaskStackChanged() throws RemoteException {
- // Post the message back to the UI thread.
- post(new Runnable() {
- @Override
- public void run() {
- if (isAttachedToWindow()) {
- updateRecentApps();
- }
- }
- });
- }
-
- @Override
- public void onActivityPinned() {
- }
-
- @Override
- public void onPinnedActivityRestartAttempt() {
- }
- }
-
- @Override
- public void onPinnedAppsChanged() {
- if (getPinnedApps().equals(sAppsModel.getApps())) return;
- recreatePinnedAppButtons();
- updateRecentApps();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
deleted file mode 100644
index 76a9798..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
+++ /dev/null
@@ -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));
- }
-
-}
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
deleted file mode 100644
index f51e8ff..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
+++ /dev/null
@@ -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);
- }
-}