blob: eaa1c20c9b0cc0fc02cf5c03a40b85949ab42dc5 [file] [log] [blame]
/*
* 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.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 |
ActivityManager.RECENT_WITH_EXCLUDED,
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 = getRealActivityForTask(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.
new GetActivityIconTask(mPackageManager, button).execute(getRealActivityForTask(task));
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) {
e.printStackTrace();
}
}
});
}
private static ComponentName getRealActivityForTask(RecentTaskInfo task) {
// Prefer 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;
}
@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();
String packageName = getRealActivityForTask(task).getPackageName();
Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
if (intent == null) {
return false;
}
if (DEBUG) Slog.d(TAG, "Start drag with " + intent);
NavigationBarApps.startAppDrag(icon, new AppInfo (intent.getComponent(), AppInfo.USER_UNSPECIFIED));
return true;
}
}
}