Merge "Implementing app-centric Shelf."
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 580f721..eeae20f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1108,7 +1108,7 @@
* of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
*
* @return Returns a list of RecentTaskInfo records describing each of
- * the recent tasks.
+ * the recent tasks. Most recently activated tasks go first.
*
* @hide
*/
diff --git a/packages/SystemUI/res/drawable-anydpi/nav_app_divider.xml b/packages/SystemUI/res/drawable-anydpi/nav_app_divider.xml
deleted file mode 100644
index b2d988e..0000000
--- a/packages/SystemUI/res/drawable-anydpi/nav_app_divider.xml
+++ /dev/null
@@ -1,24 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="1dp"
- android:height="24dp"
- android:viewportWidth="1"
- android:viewportHeight="24">
- <path
- android:pathData="M0,0 L1,0 L1,24 L0,24 z"
- android:fillColor="#AAFFFFFF"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/navigation_bar_with_apps.xml b/packages/SystemUI/res/layout/navigation_bar_with_apps.xml
index 01c239e..ac95b5e 100644
--- a/packages/SystemUI/res/layout/navigation_bar_with_apps.xml
+++ b/packages/SystemUI/res/layout/navigation_bar_with_apps.xml
@@ -82,21 +82,6 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
/>
-
- <ImageView android:id="@+id/app_divider"
- android:focusable="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="10dp"
- android:src="@drawable/nav_app_divider"
- />
-
- <com.android.systemui.statusbar.phone.NavigationBarRecents
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- />
</LinearLayout>
<FrameLayout
@@ -248,21 +233,6 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
/>
-
- <ImageView android:id="@+id/app_divider"
- android:focusable="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="10dp"
- android:src="@drawable/nav_app_divider"
- />
-
- <com.android.systemui.statusbar.phone.NavigationBarRecents
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- />
</LinearLayout>
<FrameLayout
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java
new file mode 100644
index 0000000..2c6987c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ActivityManager.RecentTaskInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Data associated with an app button.
+ */
+class AppButtonData {
+ public final AppInfo appInfo;
+ public boolean pinned;
+ // Recent tasks for this app, sorted by lastActiveTime, descending.
+ public ArrayList<RecentTaskInfo> tasks;
+
+ public AppButtonData(AppInfo appInfo, boolean pinned) {
+ this.appInfo = appInfo;
+ this.pinned = pinned;
+ }
+
+ /**
+ * Returns true if the button contains no useful information and should be removed.
+ */
+ public boolean isEmpty() {
+ return !pinned && (tasks == null || tasks.isEmpty());
+ }
+
+ public void addTask(RecentTaskInfo task) {
+ if (tasks == null) {
+ tasks = new ArrayList<RecentTaskInfo>();
+ }
+ tasks.add(task);
+ }
+
+ public void clearTasks() {
+ if (tasks != null) {
+ tasks.clear();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
index e34c821..8f0b532 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
@@ -39,4 +39,16 @@
public UserHandle getUser() {
return mUser;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final AppInfo other = (AppInfo) obj;
+ return mComponentName.equals(other.mComponentName) && mUser.equals(other.mUser);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java
index f46d1a6..d2bec7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/GetActivityIconTask.java
@@ -20,6 +20,12 @@
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;
@@ -30,7 +36,7 @@
* 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<AppInfo, Void, Drawable> {
+class GetActivityIconTask extends AsyncTask<AppButtonData, Void, Drawable> {
private final static String TAG = "GetActivityIconTask";
private final PackageManager mPackageManager;
@@ -44,11 +50,12 @@
}
@Override
- protected Drawable doInBackground(AppInfo... params) {
+ protected Drawable doInBackground(AppButtonData... params) {
if (params.length != 1) {
throw new IllegalArgumentException("Expected one parameter");
}
- AppInfo appInfo = params[0];
+ AppButtonData buttonData = params[0];
+ AppInfo appInfo = buttonData.appInfo;
try {
IPackageManager mPM = AppGlobals.getPackageManager();
ActivityInfo ai = mPM.getActivityInfo(
@@ -62,7 +69,37 @@
}
Drawable unbadgedIcon = ai.loadIcon(mPackageManager);
- return mPackageManager.getUserBadgedIcon(unbadgedIcon, appInfo.getUser());
+ 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;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index 5c01f01..1c9b04f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -19,7 +19,11 @@
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;
@@ -29,8 +33,10 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
@@ -51,12 +57,12 @@
/**
* Container for application icons that appear in the navigation bar. Their appearance is similar
- * to the launcher hotseat. Clicking an icon launches 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.
+ * 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.
*/
class NavigationBarApps extends LinearLayout {
- private final static boolean DEBUG = false;
+ public final static boolean DEBUG = false;
private final static String TAG = "NavigationBarApps";
/**
@@ -97,16 +103,9 @@
}
};
- public static NavigationBarAppsModel getModel(Context context) {
- if (sAppsModel == null) {
- sAppsModel = new NavigationBarAppsModel(context);
- }
- return sAppsModel;
- }
-
public NavigationBarApps(Context context, AttributeSet attrs) {
super(context, attrs);
- getModel(context);
+ sAppsModel = new NavigationBarAppsModel(context);
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mLayoutInflater = LayoutInflater.from(context);
@@ -124,19 +123,27 @@
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);
+ }
}
// Monitor that catches events like "app uninstalled".
private class AppPackageMonitor extends PackageMonitor {
@Override
public void onPackageRemoved(String packageName, int uid) {
- postRemoveIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
+ postUnpinIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
super.onPackageRemoved(packageName, uid);
}
@Override
public void onPackageModified(String packageName) {
- postRemoveIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
+ postUnpinIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
super.onPackageModified(packageName);
}
@@ -146,7 +153,7 @@
UserHandle user = new UserHandle(getChangingUserId());
for (String packageName : packages) {
- postRemoveIfUnlauncheable(packageName, user);
+ postUnpinIfUnlauncheable(packageName, user);
}
}
super.onPackagesAvailable(packages);
@@ -158,31 +165,36 @@
UserHandle user = new UserHandle(getChangingUserId());
for (String packageName : packages) {
- postRemoveIfUnlauncheable(packageName, user);
+ postUnpinIfUnlauncheable(packageName, user);
}
}
super.onPackagesUnavailable(packages);
}
}
- private void postRemoveIfUnlauncheable(final String packageName, final UserHandle user) {
+ 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;
- removeIfUnlauncheable(packageName, user);
+ unpinIfUnlauncheable(packageName, user);
}
});
}
- private void removeIfUnlauncheable(String packageName, UserHandle user) {
- // Remove icons for all apps that match a package that perhaps became unlauncheable.
+ 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);
- AppInfo appInfo = (AppInfo)child.getTag();
- if (appInfo == null) continue; // Skip the drag placeholder.
+ 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();
@@ -192,10 +204,15 @@
continue;
}
- removeViewAt(i);
+ appButtonData.pinned = false;
+ appsWereUnpinned = true;
+
+ if (appButtonData.isEmpty()) {
+ removeViewAt(i);
+ }
}
- if (getChildCount() != sAppsModel.getApps().size()) {
- saveApps();
+ if (appsWereUnpinned) {
+ savePinnedApps();
}
}
@@ -215,7 +232,8 @@
parent.setLayoutTransition(transition);
sAppsModel.setCurrentUser(ActivityManager.getCurrentUser());
- recreateAppButtons();
+ recreatePinnedAppButtons();
+ updateRecentApps();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -232,11 +250,23 @@
mAppPackageMonitor.unregister();
}
+ private void addAppButton(AppButtonData appButtonData) {
+ ImageView button = createAppButton(appButtonData);
+ addView(button);
+
+ AppInfo app = appButtonData.appInfo;
+ CharSequence appLabel = getAppLabel(mPackageManager, app.getComponentName());
+ button.setContentDescription(appLabel);
+
+ // Load the icon asynchronously.
+ new GetActivityIconTask(mPackageManager, button).execute(appButtonData);
+ }
+
/**
* 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.
*/
- public void recreateAppButtons() {
+ private void recreatePinnedAppButtons() {
// Remove any existing icon buttons.
removeAllViews();
@@ -244,54 +274,46 @@
int appCount = apps.size();
for (int i = 0; i < appCount; i++) {
AppInfo app = apps.get(i);
- ImageView button = createAppButton(app);
- addView(button);
-
- CharSequence appLabel = getAppLabel(mPackageManager, app.getComponentName());
- button.setContentDescription(appLabel);
-
- // Load the icon asynchronously.
- new GetActivityIconTask(mPackageManager, button).execute(app);
+ addAppButton(new AppButtonData(app, true /* pinned */));
}
}
/**
- * Saves apps stored in app icons into the data model.
+ * Saves pinned apps stored in app icons into the data model.
*/
- private void saveApps() {
+ private void savePinnedApps() {
List<AppInfo> apps = new ArrayList<AppInfo>();
int childCount = getChildCount();
for (int i = 0; i != childCount; ++i) {
View child = getChildAt(i);
- AppInfo appInfo = (AppInfo)child.getTag();
- if (appInfo == null) continue; // Skip the drag placeholder.
- apps.add(appInfo);
+ AppButtonData appButtonData = (AppButtonData)child.getTag();
+ if (appButtonData == null) continue; // Skip the drag placeholder.
+ if(!appButtonData.pinned) continue;
+ apps.add(appButtonData.appInfo);
}
sAppsModel.setApps(apps);
}
/**
- * Creates a new ImageView for a launcher activity, inflated from
- * R.layout.navigation_bar_app_item.
+ * Creates a new ImageView for an app, inflated from R.layout.navigation_bar_app_item.
*/
- private ImageView createAppButton(AppInfo appInfo) {
+ private ImageView createAppButton(AppButtonData appButtonData) {
ImageView button = (ImageView) mLayoutInflater.inflate(
R.layout.navigation_bar_app_item, this, false /* attachToRoot */);
button.setOnClickListener(new AppClickListener());
// TODO: Ripple effect. Use either KeyButtonRipple or the default ripple background.
button.setOnLongClickListener(new AppLongClickListener());
button.setOnDragListener(new AppIconDragListener());
- button.setTag(appInfo);
+ button.setTag(appButtonData);
return button;
}
- // Not shared with NavigationBarRecents because the data model is specific to pinned apps.
private class AppLongClickListener implements View.OnLongClickListener {
@Override
public boolean onLongClick(View v) {
mDragView = (ImageView) v;
- AppInfo app = (AppInfo) v.getTag();
- startAppDrag(mDragView, app);
+ AppButtonData appButtonData = (AppButtonData) v.getTag();
+ startAppDrag(mDragView, appButtonData.appInfo);
return true;
}
}
@@ -443,30 +465,30 @@
return true;
}
+ boolean dragResult = true;
AppInfo appInfo = getAppFromDragEvent(event);
if (appInfo == null) {
// This wasn't a valid drop. Clean up the placeholder.
removePlaceholderDragViewIfNeeded();
- return false;
+ 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 */));
}
-
- // If this was an existing app being dragged then end the drag.
- if (mDragView.getTag() != null) {
- endDrag();
- return true;
- }
-
- // The drop had valid data. Convert the placeholder to a real icon.
- updateApp(mDragView, appInfo);
endDrag();
- return true;
+ 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;
- saveApps();
+ 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. */
@@ -506,9 +528,9 @@
}
/** Updates the app at a given view index. */
- private void updateApp(ImageView button, AppInfo appInfo) {
- button.setTag(appInfo);
- new GetActivityIconTask(mPackageManager, button).execute(appInfo);
+ private void updateApp(ImageView button, AppButtonData appButtonData) {
+ button.setTag(appButtonData);
+ new GetActivityIconTask(mPackageManager, button).execute(appButtonData);
}
/** Removes the empty placeholder view. */
@@ -518,7 +540,6 @@
return;
}
removeView(mDragView);
- endDrag();
}
/** Cleans up at the end of the drag. */
@@ -526,6 +547,7 @@
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;
}
@@ -561,9 +583,7 @@
* A click listener that launches an activity.
*/
private class AppClickListener implements View.OnClickListener {
- @Override
- public void onClick(View v) {
- AppInfo appInfo = (AppInfo)v.getTag();
+ private void launchApp(AppInfo appInfo, View anchor) {
Intent launchIntent = sAppsModel.buildAppLaunchIntent(appInfo);
if (launchIntent == null) {
Toast.makeText(
@@ -576,32 +596,226 @@
// 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();
- v.getBoundsOnScreen(sourceBounds);
+ anchor.getBoundsOnScreen(sourceBounds);
ActivityOptions opts =
- ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight());
+ ActivityOptions.makeScaleUpAnimation(
+ anchor, 0, 0, anchor.getWidth(), anchor.getHeight());
Bundle optsBundle = opts.toBundle();
launchIntent.setSourceBounds(sourceBounds);
mContext.startActivityAsUser(launchIntent, optsBundle, appInfo.getUser());
}
+
+ private void activateLatestTask(List<RecentTaskInfo> tasks) {
+ // 'tasks' is guaranteed to be non-empty.
+ int latestTaskPersistentId = tasks.get(0).persistentId;
+ // Launch or bring the activity to front.
+ IActivityManager manager = ActivityManagerNative.getDefault();
+ try {
+ manager.startActivityFromRecents(latestTaskPersistentId, 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);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ AppButtonData appButtonData = (AppButtonData)v.getTag();
+
+ if (appButtonData.tasks == null || appButtonData.tasks.size() == 0) {
+ launchApp(appButtonData.appInfo, v);
+ } else {
+ activateLatestTask(appButtonData.tasks);
+ }
+ }
}
private void onUserSwitched(int currentUserId) {
sAppsModel.setCurrentUser(currentUserId);
- recreateAppButtons();
+ 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);
- AppInfo appInfo = (AppInfo)view.getTag();
- if (appInfo == null) return; // Skip the drag placeholder.
- if (!appInfo.getUser().equals(removedProfile)) continue;
+ AppButtonData appButtonData = (AppButtonData)view.getTag();
+ if (appButtonData == null) return; // Skip the drag placeholder.
+ if (!appButtonData.pinned) continue;
+ if (!appButtonData.appInfo.getUser().equals(removedProfile)) continue;
- removeViewAt(i);
+ appButtonData.pinned = false;
+ itemsWereUnpinned = true;
+ if (appButtonData.isEmpty()) {
+ removeViewAt(i);
+ }
}
- if (getChildCount() != sAppsModel.getApps().size()) {
- saveApps();
+ 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.e(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.buildAppLaunchIntent(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();
+ }
+ }
+ });
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java
deleted file mode 100644
index 7ff56ba..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java
+++ /dev/null
@@ -1,294 +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.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo;
-import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
-import android.app.ITaskStackListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-
-import java.util.List;
-
-/**
- * Recent task icons appearing in the navigation bar. Touching an icon brings the activity to the
- * front. The tag for each icon's View contains the RecentTaskInfo.
- */
-class NavigationBarRecents extends LinearLayout {
- private final static boolean DEBUG = false;
- private final static String TAG = "NavigationBarRecents";
-
- // Maximum number of icons to show.
- // TODO: Implement an overflow UI so the shelf can display an unlimited number of recents.
- private final static int MAX_RECENTS = 10;
-
- private final ActivityManager mActivityManager;
- private final PackageManager mPackageManager;
- private final LayoutInflater mLayoutInflater;
- // All icons share the same long-click listener.
- private final AppLongClickListener mAppLongClickListener;
- private final TaskStackListenerImpl mTaskStackListener;
-
- public NavigationBarRecents(Context context, AttributeSet attrs) {
- super(context, attrs);
- mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mPackageManager = getContext().getPackageManager();
- mLayoutInflater = LayoutInflater.from(context);
- mAppLongClickListener = new AppLongClickListener(context);
-
- // Listen for task stack changes and refresh when they happen. Update notifications happen
- // on an IPC thread, so use Handler to handle the message on the main thread.
- // TODO: This has too much latency. It only adds the icon when app launch is completed
- // and the launch animation is done playing. This class should add the icon immediately
- // when the launch starts.
- Handler handler = new Handler();
- mTaskStackListener = new TaskStackListenerImpl(handler);
- IActivityManager iam = ActivityManagerNative.getDefault();
- try {
- iam.registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- private void updateRecentApps() {
- // TODO: Should this be getRunningTasks?
- // TODO: Query other UserHandles?
- List<RecentTaskInfo> recentTasks = mActivityManager.getRecentTasksForUser(
- MAX_RECENTS,
- 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());
- removeMissingRecents(recentTasks);
- addNewRecents(recentTasks);
- }
-
- // Removes any icons that disappeared from recents.
- private void removeMissingRecents(List<RecentTaskInfo> recentTasks) {
- // Build a set of the new task ids.
- SparseBooleanArray newTaskIds = new SparseBooleanArray();
- for (RecentTaskInfo task : recentTasks) {
- newTaskIds.put(task.persistentId, true);
- }
-
- // Iterate through the currently displayed tasks. If they no longer exist in recents,
- // remove them.
- int i = 0;
- while (i < getChildCount()) {
- RecentTaskInfo currentTask = (RecentTaskInfo) getChildAt(i).getTag();
- if (!newTaskIds.get(currentTask.persistentId)) {
- if (DEBUG) Slog.d(TAG, "Removing " + currentTask.baseIntent);
- removeViewAt(i);
- } else {
- i++;
- }
- }
- }
-
- // Adds new tasks at the end of the icon list.
- private void addNewRecents(List<RecentTaskInfo> recentTasks) {
- // Build a set of the current task ids.
- SparseBooleanArray currentTaskIds = new SparseBooleanArray();
- for (int i = 0; i < getChildCount(); i++) {
- RecentTaskInfo task = (RecentTaskInfo) getChildAt(i).getTag();
- currentTaskIds.put(task.persistentId, true);
- }
-
- // Add tasks that don't currently exist to the end of the view.
- for (RecentTaskInfo task : recentTasks) {
- // Don't overflow the list.
- if (getChildCount() >= MAX_RECENTS) {
- return;
- }
- // Don't add tasks that are already being shown.
- if (currentTaskIds.get(task.persistentId)) {
- continue;
- }
- addRecentAppButton(task);
- }
- }
-
- // Adds an icon at the end of the shelf.
- private void addRecentAppButton(RecentTaskInfo task) {
- if (DEBUG) Slog.d(TAG, "Adding " + task.baseIntent);
-
- // Add an icon for the task.
- ImageView button = (ImageView) mLayoutInflater.inflate(
- R.layout.navigation_bar_app_item, this, false /* attachToRoot */);
- button.setOnLongClickListener(mAppLongClickListener);
- addView(button);
-
- ComponentName activityName = getActivityForTask(task);
- CharSequence appLabel = NavigationBarApps.getAppLabel(mPackageManager, activityName);
- button.setContentDescription(appLabel);
-
- // Use the View's tag to store metadata for drag and drop.
- button.setTag(task);
-
- button.setVisibility(View.VISIBLE);
- // Load the activity icon on a background thread.
- AppInfo app = new AppInfo(activityName, new UserHandle(task.userId));
- new GetActivityIconTask(mPackageManager, button).execute(app);
-
- final int taskPersistentId = task.persistentId;
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Launch or bring the activity to front.
- IActivityManager manager = ActivityManagerNative.getDefault();
- try {
- manager.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);
- }
- }
- });
- }
-
- 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();
- }
-
- /**
- * A listener that updates the app buttons whenever the recents task stack changes.
- * NOTE: This is not the right way to do this.
- */
- private class TaskStackListenerImpl extends ITaskStackListener.Stub {
- // Handler to post messages to the UI thread.
- private Handler mHandler;
-
- public TaskStackListenerImpl(Handler handler) {
- mHandler = handler;
- }
-
- @Override
- public void onTaskStackChanged() throws RemoteException {
- // Post the message back to the UI thread.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- updateRecentApps();
- }
- });
- }
- }
-
- /** Starts a drag on long-click on an app icon. */
- private static class AppLongClickListener implements View.OnLongClickListener {
- private final Context mContext;
-
- public AppLongClickListener(Context context) {
- mContext = context;
- }
-
- 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.e(TAG, "Failed to build intent for " + packageName);
- return null;
- }
- return new ComponentName(ris.get(0).activityInfo.packageName,
- ris.get(0).activityInfo.name);
- }
-
- @Override
- public boolean onLongClick(View v) {
- ImageView icon = (ImageView) v;
-
- // The drag will go to the pinned section, which wants to launch the main activity
- // for the task's package.
- RecentTaskInfo task = (RecentTaskInfo) v.getTag();
- ComponentName componentName = getActivityForTask(task);
- UserHandle taskUser = new UserHandle(task.userId);
- AppInfo appInfo = new AppInfo(componentName, taskUser);
-
- if (NavigationBarApps.getModel(mContext).buildAppLaunchIntent(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 false;
- }
-
- appInfo = new AppInfo(component, taskUser);
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Start drag with " + appInfo.getComponentName().flattenToString());
- }
-
- NavigationBarApps.startAppDrag(icon, appInfo);
- return true;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 7de7a7b..f37383a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -492,14 +492,6 @@
}
updateTaskSwitchHelper();
-
- // If using the app shelf, synchronize the current icons to the data model.
- NavigationBarApps apps =
- (NavigationBarApps) mCurrentView.findViewById(R.id.navigation_bar_apps);
- if (apps != null) {
- apps.recreateAppButtons();
- }
-
setNavigationIconHints(mNavigationIconHints, true);
}