diff options
9 files changed, 399 insertions, 436 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 580f721ff75b..eeae20fa1ccf 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1108,7 +1108,7 @@ public class ActivityManager { * 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 b2d988e5e54b..000000000000 --- 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 01c239e0f0a4..ac95b5eb6530 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 000000000000..2c6987c5fe42 --- /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 e34c821631be..8f0b532eabee 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 @@ class AppInfo { 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 f46d1a6067ff..d2bec7c3be0d 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.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; @@ -30,7 +36,7 @@ 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<AppInfo, Void, Drawable> { +class GetActivityIconTask extends AsyncTask<AppButtonData, Void, Drawable> { private final static String TAG = "GetActivityIconTask"; private final PackageManager mPackageManager; @@ -44,11 +50,12 @@ class GetActivityIconTask extends AsyncTask<AppInfo, Void, Drawable> { } @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 @@ class GetActivityIconTask extends AsyncTask<AppInfo, Void, Drawable> { } 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 5c01f013d39f..1c9b04f5b4bd 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 @@ package com.android.systemui.statusbar.phone; 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.Intent; 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 @@ 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 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 @@ class NavigationBarApps extends LinearLayout { } }; - 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 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { UserHandle user = new UserHandle(getChangingUserId()); for (String packageName : packages) { - postRemoveIfUnlauncheable(packageName, user); + postUnpinIfUnlauncheable(packageName, user); } } super.onPackagesAvailable(packages); @@ -158,31 +165,36 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { parent.setLayoutTransition(transition); sAppsModel.setCurrentUser(ActivityManager.getCurrentUser()); - recreateAppButtons(); + recreatePinnedAppButtons(); + updateRecentApps(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); @@ -232,11 +250,23 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { } /** 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 @@ class NavigationBarApps extends LinearLayout { return; } removeView(mDragView); - endDrag(); } /** Cleans up at the end of the drag. */ @@ -526,6 +547,7 @@ class NavigationBarApps extends LinearLayout { 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 @@ class NavigationBarApps extends LinearLayout { * 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 @@ class NavigationBarApps extends LinearLayout { // 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; + + appButtonData.pinned = false; + itemsWereUnpinned = true; + if (appButtonData.isEmpty()) { + removeViewAt(i); + } + } + if (itemsWereUnpinned) { + savePinnedApps(); + } + } - removeViewAt(i); + /** + * 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; } - if (getChildCount() != sAppsModel.getApps().size()) { - saveApps(); + // 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 7ff56bab1db4..000000000000 --- 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 7de7a7b58c7a..f37383afb3cd 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 @@ public class NavigationBarView extends LinearLayout { } 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); } |