diff options
28 files changed, 1170 insertions, 318 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 379fe11..97b9f4c 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -766,6 +766,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_FOCUSED_STACK_ID_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int focusedStackId = getFocusedStackId(); + reply.writeNoException(); + reply.writeInt(focusedStackId); + return true; + } + case REGISTER_TASK_STACK_LISTENER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -3290,6 +3298,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } @Override + public int getFocusedStackId() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_FOCUSED_STACK_ID_TRANSACTION, data, reply, 0); + reply.readException(); + int focusedStackId = reply.readInt(); + data.recycle(); + reply.recycle(); + return focusedStackId; + } + @Override public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl index 52884f7..ff1175f 100644 --- a/core/java/android/app/IActivityContainer.aidl +++ b/core/java/android/app/IActivityContainer.aidl @@ -32,6 +32,7 @@ interface IActivityContainer { void checkEmbeddedAllowed(in Intent intent); void checkEmbeddedAllowedIntentSender(in IIntentSender intentSender); int getDisplayId(); + int getStackId(); boolean injectEvent(in InputEvent event); void release(); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index f152c6f..efc4543 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -139,6 +139,7 @@ public interface IActivityManager extends IInterface { public StackInfo getStackInfo(int stackId) throws RemoteException; public boolean isInHomeStack(int taskId) throws RemoteException; public void setFocusedStack(int stackId) throws RemoteException; + public int getFocusedStackId() throws RemoteException; public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, @@ -800,4 +801,5 @@ public interface IActivityManager extends IInterface { // Start of M transactions int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+280; int CREATE_STACK_ON_DISPLAY = IBinder.FIRST_CALL_TRANSACTION+281; + int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282; } diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml index 8f367a6..26523f9 100644 --- a/packages/SystemUI/res/layout/recents.xml +++ b/packages/SystemUI/res/layout/recents.xml @@ -15,7 +15,7 @@ --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" + android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Status Bar Scrim View --> <ImageView @@ -29,9 +29,16 @@ <!-- Recents View --> <com.android.systemui.recents.views.RecentsView android:id="@+id/recents_view" - android:layout_width="match_parent" + android:layout_width="match_parent" android:layout_height="match_parent" - android:focusable="true" /> + android:focusable="true"> + <!-- MultiStack Debug View --> + <ViewStub android:id="@+id/multistack_debug_view_stub" + android:layout="@layout/recents_multistack_debug" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left|bottom" /> + </com.android.systemui.recents.views.RecentsView> <!-- Empty View --> <ViewStub android:id="@+id/empty_view_stub" diff --git a/packages/SystemUI/res/layout/recents_multistack_debug.xml b/packages/SystemUI/res/layout/recents_multistack_debug.xml new file mode 100644 index 0000000..6524a54 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_multistack_debug.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left|bottom" + android:orientation="vertical"> + <Button + android:id="@+id/add_stack" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:padding="8dp" + android:textSize="20sp" + android:textColor="#ffffffff" + android:text="@string/recents_multistack_add_stack" + android:fontFamily="sans-serif" + android:background="#000000" + android:alpha="0.5" /> + <Button + android:id="@+id/resize_stack" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:padding="8dp" + android:textSize="20sp" + android:textColor="#ffffffff" + android:text="@string/recents_multistack_resize_stack" + android:fontFamily="sans-serif" + android:background="#000000" + android:alpha="0.5" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml b/packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml new file mode 100644 index 0000000..36e54a0 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <EditText + android:id="@+id/inset_left" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Left" + android:singleLine="true" + android:imeOptions="actionNext" + android:inputType="number" + android:selectAllOnFocus="true" /> + <EditText + android:id="@+id/inset_top" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Top" + android:singleLine="true" + android:imeOptions="actionNext" + android:inputType="number" + android:selectAllOnFocus="true" /> + <EditText + android:id="@+id/inset_right" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Right" + android:singleLine="true" + android:imeOptions="actionNext" + android:inputType="number" + android:selectAllOnFocus="true" /> + <EditText + android:id="@+id/inset_bottom" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Bottom" + android:singleLine="true" + android:imeOptions="actionDone" + android:inputType="number" + android:selectAllOnFocus="true" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index f1d8ad0..53047a3 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -43,6 +43,16 @@ android:ellipsize="marquee" android:fadingEdge="horizontal" /> <com.android.systemui.recents.views.FixedSizeImageView + android:id="@+id/move_task" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="52dp" + android:layout_gravity="center_vertical|end" + android:padding="12dp" + android:background="@drawable/recents_button_bg" + android:src="@drawable/star" + android:visibility="gone" /> + <com.android.systemui.recents.views.FixedSizeImageView android:id="@+id/dismiss_task" android:layout_width="48dp" android:layout_height="48dp" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c977db9..40bf13f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -672,6 +672,19 @@ <!-- Recents: Dismiss all button. [CHAR LIMIT=NONE] --> <string name="recents_dismiss_all_message">Dismiss all applications</string> + <!-- Recents: MultiStack add stack button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack">+</string> + <!-- Recents: MultiStack remove stack button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_remove_stack">-</string> + <!-- Recents: MultiStack resize stack button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_resize_stack">[]</string> + <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string> + <!-- Recents: MultiStack add stack split vertical radio button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack_dialog_split_vertical">Split Vertical</string> + <!-- Recents: MultiStack add stack split custom radio button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack_dialog_split_custom">Split Custom</string> + <!-- Expanded Status Bar Header: Battery Charged [CHAR LIMIT=40] --> <string name="expanded_header_battery_charged">Charged</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index f2b4a69..bf19b8d 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -20,7 +20,7 @@ <item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item> </style> - <style name="RecentsTheme" parent="@android:style/Theme"> + <style name="RecentsTheme" parent="@android:style/Theme.Material.Light"> <!-- NoTitle --> <item name="android:windowNoTitle">true</item> <!-- Misc --> diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index cfd6b40..192acc6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -39,6 +39,8 @@ public class Constants { public static final boolean EnableSearchLayout = true; // Enables the thumbnail alpha on the front-most task public static final boolean EnableThumbnailAlphaOnFrontmost = false; + // Enables all system stacks to show up in the same recents stack + public static final boolean EnableMultiStackToSingleStack = true; // This disables the bitmap and icon caches public static final boolean DisableBackgroundCache = false; // Enables the simulated task affiliations diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 3c75aac..9dd82fc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -24,7 +24,6 @@ import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -56,7 +55,6 @@ import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; import java.util.ArrayList; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -112,6 +110,9 @@ public class Recents extends SystemUI /** Preloads the next task */ public void run() { + // Temporarily skip this if multi stack is enabled + if (mConfig.multiStackEnabled) return; + RecentsConfiguration config = RecentsConfiguration.getInstance(); if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); @@ -362,13 +363,21 @@ public class Recents extends SystemUI } void showRelativeAffiliatedTask(boolean showNextTask) { + // Return early if there is no focused stack + int focusedStackId = mSystemServicesProxy.getFocusedStack(); + TaskStack focusedStack = null; RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); loader.preloadTasks(plan, true /* isTopTaskHome */); - TaskStack stack = plan.getTaskStack(); + if (mConfig.multiStackEnabled) { + if (focusedStackId < 0) return; + focusedStack = plan.getTaskStack(focusedStackId); + } else { + focusedStack = plan.getAllTaskStacks().get(0); + } - // Return early if there are no tasks - if (stack.getTaskCount() == 0) return; + // Return early if there are no tasks in the focused stack + if (focusedStack.getTaskCount() == 0) return; ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask(); // Return early if there is no running task (can't determine affiliated tasks in this case) @@ -377,7 +386,7 @@ public class Recents extends SystemUI if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return; // Find the task in the recents list - ArrayList<Task> tasks = stack.getTasks(); + ArrayList<Task> tasks = focusedStack.getTasks(); Task toTask = null; ActivityOptions launchOpts = null; int taskCount = tasks.size(); @@ -399,7 +408,7 @@ public class Recents extends SystemUI R.anim.recents_launch_prev_affiliated_task_source); } if (toTaskKey != null) { - toTask = stack.findTaskWithId(toTaskKey.id); + toTask = focusedStack.findTaskWithId(toTaskKey.id); } numAffiliatedTasks = group.getTaskCount(); break; @@ -473,8 +482,9 @@ public class Recents extends SystemUI // Reload the widget id before we get the task stack bounds reloadSearchBarAppWidget(mContext, mSystemServicesProxy); } - mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, - (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds); + mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(), + mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), + mTaskStackBounds); if (mConfig.isLandscape && mConfig.hasTransposedNavBar) { mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0); } else { @@ -653,8 +663,25 @@ public class Recents extends SystemUI // Create a new load plan if onPreloadRecents() was never triggered sInstanceLoadPlan = loader.createLoadPlan(mContext); } + + // Temporarily skip the transition (use a dummy fade) if multi stack is enabled. + // For multi-stack we need to figure out where each of the tasks are going. + if (mConfig.multiStackEnabled) { + loader.preloadTasks(sInstanceLoadPlan, true); + ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); + TaskStack stack = stacks.get(0); + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true); + TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = + mDummyStackView.computeStackVisibilityReport(); + ActivityOptions opts = getUnknownTransitionActivityOptions(); + startAlternateRecentsActivity(topTask, opts, true /* fromHome */, + false /* fromSearchHome */, false /* fromThumbnail */, stackVr); + return; + } + loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); - TaskStack stack = sInstanceLoadPlan.getTaskStack(); + ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); + TaskStack stack = stacks.get(0); // Prepare the dummy stack for the transition mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 1833e09..b1ac733 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -18,6 +18,7 @@ package com.android.systemui.recents; import android.app.Activity; import android.app.ActivityOptions; +import android.app.Dialog; import android.app.SearchManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; @@ -43,7 +44,6 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.DebugOverlayView; @@ -75,6 +75,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView View mEmptyView; DebugOverlayView mDebugOverlay; + // MultiStack debug + RecentsMultiStackDialog mMultiStackDebugDialog; + // Search AppWidget RecentsAppWidgetHost mAppWidgetHost; AppWidgetProviderInfo mSearchAppWidgetInfo; @@ -190,7 +193,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } // Start loading tasks according to the load plan - if (plan.getTaskStack() == null) { + ArrayList<TaskStack> stacks = plan.getAllTaskStacks(); + if (stacks.size() == 0) { loader.preloadTasks(plan, mConfig.launchedFromHome); } RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); @@ -199,9 +203,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails; loader.loadTasks(this, plan, loadOpts); - SpaceNode root = plan.getSpaceNode(); - ArrayList<TaskStack> stacks = root.getStacks(); - boolean hasTasks = root.hasTasks(); + boolean hasTasks = plan.hasTasks(); if (hasTasks) { mRecentsView.setTaskStacks(stacks); } @@ -591,6 +593,40 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } } + + /**** RecentsMultiStackDialog ****/ + + private RecentsMultiStackDialog getMultiStackDebugDialog() { + if (mMultiStackDebugDialog == null) { + mMultiStackDebugDialog = new RecentsMultiStackDialog(getFragmentManager()); + } + return mMultiStackDebugDialog; + } + + @Override + public void onMultiStackAddStack() { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showAddStackDialog(); + } + + @Override + public void onMultiStackResizeStack() { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showResizeStackDialog(); + } + + @Override + public void onMultiStackRemoveStack() { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showRemoveStackDialog(); + } + + @Override + public void onMultiStackMoveTask(Task t) { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showMoveTaskDialog(t); + } + /**** RecentsView.RecentsViewCallbacks Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index bc10a48..1736c77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -134,6 +134,7 @@ public class RecentsConfiguration { public boolean fakeShadows; /** Dev options and global settings */ + public boolean multiStackEnabled; public boolean lockToAppEnabled; public boolean developerOptionsEnabled; public boolean debugModeEnabled; @@ -294,6 +295,7 @@ public class RecentsConfiguration { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0; lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0; + multiStackEnabled = "1".equals(ssp.getSystemProperty("overview.enableMultiStack")); } /** Called when the configuration has changed, and we want to reset any configuration specific @@ -335,8 +337,8 @@ public class RecentsConfiguration { * Returns the task stack bounds in the current orientation. These bounds do not account for * the system insets. */ - public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset, - Rect taskStackBounds) { + public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset, + int rightInset, Rect taskStackBounds) { Rect searchBarBounds = new Rect(); getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds); if (isLandscape && hasTransposedSearchBar) { @@ -353,7 +355,7 @@ public class RecentsConfiguration { * the system insets. */ public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset, - Rect searchBarSpaceBounds) { + Rect searchBarSpaceBounds) { // Return empty rects if search is not enabled int searchBarSize = searchBarSpaceHeightPx; if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java new file mode 100644 index 0000000..fdf9d39 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java @@ -0,0 +1,339 @@ +/* + * 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.recents; + +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.graphics.Rect; +import android.os.Bundle; +import android.util.MutableInt; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; +import com.android.systemui.R; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.Task; + +import java.util.List; + +/** + * A helper for the dialogs that show when multistack debugging is on. + */ +public class RecentsMultiStackDialog extends DialogFragment { + + static final String TAG = "RecentsMultiStackDialog"; + + public static final int ADD_STACK_DIALOG = 0; + public static final int ADD_STACK_PICK_APP_DIALOG = 1; + public static final int REMOVE_STACK_DIALOG = 2; + public static final int RESIZE_STACK_DIALOG = 3; + public static final int RESIZE_STACK_PICK_STACK_DIALOG = 4; + public static final int MOVE_TASK_DIALOG = 5; + + FragmentManager mFragmentManager; + int mCurrentDialogType; + MutableInt mTargetStackIndex = new MutableInt(0); + Task mTaskToMove; + SparseArray<ActivityManager.StackInfo> mStacks; + List<ResolveInfo> mLauncherActivities; + Rect mAddStackRect; + Intent mAddStackIntent; + + View mAddStackDialogContent; + + public RecentsMultiStackDialog() {} + + public RecentsMultiStackDialog(FragmentManager mgr) { + mFragmentManager = mgr; + } + + /** Shows the add-stack dialog. */ + void showAddStackDialog() { + mCurrentDialogType = ADD_STACK_DIALOG; + show(mFragmentManager, TAG); + } + + /** Creates a new add-stack dialog. */ + private void createAddStackDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + builder.setTitle("Add Stack - Enter new dimensions"); + mAddStackDialogContent = + inflater.inflate(R.layout.recents_multistack_stack_size_dialog, null, false); + Rect windowRect = ssp.getWindowRect(); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_left, windowRect.left); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_top, windowRect.top); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_right, windowRect.right); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_bottom, windowRect.bottom); + builder.setView(mAddStackDialogContent); + builder.setPositiveButton("Add Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int left = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_left); + int top = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_top); + int right = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_right); + int bottom = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_bottom); + if (bottom <= top || right <= left) { + Toast.makeText(context, "Invalid dimensions", Toast.LENGTH_SHORT).show(); + dismiss(); + return; + } + + // Prompt the user for the app to start + dismiss(); + mCurrentDialogType = ADD_STACK_PICK_APP_DIALOG; + mAddStackRect = new Rect(left, top, right, bottom); + show(mFragmentManager, TAG); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Creates a new add-stack pick-app dialog. */ + private void createAddStackPickAppDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + mLauncherActivities = ssp.getLauncherApps(); + mAddStackIntent = null; + int activityCount = mLauncherActivities.size(); + CharSequence[] activityNames = new CharSequence[activityCount]; + for (int i = 0; i < activityCount; i++) { + activityNames[i] = ssp.getActivityLabel(mLauncherActivities.get(i).activityInfo); + } + builder.setTitle("Add Stack - Pick starting app"); + builder.setSingleChoiceItems(activityNames, -1, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ActivityInfo ai = mLauncherActivities.get(which).activityInfo; + mAddStackIntent = new Intent(Intent.ACTION_MAIN); + mAddStackIntent.addCategory(Intent.CATEGORY_LAUNCHER); + mAddStackIntent.setComponent(new ComponentName(ai.packageName, ai.name)); + } + }); + builder.setPositiveButton("Add Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Display 0 = default display + ssp.createNewStack(0, mAddStackRect, mAddStackIntent); + } + }); + builder.setNegativeButton("Skip", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Display 0 = default display + ssp.createNewStack(0, mAddStackRect, null); + } + }); + } + + /** Shows the resize-stack dialog. */ + void showResizeStackDialog() { + mCurrentDialogType = RESIZE_STACK_PICK_STACK_DIALOG; + show(mFragmentManager, TAG); + } + + /** Creates a new resize-stack pick-stack dialog. */ + private void createResizeStackPickStackDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + mStacks = ssp.getAllStackInfos(); + mTargetStackIndex.value = -1; + CharSequence[] stackNames = getAllStacksDescriptions(mStacks, -1, null); + builder.setTitle("Resize Stack - Pick stack"); + builder.setSingleChoiceItems(stackNames, mTargetStackIndex.value, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mTargetStackIndex.value = which; + } + }); + builder.setPositiveButton("Resize Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (mTargetStackIndex.value != -1) { + // Prompt the user for the new dimensions + dismiss(); + mCurrentDialogType = RESIZE_STACK_DIALOG; + show(mFragmentManager, TAG); + } + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Creates a new resize-stack dialog. */ + private void createResizeStackDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + builder.setTitle("Resize Stack - Enter new dimensions"); + final ActivityManager.StackInfo stack = mStacks.valueAt(mTargetStackIndex.value); + mAddStackDialogContent = + inflater.inflate(R.layout.recents_multistack_stack_size_dialog, null, false); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_left, stack.bounds.left); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_top, stack.bounds.top); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_right, stack.bounds.right); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_bottom, stack.bounds.bottom); + builder.setView(mAddStackDialogContent); + builder.setPositiveButton("Resize Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int left = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_left); + int top = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_top); + int right = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_right); + int bottom = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_bottom); + if (bottom <= top || right <= left) { + Toast.makeText(context, "Invalid dimensions", Toast.LENGTH_SHORT).show(); + dismiss(); + return; + } + ssp.resizeStack(stack.stackId, new Rect(left, top, right, bottom)); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Shows the remove-stack dialog. */ + void showRemoveStackDialog() { + mCurrentDialogType = REMOVE_STACK_DIALOG; + show(mFragmentManager, TAG); + } + + /** Shows the move-task dialog. */ + void showMoveTaskDialog(Task task) { + mCurrentDialogType = MOVE_TASK_DIALOG; + mTaskToMove = task; + show(mFragmentManager, TAG); + } + + /** Creates a new move-stack dialog. */ + private void createMoveTaskDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + mStacks = ssp.getAllStackInfos(); + mTargetStackIndex.value = -1; + CharSequence[] stackNames = getAllStacksDescriptions(mStacks, mTaskToMove.key.stackId, + mTargetStackIndex); + builder.setTitle("Move Task to Stack"); + builder.setSingleChoiceItems(stackNames, mTargetStackIndex.value, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mTargetStackIndex.value = which; + } + }); + builder.setPositiveButton("Move Task", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (mTargetStackIndex.value != -1) { + ActivityManager.StackInfo toStack = mStacks.valueAt(mTargetStackIndex.value); + if (toStack.stackId != mTaskToMove.key.stackId) { + ssp.moveTaskToStack(mTaskToMove.key.id, toStack.stackId, true); + mTaskToMove.setStackId(toStack.stackId); + } + } + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Helper to get an integer value from an edit text. */ + private int getDimensionFromEditText(View container, int id) { + String text = ((EditText) container.findViewById(id)).getText().toString(); + if (text.trim().length() != 0) { + return Integer.parseInt(text.trim()); + } + return 0; + } + + /** Helper to set an integer value to an edit text. */ + private void setDimensionInEditText(View container, int id, int value) { + ((EditText) container.findViewById(id)).setText("" + value); + } + + /** Gets a list of all the stacks. */ + private CharSequence[] getAllStacksDescriptions(SparseArray<ActivityManager.StackInfo> stacks, + int targetStackId, MutableInt indexOfTargetStackId) { + int stackCount = stacks.size(); + CharSequence[] stackNames = new CharSequence[stackCount]; + for (int i = 0; i < stackCount; i++) { + ActivityManager.StackInfo stack = stacks.valueAt(i); + Rect b = stack.bounds; + String desc = "Stack " + stack.stackId + " / " + + "" + (stack.taskIds.length > 0 ? stack.taskIds.length : "No") + " tasks\n" + + "(" + b.left + ", " + b.top + ")-(" + b.right + ", " + b.bottom + ")\n"; + stackNames[i] = desc; + if (targetStackId != -1 && stack.stackId == targetStackId) { + indexOfTargetStackId.value = i; + } + } + return stackNames; + } + + @Override + public Dialog onCreateDialog(Bundle args) { + final Context context = this.getActivity(); + final SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + LayoutInflater inflater = getActivity().getLayoutInflater(); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + switch(mCurrentDialogType) { + case ADD_STACK_DIALOG: + createAddStackDialog(context, inflater, builder, ssp); + break; + case ADD_STACK_PICK_APP_DIALOG: + createAddStackPickAppDialog(context, inflater, builder, ssp); + break; + case MOVE_TASK_DIALOG: + createMoveTaskDialog(context, inflater, builder, ssp); + break; + case RESIZE_STACK_PICK_STACK_DIALOG: + createResizeStackPickStackDialog(context, inflater, builder, ssp); + break; + case RESIZE_STACK_DIALOG: + createResizeStackDialog(context, inflater, builder, ssp); + break; + } + return builder.create(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 237d4f0..72040fe 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.AppGlobals; +import android.app.IActivityContainer; import android.app.IActivityManager; import android.app.ITaskStackListener; import android.app.SearchManager; @@ -49,10 +50,12 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; @@ -64,6 +67,8 @@ import com.android.systemui.recents.Recents; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Random; @@ -228,6 +233,23 @@ public class SystemServicesProxy { return null; } + /** Returns a list of all the launcher apps sorted by name. */ + public List<ResolveInfo> getLauncherApps() { + if (mPm == null) return new ArrayList<ResolveInfo>(); + + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> activities = mPm.queryIntentActivities(mainIntent, 0 /* flags */); + Collections.sort(activities, new Comparator<ResolveInfo>() { + @Override + public int compare(ResolveInfo o1, ResolveInfo o2) { + return getActivityLabel(o1.activityInfo).compareTo( + getActivityLabel(o2.activityInfo)); + } + }); + return activities; + } + /** Returns whether the recents is currently running */ public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) { @@ -250,6 +272,64 @@ public class SystemServicesProxy { return false; } + /** Create a new stack. */ + public void createNewStack(int displayId, Rect bounds, Intent activity) { + try { + IActivityContainer container = mIam.createStackOnDisplay(displayId); + if (container != null) { + // Resize the stack + resizeStack(container.getStackId(), bounds); + // Start the new activity on that stack + container.startActivity(activity); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** Resizes a stack. */ + public void resizeStack(int stackId, Rect bounds) { + if (mIam == null) return; + + try { + mIam.resizeStack(stackId, bounds); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** Returns the stack info for all stacks. */ + public SparseArray<ActivityManager.StackInfo> getAllStackInfos() { + if (mIam == null) return new SparseArray<ActivityManager.StackInfo>(); + + try { + SparseArray<ActivityManager.StackInfo> stacks = + new SparseArray<ActivityManager.StackInfo>(); + List<ActivityManager.StackInfo> infos = mIam.getAllStackInfos(); + int stackCount = infos.size(); + for (int i = 0; i < stackCount; i++) { + ActivityManager.StackInfo info = infos.get(i); + stacks.put(info.stackId, info); + } + return stacks; + } catch (RemoteException e) { + e.printStackTrace(); + return new SparseArray<ActivityManager.StackInfo>(); + } + } + + /** Returns the focused stack id. */ + public int getFocusedStack() { + if (mIam == null) return -1; + + try { + return mIam.getFocusedStackId(); + } catch (RemoteException e) { + e.printStackTrace(); + return -1; + } + } + /** Returns whether the specified task is in the home stack */ public boolean isInHomeStack(int taskId) { if (mAm == null) return false; @@ -313,7 +393,7 @@ public class SystemServicesProxy { return thumbnail; } - /** Moves a task to the front with the specified activity options */ + /** Moves a task to the front with the specified activity options. */ public void moveTaskToFront(int taskId, ActivityOptions opts) { if (mAm == null) return; if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; @@ -326,6 +406,18 @@ public class SystemServicesProxy { } } + /** Moves a task to another stack. */ + public void moveTaskToStack(int taskId, int stackId, boolean toTop) { + if (mIam == null) return; + if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; + + try { + mIam.moveTaskToStack(taskId, stackId, toTop); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + /** Removes the task */ public void removeTask(int taskId) { if (mAm == null) return; @@ -524,6 +616,13 @@ public class SystemServicesProxy { } /** + * Returns a system property. + */ + public String getSystemProperty(String key) { + return SystemProperties.get(key); + } + + /** * Returns the window rect. */ public Rect getWindowRect() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 3d25c80..788e473 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -20,9 +20,12 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.util.Log; +import android.util.SparseArray; +import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -60,7 +63,7 @@ public class RecentsTaskLoadPlan { SystemServicesProxy mSystemServicesProxy; List<ActivityManager.RecentTaskInfo> mRawTasks; - TaskStack mStack; + SparseArray<TaskStack> mStacks = new SparseArray<>(); HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache = new HashMap<Task.ComponentNameKey, ActivityInfoHandle>(); @@ -90,21 +93,28 @@ public class RecentsTaskLoadPlan { synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { if (DEBUG) Log.d(TAG, "preloadPlan"); + // This activity info cache will be used for both preloadPlan() and executePlan() mActivityInfoCache.clear(); - mStack = new TaskStack(); + + // TODO (multi-display): Currently assume the primary display + Rect displayBounds = mSystemServicesProxy.getWindowRect(); Resources res = mContext.getResources(); - ArrayList<Task> loadedTasks = new ArrayList<Task>(); + SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>(); if (mRawTasks == null) { preloadRawTasks(isTopTaskHome); } + int firstStackId = -1; int taskCount = mRawTasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + if (firstStackId < 0) { + firstStackId = t.stackId; + } // Compose the task key - Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, - t.firstActiveTime, t.lastActiveTime); + Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent, + t.userId, t.firstActiveTime, t.lastActiveTime); // Get an existing activity info handle if possible Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); @@ -143,14 +153,42 @@ public class RecentsTaskLoadPlan { iconFilename); task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false); if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail); - loadedTasks.add(task); + + if (!mConfig.multiStackEnabled || + Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + ArrayList<Task> stackTasks = stacksTasks.get(firstStackId); + if (stackTasks == null) { + stackTasks = new ArrayList<Task>(); + stacksTasks.put(firstStackId, stackTasks); + } + stackTasks.add(task); + } else { + ArrayList<Task> stackTasks = stacksTasks.get(t.stackId); + if (stackTasks == null) { + stackTasks = new ArrayList<Task>(); + stacksTasks.put(t.stackId, stackTasks); + } + stackTasks.add(task); + } } - mStack.setTasks(loadedTasks); - mStack.createAffiliatedGroupings(mConfig); - // Assertion - if (mStack.getTaskCount() != mRawTasks.size()) { - throw new RuntimeException("Loading failed"); + // Initialize the stacks + SparseArray<ActivityManager.StackInfo> stackInfos = mSystemServicesProxy.getAllStackInfos(); + mStacks.clear(); + int stackCount = stacksTasks.size(); + for (int i = 0; i < stackCount; i++) { + int stackId = stacksTasks.keyAt(i); + ActivityManager.StackInfo info = stackInfos.get(stackId); + ArrayList<Task> stackTasks = stacksTasks.valueAt(i); + TaskStack stack = new TaskStack(stackId); + if (Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + stack.setBounds(displayBounds, displayBounds); + } else { + stack.setBounds(info.bounds, displayBounds); + } + stack.setTasks(stackTasks); + stack.createAffiliatedGroupings(mConfig); + mStacks.put(stackId, stack); } } @@ -166,72 +204,93 @@ public class RecentsTaskLoadPlan { Resources res = mContext.getResources(); // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - Task task = tasks.get(i); - Task.TaskKey taskKey = task.key; + int stackCount = mStacks.size(); + for (int j = 0; j < stackCount; j++) { + ArrayList<Task> tasks = mStacks.valueAt(j).getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + Task task = tasks.get(i); + Task.TaskKey taskKey = task.key; - // Get an existing activity info handle if possible - Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); - ActivityInfoHandle infoHandle; - boolean hadCachedActivityInfo = false; - if (mActivityInfoCache.containsKey(cnKey)) { - infoHandle = mActivityInfoCache.get(cnKey); - hadCachedActivityInfo = true; - } else { - infoHandle = new ActivityInfoHandle(); - } + // Get an existing activity info handle if possible + Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); + ActivityInfoHandle infoHandle; + boolean hadCachedActivityInfo = false; + if (mActivityInfoCache.containsKey(cnKey)) { + infoHandle = mActivityInfoCache.get(cnKey); + hadCachedActivityInfo = true; + } else { + infoHandle = new ActivityInfoHandle(); + } - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); + boolean isRunningTask = (task.key.id == opts.runningTaskId); + boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); + boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } + // If requested, skip the running task + if (opts.onlyLoadPausedActivities && isRunningTask) { + continue; + } - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.activityIcon == null) { - if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); - task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, - mSystemServicesProxy, res, infoHandle, true); + if (opts.loadIcons && (isRunningTask || isVisibleTask)) { + if (task.activityIcon == null) { + if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); + task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, + t.taskDescription, mSystemServicesProxy, res, infoHandle, true); + } } - } - if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { - if (task.thumbnail == null || isRunningTask) { - if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); - if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, - true); - } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { - loadQueue.addTask(task); + if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { + if (task.thumbnail == null || isRunningTask) { + if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); + if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, + mSystemServicesProxy, true); + } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { + loadQueue.addTask(task); + } } } - } - // Update the activity info cache - if (!hadCachedActivityInfo && infoHandle.info != null) { - mActivityInfoCache.put(cnKey, infoHandle); + // Update the activity info cache + if (!hadCachedActivityInfo && infoHandle.info != null) { + mActivityInfoCache.put(cnKey, infoHandle); + } } } } /** - * Composes and returns a TaskStack from the preloaded list of recent tasks. + * Returns all TaskStacks from the preloaded list of recent tasks. */ - public TaskStack getTaskStack() { - return mStack; + public ArrayList<TaskStack> getAllTaskStacks() { + ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + stacks.add(mStacks.valueAt(i)); + } + // Ensure that we have at least one stack + if (stacks.isEmpty()) { + stacks.add(new TaskStack()); + } + return stacks; } /** - * Composes and returns a SpaceNode from the preloaded list of recent tasks. + * Returns a specific TaskStack from the preloaded list of recent tasks. */ - public SpaceNode getSpaceNode() { - SpaceNode node = new SpaceNode(); - node.setStack(mStack); - return node; + public TaskStack getTaskStack(int stackId) { + return mStacks.get(stackId); + } + + /** Returns whether there are any tasks in any stacks. */ + public boolean hasTasks() { + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + if (mStacks.valueAt(i).getTaskCount() > 0) { + return true; + } + } + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java deleted file mode 100644 index 831698a..0000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 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.recents.model; - -import android.graphics.Rect; - -import java.util.ArrayList; - - -/** - * The full recents space is partitioned using a BSP into various nodes that define where task - * stacks should be placed. - */ -public class SpaceNode { - /* BSP node callbacks */ - public interface SpaceNodeCallbacks { - /** Notifies when a node is added */ - public void onSpaceNodeAdded(SpaceNode node); - /** Notifies when a node is measured */ - public void onSpaceNodeMeasured(SpaceNode node, Rect rect); - } - - SpaceNode mStartNode; - SpaceNode mEndNode; - - TaskStack mStack; - - public SpaceNode() { - // Do nothing - } - - /** Sets the current stack for this space node */ - public void setStack(TaskStack stack) { - mStack = stack; - } - - /** Returns the task stack (not null if this is a leaf) */ - TaskStack getStack() { - return mStack; - } - - /** Returns whether there are any tasks in any stacks below this node. */ - public boolean hasTasks() { - return (mStack.getTaskCount() > 0) || - (mStartNode != null && mStartNode.hasTasks()) || - (mEndNode != null && mEndNode.hasTasks()); - } - - /** Returns whether this is a leaf node */ - boolean isLeafNode() { - return (mStartNode == null) && (mEndNode == null); - } - - /** Returns all the descendent task stacks */ - private void getStacksRec(ArrayList<TaskStack> stacks) { - if (isLeafNode()) { - stacks.add(mStack); - } else { - mStartNode.getStacksRec(stacks); - mEndNode.getStacksRec(stacks); - } - } - public ArrayList<TaskStack> getStacks() { - ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); - getStacksRec(stacks); - return stacks; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 55dfe45..0cd55d7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -36,6 +36,9 @@ public class Task { public void onTaskDataLoaded(); /* Notifies when a task has been unbound */ public void onTaskDataUnloaded(); + + /* Notifies when a task's stack id has changed. */ + public void onMultiStackDebugTaskStackIdChanged(); } /** The ComponentNameKey represents the unique primary key for a component @@ -68,14 +71,17 @@ public class Task { public static class TaskKey { final ComponentNameKey mComponentNameKey; public final int id; + public int stackId; public final Intent baseIntent; public final int userId; public long firstActiveTime; public long lastActiveTime; - public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) { + public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime, + long lastActiveTime) { mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId); this.id = id; + this.stackId = stackId; this.baseIntent = intent; this.userId = userId; this.firstActiveTime = firstActiveTime; @@ -92,18 +98,19 @@ public class Task { if (!(o instanceof TaskKey)) { return false; } - return id == ((TaskKey) o).id - && userId == ((TaskKey) o).userId; + TaskKey otherKey = (TaskKey) o; + return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId; } @Override public int hashCode() { - return (id << 5) + userId; + return Objects.hash(id, stackId, userId); } @Override public String toString() { return "Task.Key: " + id + ", " + + "s: " + stackId + ", " + "u: " + userId + ", " + "lat: " + lastActiveTime + ", " + baseIntent.getComponent().getPackageName(); @@ -180,6 +187,14 @@ public class Task { this.group = group; } + /** Updates the stack id of this task. */ + public void setStackId(int stackId) { + key.stackId = stackId; + if (mCb != null) { + mCb.onMultiStackDebugTaskStackIdChanged(); + } + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { this.applicationIcon = applicationIcon; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 7f7eee4..5aaea15 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.model; import android.graphics.Color; +import android.graphics.Rect; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.NamedCounter; @@ -173,33 +174,38 @@ public class TaskStack { public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks); } - /** A pair of indices representing the group and task positions in the stack and group. */ - public static class GroupTaskIndex { - public int groupIndex; // Index in the stack - public int taskIndex; // Index in the group - - public GroupTaskIndex() {} - - public GroupTaskIndex(int gi, int ti) { - groupIndex = gi; - taskIndex = ti; - } - } - // The task offset to apply to a task id as a group affiliation static final int IndividualTaskIdOffset = 1 << 16; + public final int id; + public final Rect stackBounds = new Rect(); + public final Rect displayBounds = new Rect(); + FilteredTaskList mTaskList = new FilteredTaskList(); TaskStackCallbacks mCb; ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>(); HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>(); - /** Sets the callbacks for this task stack */ + public TaskStack() { + this(0); + } + + public TaskStack(int stackId) { + id = stackId; + } + + /** Sets the callbacks for this task stack. */ public void setCallbacks(TaskStackCallbacks cb) { mCb = cb; } + /** Sets the bounds of this stack. */ + public void setBounds(Rect stackBounds, Rect displayBounds) { + this.stackBounds.set(stackBounds); + this.displayBounds.set(displayBounds); + } + /** Resets this TaskStack. */ public void reset() { mCb = null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 5152150..1bed553 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -29,8 +29,10 @@ import android.provider.Settings; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewStub; import android.view.WindowInsets; import android.widget.FrameLayout; +import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -40,6 +42,7 @@ import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -56,13 +59,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public void onAllTaskViewsDismissed(); public void onExitToHomeAnimationTriggered(); public void onScreenPinningRequest(); + + public void onMultiStackAddStack(); + public void onMultiStackResizeStack(); + public void onMultiStackRemoveStack(); + public void onMultiStackMoveTask(Task t); } RecentsConfiguration mConfig; LayoutInflater mInflater; DebugOverlayView mDebugOverlay; + ViewStub mMultiStackDebugStub; + View mMultiStackDebugView; + RecentsViewLayoutAlgorithm mLayoutAlgorithm; ArrayList<TaskStack> mStacks; + List<TaskStackView> mImmutableTaskStackViews = new ArrayList<TaskStackView>(); View mSearchBar; RecentsViewCallbacks mCb; @@ -82,6 +94,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV super(context, attrs, defStyleAttr, defStyleRes); mConfig = RecentsConfiguration.getInstance(); mInflater = LayoutInflater.from(context); + mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig); + } + + @Override + protected void onFinishInflate() { + if (!mConfig.multiStackEnabled) return; + + mMultiStackDebugStub = (ViewStub) findViewById(R.id.multistack_debug_view_stub); + if (mMultiStackDebugView == null) { + mMultiStackDebugView = mMultiStackDebugStub.inflate(); + mMultiStackDebugView.findViewById(R.id.add_stack).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCb.onMultiStackAddStack(); + } + }); + mMultiStackDebugView.findViewById(R.id.resize_stack).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCb.onMultiStackResizeStack(); + } + }); + } } /** Sets the callbacks */ @@ -99,24 +134,19 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV int numStacks = stacks.size(); // Make a list of the stack view children only - ArrayList<TaskStackView> stackViews = new ArrayList<TaskStackView>(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - stackViews.add((TaskStackView) child); - } - } + ArrayList<TaskStackView> stackViewsList = new ArrayList<TaskStackView>(); + List<TaskStackView> stackViews = getTaskStackViews(); // Remove all/extra stack views int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout if (mConfig.launchedReuseTaskStackViews) { - numTaskStacksToKeep = Math.min(childCount, numStacks); + numTaskStacksToKeep = Math.min(stackViews.size(), numStacks); } for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) { removeView(stackViews.get(i)); stackViews.remove(i); } + stackViewsList.addAll(stackViews); // Update the stack views that we are keeping for (int i = 0; i < numTaskStacksToKeep; i++) { @@ -133,42 +163,51 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV TaskStackView stackView = new TaskStackView(getContext(), stack); stackView.setCallbacks(this); addView(stackView); + stackViewsList.add(stackView); } + // Set the immutable stack views list + mImmutableTaskStackViews = Collections.unmodifiableList(stackViewsList); + // Enable debug mode drawing on all the stacks if necessary if (mConfig.debugModeEnabled) { - for (int i = childCount - 1; i >= 0; i--) { - View v = getChildAt(i); - if (v != mSearchBar) { - TaskStackView stackView = (TaskStackView) v; - stackView.setDebugOverlay(mDebugOverlay); - } + for (int i = mImmutableTaskStackViews.size() - 1; i >= 0; i--) { + TaskStackView stackView = mImmutableTaskStackViews.get(i); + stackView.setDebugOverlay(mDebugOverlay); } } + // Bring the debug view to the front + if (mMultiStackDebugView != null) { + mMultiStackDebugView.bringToFront(); + } + // Trigger a new layout requestLayout(); } + /** Gets the list of task views */ + List<TaskStackView> getTaskStackViews() { + return mImmutableTaskStackViews; + } + /** Launches the focused task from the first stack if possible */ public boolean launchFocusedTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - TaskStack stack = stackView.mStack; - // Iterate the stack views and try and find the focused task - List<TaskView> taskViews = stackView.getTaskViews(); - int taskViewCount = taskViews.size(); - for (int j = 0; j < taskViewCount; j++) { - TaskView tv = taskViews.get(j); - Task task = tv.getTask(); - if (tv.isFocusedTask()) { - onTaskViewClicked(stackView, tv, stack, task, false); - return true; - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + // Iterate the stack views and try and find the focused task + List<TaskView> taskViews = stackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int j = 0; j < taskViewCount; j++) { + TaskView tv = taskViews.get(j); + Task task = tv.getTask(); + if (tv.isFocusedTask()) { + onTaskViewClicked(stackView, tv, stack, task, false); + return true; } } } @@ -178,24 +217,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Launches the task that Recents was launched from, if possible */ public boolean launchPreviousTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - TaskStack stack = stackView.mStack; - ArrayList<Task> tasks = stack.getTasks(); - - // Find the launch task in the stack - if (!tasks.isEmpty()) { - int taskCount = tasks.size(); - for (int j = 0; j < taskCount; j++) { - if (tasks.get(j).isLaunchTarget) { - Task task = tasks.get(j); - TaskView tv = stackView.getChildViewForTask(task); - onTaskViewClicked(stackView, tv, stack, task, false); - return true; - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + ArrayList<Task> tasks = stack.getTasks(); + + // Find the launch task in the stack + if (!tasks.isEmpty()) { + int taskCount = tasks.size(); + for (int j = 0; j < taskCount; j++) { + if (tasks.get(j).isLaunchTarget) { + Task task = tasks.get(j); + TaskView tv = stackView.getChildViewForTask(task); + onTaskViewClicked(stackView, tv, stack, task, false); + return true; } } } @@ -209,13 +246,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // to ensure that it runs ctx.postAnimationTrigger.increment(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.startEnterRecentsAnimation(ctx); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.startEnterRecentsAnimation(ctx); } ctx.postAnimationTrigger.decrement(); } @@ -225,13 +260,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // We have to increment/decrement the post animation trigger in case there are no children // to ensure that it runs ctx.postAnimationTrigger.increment(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.startExitToHomeAnimation(ctx); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.startExitToHomeAnimation(ctx); } ctx.postAnimationTrigger.decrement(); @@ -287,22 +320,31 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } Rect taskStackBounds = new Rect(); - mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top, + mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top, mConfig.systemInsets.right, taskStackBounds); - // Measure each TaskStackView with the full width and height of the window since the + // Measure each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar && child.getVisibility() != GONE) { - TaskStackView tsv = (TaskStackView) child; - // Set the insets to be the top/left inset + search bounds - tsv.setStackInsetRect(taskStackBounds); - tsv.measure(widthMeasureSpec, heightMeasureSpec); + List<TaskStackView> stackViews = getTaskStackViews(); + List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews, + taskStackBounds); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + if (stackView.getVisibility() != GONE) { + // We are going to measure the TaskStackView with the whole RecentsView dimensions, + // but the actual stack is going to be inset to the bounds calculated by the layout + // algorithm + stackView.setStackInsetRect(stackViewsBounds.get(i)); + stackView.measure(widthMeasureSpec, heightMeasureSpec); } } + // Measure the multistack debug view + if (mMultiStackDebugView != null) { + mMultiStackDebugView.measure(width, height); + } + setMeasuredDimension(width, height); } @@ -322,14 +364,27 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Layout each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar && child.getVisibility() != GONE) { - child.layout(left, top, left + child.getMeasuredWidth(), - top + child.getMeasuredHeight()); + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + if (stackView.getVisibility() != GONE) { + stackView.layout(left, top, left + stackView.getMeasuredWidth(), + top + stackView.getMeasuredHeight()); } } + + // Layout the multistack debug view + if (mMultiStackDebugView != null) { + Rect taskStackBounds = new Rect(); + mConfig.getAvailableTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), + mConfig.systemInsets.top, mConfig.systemInsets.right, taskStackBounds); + mMultiStackDebugView.layout(left, + taskStackBounds.bottom - mConfig.systemInsets.bottom - + mMultiStackDebugView.getMeasuredHeight(), + left + mMultiStackDebugView.getMeasuredWidth(), + taskStackBounds.bottom - mConfig.systemInsets.bottom); + } } @Override @@ -343,41 +398,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Notifies each task view of the user interaction. */ public void onUserInteraction() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onUserInteraction(); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onUserInteraction(); } } /** Focuses the next task in the first stack view */ public void focusNextTask(boolean forward) { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.focusNextTask(forward, true); - break; - } + List<TaskStackView> stackViews = getTaskStackViews(); + if (!stackViews.isEmpty()) { + stackViews.get(0).focusNextTask(forward, true); } } /** Dismisses the focused task. */ public void dismissFocusedTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.dismissFocusedTask(); - break; - } + List<TaskStackView> stackViews = getTaskStackViews(); + if (!stackViews.isEmpty()) { + stackViews.get(0).dismissFocusedTask(); } } @@ -476,9 +519,16 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } }; } - opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, - b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), - sourceView.getHandler(), animStartedListener); + if (mConfig.multiStackEnabled) { + opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(), + R.anim.recents_from_unknown_enter, + R.anim.recents_from_unknown_exit, + sourceView.getHandler(), animStartedListener); + } else { + opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, + b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), + sourceView.getHandler(), animStartedListener); + } } final ActivityOptions launchOpts = opts; @@ -561,13 +611,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Final callback after Recents is finally hidden. */ public void onRecentsHidden() { // Notify each task stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onRecentsHidden(); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onRecentsHidden(); } } @@ -599,18 +647,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } } + @Override + public void onMultiStackMoveTask(Task t) { + if (mCb != null) { + mCb.onMultiStackMoveTask(t); + } + } + /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ @Override public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { // Propagate this event down to each task stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onPackagesChanged(monitor, packageName, userId); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onPackagesChanged(monitor, packageName, userId); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java new file mode 100644 index 0000000..eea273c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 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.recents.views; + +import android.graphics.Rect; +import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.model.TaskStack; + +import java.util.ArrayList; +import java.util.List; + +/* The layout logic for the RecentsView. */ +public class RecentsViewLayoutAlgorithm { + + RecentsConfiguration mConfig; + + public RecentsViewLayoutAlgorithm(RecentsConfiguration config) { + mConfig = config; + } + + /** Return the relative coordinate given coordinates in another size. */ + private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) { + float relPos = (float) otherCoord / otherSize; + return availableOffset + (int) (relPos * availableSize); + } + + /** + * Computes and returns the bounds that each of the stack views should take up. + */ + List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) { + ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size()); + int stackViewsCount = stackViews.size(); + for (int i = 0; i < stackViewsCount; i++) { + TaskStack stack = stackViews.get(i).getStack(); + Rect sb = stack.stackBounds; + Rect db = stack.displayBounds; + Rect ab = availableBounds; + bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()), + getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()), + getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()), + getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height()))); + } + return bounds; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 290792a..2318319 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -60,6 +60,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks); public void onTaskStackFilterTriggered(); public void onTaskStackUnfilterTriggered(); + + public void onMultiStackMoveTask(Task t); } RecentsConfiguration mConfig; @@ -149,6 +151,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal requestLayout(); } + /** Returns the task stack. */ + TaskStack getStack() { + return mStack; + } + /** Sets the debug overlay */ public void setDebugOverlay(DebugOverlayView overlay) { mDebugOverlay = overlay; @@ -625,6 +632,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return mTouchHandler.onGenericMotionEvent(ev); } + /** Returns the region that touch gestures can be started in. */ + Rect getTouchableRegion() { + return mTaskStackBounds; + } + @Override public void computeScroll() { mStackScroller.computeScroll(); @@ -1326,6 +1338,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + @Override + public void onMultiStackMoveTask(TaskView tv) { + if (mCb != null) { + mCb.onMultiStackMoveTask(tv.getTask()); + } + } + /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index ccad2f1..fabc86d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -96,16 +96,6 @@ public class TaskStackViewScroller { } return false; } - /** Bounds the current scroll if necessary, but does not synchronize the stack view with the model. */ - public boolean boundScrollRaw() { - float curScroll = getStackScroll(); - float newScroll = getBoundedStackScroll(curScroll); - if (Float.compare(newScroll, curScroll) != 0) { - setStackScrollRaw(newScroll); - return true; - } - return false; - } /** Returns the bounded stack scroll */ float getBoundedStackScroll(float scroll) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 4a6112c..6cdddc5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -123,6 +123,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return false; } + int action = ev.getAction(); + if (mConfig.multiStackEnabled) { + // Check if we are within the bounds of the stack view contents + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + } + // Pass through to swipe helper if we are swiping mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { @@ -131,7 +141,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { boolean wasScrolling = mScroller.isScrolling() || (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning()); - int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info @@ -198,6 +207,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return false; } + int action = ev.getAction(); + if (mConfig.multiStackEnabled) { + // Check if we are within the bounds of the stack view contents + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + } + // Pass through to swipe helper if we are swiping if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { return true; @@ -206,7 +225,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Update the velocity tracker initVelocityTrackerIfNotExists(); - int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 82120bf..098f2f9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -45,6 +45,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, public void onTaskViewDismissed(TaskView tv); public void onTaskViewClipStateChanged(TaskView tv); public void onTaskViewFocusChanged(TaskView tv, boolean focused); + + public void onMultiStackMoveTask(TaskView tv); } RecentsConfiguration mConfig; @@ -457,6 +459,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, .start(); } + /** Enables/disables handling touch on this task view. */ + void setTouchEnabled(boolean enabled) { + setOnClickListener(enabled ? this : null); + } + /** Animates this task view if the user does not interact with the stack after a certain time. */ void startNoUserInteractionAnimation() { mHeaderView.startNoUserInteractionAnimation(); @@ -667,6 +674,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Rebind any listeners mHeaderView.mApplicationIcon.setOnClickListener(this); mHeaderView.mDismissButton.setOnClickListener(this); + if (mConfig.multiStackEnabled) { + mHeaderView.mMoveTaskButton.setOnClickListener(this); + } mActionButtonView.setOnClickListener(this); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { if (mConfig.developerOptionsEnabled) { @@ -687,6 +697,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Unbind any listeners mHeaderView.mApplicationIcon.setOnClickListener(null); mHeaderView.mDismissButton.setOnClickListener(null); + if (mConfig.multiStackEnabled) { + mHeaderView.mMoveTaskButton.setOnClickListener(null); + } mActionButtonView.setOnClickListener(null); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { mHeaderView.mApplicationIcon.setOnLongClickListener(null); @@ -695,9 +708,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mTaskDataLoaded = false; } - /** Enables/disables handling touch on this task view. */ - void setTouchEnabled(boolean enabled) { - setOnClickListener(enabled ? this : null); + @Override + public void onMultiStackDebugTaskStackIdChanged() { + mHeaderView.rebindToTask(mTask); } /**** View.OnClickListener Implementation ****/ @@ -717,6 +730,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } } else if (v == mHeaderView.mDismissButton) { dismissTask(); + } else if (v == mHeaderView.mMoveTaskButton) { + if (mCb != null) { + mCb.onMultiStackMoveTask(tv); + } } } }, 125); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index e13eed8..b827acc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -56,6 +56,7 @@ public class TaskViewHeader extends FrameLayout { RecentsConfiguration mConfig; // Header views + ImageView mMoveTaskButton; ImageView mDismissButton; ImageView mApplicationIcon; TextView mActivityDescription; @@ -126,6 +127,10 @@ public class TaskViewHeader extends FrameLayout { mApplicationIcon = (ImageView) findViewById(R.id.application_icon); mActivityDescription = (TextView) findViewById(R.id.activity_description); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); + mMoveTaskButton = (ImageView) findViewById(R.id.move_task); + if (mConfig.multiStackEnabled) { + mMoveTaskButton.setVisibility(View.VISIBLE); + } // Hide the backgrounds if they are ripple drawables if (!Constants.DebugFlags.App.EnableTaskFiltering) { @@ -188,7 +193,10 @@ public class TaskViewHeader extends FrameLayout { mApplicationIcon.setImageDrawable(t.applicationIcon); } mApplicationIcon.setContentDescription(t.activityLabel); - if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { + // Always update when multi stack debugging is enabled as the stack id can change + if (mConfig.multiStackEnabled) { + mActivityDescription.setText("[" + t.key.stackId + "] " + t.activityLabel); + } else if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { mActivityDescription.setText(t.activityLabel); } // Try and apply the system ui tint diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index df4b6d6..4c81b80 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16077,6 +16077,15 @@ public final class ActivityManagerService extends ActivityManagerNative return mStackSupervisor.getFocusedStack(); } + @Override + public int getFocusedStackId() throws RemoteException { + ActivityStack focusedStack = getFocusedStack(); + if (focusedStack != null) { + return focusedStack.getStackId(); + } + return -1; + } + public Configuration getConfiguration() { Configuration ci; synchronized(this) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b7728b3..03f1f71 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3729,6 +3729,13 @@ public final class ActivityStackSupervisor implements DisplayListener { } @Override + public int getStackId() { + synchronized (mService) { + return mStackId; + } + } + + @Override public boolean injectEvent(InputEvent event) { final long origId = Binder.clearCallingIdentity(); try { |