OSDN Git Service

Adding some debug controls to test multi-window.
authorWinson Chung <winsonc@google.com>
Tue, 27 Jan 2015 00:11:07 +0000 (16:11 -0800)
committerWinson Chung <winsonc@google.com>
Thu, 29 Jan 2015 23:56:58 +0000 (15:56 -0800)
Adding some preliminary controls to mirror the currently exposed api
to create new activity stacks, resize stacks, and to move tasks
between stacks.

Change-Id: I3fb51c248f53a1d4c4eb23ca9fb3a76888def1de

28 files changed:
core/java/android/app/ActivityManagerNative.java
core/java/android/app/IActivityContainer.aidl
core/java/android/app/IActivityManager.java
packages/SystemUI/res/layout/recents.xml
packages/SystemUI/res/layout/recents_multistack_debug.xml [new file with mode: 0644]
packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml [new file with mode: 0644]
packages/SystemUI/res/layout/recents_task_view_header.xml
packages/SystemUI/res/values/strings.xml
packages/SystemUI/res/values/styles.xml
packages/SystemUI/src/com/android/systemui/recents/Constants.java
packages/SystemUI/src/com/android/systemui/recents/Recents.java
packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java [deleted file]
packages/SystemUI/src/com/android/systemui/recents/model/Task.java
packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/ActivityStackSupervisor.java

index 379fe11..97b9f4c 100644 (file)
@@ -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();
index 52884f7..ff1175f 100644 (file)
@@ -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();
 }
index f152c6f..efc4543 100644 (file)
@@ -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;
 }
index 8f367a6..26523f9 100644 (file)
@@ -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
     <!-- 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 (file)
index 0000000..6524a54
--- /dev/null
@@ -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 (file)
index 0000000..36e54a0
--- /dev/null
@@ -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
index f1d8ad0..53047a3 100644 (file)
         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"
index c977db9..40bf13f 100644 (file)
     <!-- 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>
 
index f2b4a69..bf19b8d 100644 (file)
@@ -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 -->
index cfd6b40..192acc6 100644 (file)
@@ -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
index 3c75aac..9dd82fc 100644 (file)
@@ -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);
index 1833e09..b1ac733 100644 (file)
@@ -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
index bc10a48..1736c77 100644 (file)
@@ -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 (file)
index 0000000..fdf9d39
--- /dev/null
@@ -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();
+    }
+}
index 237d4f0..72040fe 100644 (file)
@@ -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() {
index 3d25c80..788e473 100644 (file)
@@ -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 (file)
index 831698a..0000000
+++ /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;
-    }
-}
index 55dfe45..0cd55d7 100644 (file)
@@ -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;
index 7f7eee4..5aaea15 100644 (file)
@@ -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;
index 5152150..1bed553 100644 (file)
@@ -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 (file)
index 0000000..eea273c
--- /dev/null
@@ -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;
+    }
+}
index 290792a..2318319 100644 (file)
@@ -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
index ccad2f1..fabc86d 100644 (file)
@@ -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) {
index 4a6112c..6cdddc5 100644 (file)
@@ -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
index 82120bf..098f2f9 100644 (file)
@@ -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);
index e13eed8..b827acc 100644 (file)
@@ -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
index df4b6d6..4c81b80 100644 (file)
@@ -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) {
index b7728b3..03f1f71 100644 (file)
@@ -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 {