OSDN Git Service

DO NOT MERGE - Port 2D recents implementation from master to nyc-mr2
authorManu Cornet <manucornet@google.com>
Tue, 20 Dec 2016 16:29:33 +0000 (08:29 -0800)
committerManu Cornet <manucornet@google.com>
Tue, 20 Dec 2016 20:10:41 +0000 (12:10 -0800)
Bug: 32101881
Test: Checked Recents layout/behavior on local sw600dp device
Change-Id: I40be7dbaf8bc017b4c7c449f9bca657817107ceb

21 files changed:
packages/SystemUI/res/layout/recents_grid_task_view.xml [new file with mode: 0644]
packages/SystemUI/res/layout/recents_task_view.xml
packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml [new file with mode: 0644]
packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml [new file with mode: 0644]
packages/SystemUI/res/values/dimens_grid.xml [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/Recents.java
packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java [new file with mode: 0644]

diff --git a/packages/SystemUI/res/layout/recents_grid_task_view.xml b/packages/SystemUI/res/layout/recents_grid_task_view.xml
new file mode 100644 (file)
index 0000000..53bec70
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<com.android.systemui.recents.views.grid.GridTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true">
+    <com.android.systemui.recents.views.TaskViewThumbnail
+        android:id="@+id/task_view_thumbnail"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <include layout="@layout/recents_task_view_header" />
+
+    <!-- TODO: Move this into a view stub -->
+    <include layout="@layout/recents_task_view_lock_to_app"/>
+
+    <!-- The incompatible app toast -->
+    <include layout="@layout/recents_task_view_incompatible_app_toast"/>
+</com.android.systemui.recents.views.grid.GridTaskView>
+
+
index c8e5b61..015e4a2 100644 (file)
@@ -4,9 +4,9 @@
      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.
     <include layout="@layout/recents_task_view_header" />
 
     <!-- TODO: Move this into a view stub -->
-    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-        android:id="@+id/lock_to_app_fab"
-        android:layout_width="@dimen/recents_lock_to_app_size"
-        android:layout_height="@dimen/recents_lock_to_app_size"
-        android:layout_gravity="bottom|end"
-        android:layout_marginEnd="15dp"
-        android:layout_marginBottom="15dp"
-        android:translationZ="4dp"
-        android:contentDescription="@string/recents_lock_to_app_button_label"
-        android:background="@drawable/recents_lock_to_task_button_bg"
-        android:visibility="invisible"
-        android:alpha="0">
-        <ImageView
-            android:layout_width="@dimen/recents_lock_to_app_icon_size"
-            android:layout_height="@dimen/recents_lock_to_app_icon_size"
-            android:layout_gravity="center"
-            android:src="@drawable/recents_lock_to_app_pin" />
-    </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+    <include layout="@layout/recents_task_view_lock_to_app"/>
 
     <!-- The incompatible app toast -->
-    <ViewStub android:id="@+id/incompatible_app_toast_stub"
-                android:inflatedId="@+id/incompatible_app_toast"
-                android:layout="@*android:layout/transient_notification"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="top|center_horizontal"
-                android:layout_marginTop="48dp"
-                android:layout_marginLeft="16dp"
-                android:layout_marginRight="16dp" />
+    <include layout="@layout/recents_task_view_incompatible_app_toast"/>
 </com.android.systemui.recents.views.TaskView>
 
 
diff --git a/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml
new file mode 100644 (file)
index 0000000..d573d6b
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<ViewStub
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/incompatible_app_toast_stub"
+    android:inflatedId="@+id/incompatible_app_toast"
+    android:layout="@*android:layout/transient_notification"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="top|center_horizontal"
+    android:layout_marginTop="48dp"
+    android:layout_marginLeft="16dp"
+    android:layout_marginRight="16dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml
new file mode 100644 (file)
index 0000000..8cece11
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lock_to_app_fab"
+    android:layout_width="@dimen/recents_lock_to_app_size"
+    android:layout_height="@dimen/recents_lock_to_app_size"
+    android:layout_gravity="bottom|end"
+    android:layout_marginEnd="15dp"
+    android:layout_marginBottom="15dp"
+    android:translationZ="4dp"
+    android:contentDescription="@string/recents_lock_to_app_button_label"
+    android:background="@drawable/recents_lock_to_task_button_bg"
+    android:visibility="invisible"
+    android:alpha="0">
+    <ImageView
+        android:layout_width="@dimen/recents_lock_to_app_icon_size"
+        android:layout_height="@dimen/recents_lock_to_app_icon_size"
+        android:layout_gravity="center"
+        android:src="@drawable/recents_lock_to_app_pin" />
+</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
new file mode 100644 (file)
index 0000000..94ffdb1
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2016, 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.
+*/
+-->
+<resources>
+  <dimen name="recents_grid_padding_left_right">32dp</dimen>
+  <dimen name="recents_grid_padding_top_bottom">150dp</dimen>
+  <dimen name="recents_grid_padding_task_view">20dp</dimen>
+  <dimen name="recents_grid_task_view_header_height">44dp</dimen>
+  <dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
+</resources>
+
index eb9beb6..ad229ad 100644 (file)
@@ -207,8 +207,6 @@ public class Recents extends SystemUI
                 getSystemService(Context.UI_MODE_SERVICE);
         if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
             mImpl = new RecentsTvImpl(mContext);
-        } else if (SystemProperties.getBoolean("ro.recents.grid", false) == true) {
-            mImpl = new RecentsGridImpl(mContext);
         } else {
             mImpl = new RecentsImpl(mContext);
         }
index 73c6e6e..711f0c6 100644 (file)
@@ -20,6 +20,7 @@ import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
 
+import android.os.SystemProperties;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
@@ -58,6 +59,10 @@ public class RecentsConfiguration {
     public boolean fakeShadows;
     public int svelteLevel;
 
+    // Whether this product supports Grid-based Recents. If this is field is set to true, then
+    // Recents will layout task views in a grid mode when there's enough space in the screen.
+    public boolean isGridEnabled;
+
     public RecentsConfiguration(Context context) {
         // Load only resources that can not change after the first load either through developer
         // settings or via multi window
@@ -66,6 +71,7 @@ public class RecentsConfiguration {
         Resources res = appContext.getResources();
         fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
         svelteLevel = res.getInteger(R.integer.recents_svelte_level);
+        isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
 
         float screenDensity = context.getResources().getDisplayMetrics().density;
         smallestWidth = ssp.getDeviceSmallestWidth();
index 42d1b61..2263a4e 100644 (file)
@@ -180,10 +180,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         ssp.registerTaskStackListener(mTaskStackListener);
 
         // Initialize the static configuration resources
-        LayoutInflater inflater = LayoutInflater.from(mContext);
         mDummyStackView = new TaskStackView(mContext);
-        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
-                null, false);
         reloadResources();
     }
 
@@ -204,14 +201,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         Resources res = mContext.getResources();
         reloadResources();
         mDummyStackView.reloadOnConfigurationChange();
-        // Update the header bar direction directly as it is not attached to anything and does not
-        // layout except in updateHeaderBarLayout()
-        mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
-        mHeaderBar.onConfigurationChanged();
-        mHeaderBar.forceLayout();
-        mHeaderBar.measure(
-                MeasureSpec.makeMeasureSpec(mHeaderBar.getMeasuredWidth(), MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mHeaderBar.getMeasuredHeight(), MeasureSpec.EXACTLY));
     }
 
     /**
@@ -582,7 +571,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
                 R.dimen.recents_task_view_header_height,
                 R.dimen.recents_task_view_header_height_tablet_land,
                 R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land);
+                R.dimen.recents_task_view_header_height_tablet_land,
+                R.dimen.recents_grid_task_view_header_height);
+
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
+                null, false);
+        mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
     }
 
     /**
@@ -721,7 +716,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
                 if (task.isFreeformTask()) {
                     mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
                             stackScroller.getStackScroll(), mTmpTransform, null,
-                            windowOverrideRect);
+                            windowOverrideRect, false /* useGridLayout */);
                     Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
                             mThumbTransitionBitmapCache);
                     Rect toTaskRect = new Rect();
@@ -771,7 +766,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         stackView.updateLayoutAlgorithm(true /* boundScroll */);
         stackView.updateToInitialState();
         stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
-                stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
+                stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect,
+                Recents.getConfiguration().isGridEnabled);
         return mTmpTransform;
     }
 
index 2c5c437..d64fc36 100644 (file)
@@ -79,14 +79,15 @@ public class Utilities {
     public static final Rect EMPTY_RECT = new Rect();
 
     /**
-     * @return the first parent walking up the view hierarchy that has the given class type.
+     * @return the first parent walking up the view hierarchy that has the given class type (or
+     * a subclass).
      *
      * @param parentClass must be a class derived from {@link View}
      */
     public static <T extends View> T findParent(View v, Class<T> parentClass) {
         ViewParent parent = v.getParent();
         while (parent != null) {
-            if (parent.getClass().equals(parentClass)) {
+            if (parentClass.isAssignableFrom(parent.getClass())) {
                 return (T) parent;
             }
             parent = parent.getParent();
index 745f5a5..178cb9f 100644 (file)
@@ -849,6 +849,24 @@ public class TaskStack {
         return null;
     }
 
+    /**
+     * Returns the task in stack tasks which should be launched next if Recents are toggled
+     * again, or null if there is no task to be launched.
+     */
+    public Task getNextLaunchTarget() {
+        int taskCount = getTaskCount();
+        if (taskCount == 0) {
+            return null;
+        }
+        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+        if (launchTaskIndex != -1) {
+            launchTaskIndex = Math.max(0, launchTaskIndex - 1);
+        } else {
+            launchTaskIndex = getTaskCount() - 1;
+        }
+        return getStackTasks().get(launchTaskIndex);
+    }
+
     /** Returns the index of this task in this current task stack */
     public int indexOfStackTask(Task t) {
         return mStackTaskList.indexOf(t);
index 253d06a..dba085e 100644 (file)
@@ -30,17 +30,17 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
     private static final float MIN_ALPHA = 0.1f;
     private static final float MAX_ALPHA = 0.8f;
 
-    View mSourceView;
+    protected View mSourceView;
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mClipRect = new Rect();
+    protected Rect mClipRect = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mClipBounds = new Rect();
+    protected Rect mClipBounds = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mLastClipBounds = new Rect();
+    protected Rect mLastClipBounds = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    int mCornerRadius;
+    protected int mCornerRadius;
     @ViewDebug.ExportedProperty(category="recents")
-    float mAlpha = 1f;
+    protected float mAlpha = 1f;
 
     public AnimateableViewBounds(View source, int cornerRadius) {
         mSourceView = source;
@@ -110,7 +110,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
         return mClipRect.bottom;
     }
 
-    private void updateClipBounds() {
+    protected void updateClipBounds() {
         mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
                 mSourceView.getWidth() - Math.max(0, mClipRect.right),
                 mSourceView.getHeight() - Math.max(0, mClipRect.bottom));
index 24ef433..8d32c9c 100644 (file)
@@ -95,7 +95,6 @@ public class RecentsView extends FrameLayout {
     private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
     private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
 
-    private TaskStack mStack;
     private TaskStackView mTaskStackView;
     private TextView mStackActionButton;
     private TextView mEmptyView;
@@ -195,7 +194,6 @@ public class RecentsView extends FrameLayout {
      * Called from RecentsActivity when the task stack is updated.
      */
     public void updateStack(TaskStack stack, boolean setStackViewTasks) {
-        mStack = stack;
         if (setStackViewTasks) {
             mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
         }
@@ -212,7 +210,7 @@ public class RecentsView extends FrameLayout {
      * Returns the current TaskStack.
      */
     public TaskStack getStack() {
-        return mStack;
+        return mTaskStackView.getStack();
     }
 
     /*
@@ -251,8 +249,7 @@ public class RecentsView extends FrameLayout {
     /** Launches the task that recents was launched from if possible */
     public boolean launchPreviousTask() {
         if (mTaskStackView != null) {
-            TaskStack stack = mTaskStackView.getStack();
-            Task task = stack.getLaunchTarget();
+            Task task = getStack().getLaunchTarget();
             if (task != null) {
                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
                 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
@@ -340,7 +337,8 @@ public class RecentsView extends FrameLayout {
 
         if (RecentsDebugFlags.Static.EnableStackActionButton) {
             // Measure the stack action button within the constraints of the space above the stack
-            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
+            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
+                    mTaskStackView.useGridLayout());
             measureChild(mStackActionButton,
                     MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
                     MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
@@ -437,8 +435,9 @@ public class RecentsView extends FrameLayout {
 
     public final void onBusEvent(LaunchTaskEvent event) {
         mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
-        mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView,
-                event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack);
+        mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
+                event.taskView, event.screenPinningRequested, event.targetTaskBounds,
+                event.targetTaskStack);
     }
 
     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
@@ -514,8 +513,7 @@ public class RecentsView extends FrameLayout {
                         EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
                         // Remove the task and don't bother relaying out, as all the tasks will be
                         // relaid out when the stack changes on the multiwindow change event
-                        mTaskStackView.getStack().removeTask(event.task, null,
-                                true /* fromDockGesture */);
+                        getStack().removeTask(event.task, null, true /* fromDockGesture */);
                     }
                 };
 
@@ -536,7 +534,7 @@ public class RecentsView extends FrameLayout {
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
                         event.task.getTopComponent().flattenToShortString());
             } else {
-                EventBus.getDefault().send(new DragEndCancelledEvent(mStack, event.task,
+                EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
                         event.taskView));
             }
         } else {
@@ -598,7 +596,7 @@ public class RecentsView extends FrameLayout {
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
-                && mStack.getTaskCount() > 0) {
+                && getStack().getTaskCount() > 0) {
             animateBackgroundScrim(1f,
                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
         }
@@ -775,7 +773,8 @@ public class RecentsView extends FrameLayout {
      * @return the bounds of the stack action button.
      */
     private Rect getStackActionButtonBoundsFromStackLayout() {
-        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect);
+        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
+                mTaskStackView.useGridLayout()));
         int left = isLayoutRtl()
                 ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
                 : actionButtonRect.right + mStackActionButton.getPaddingRight()
@@ -797,8 +796,8 @@ public class RecentsView extends FrameLayout {
         writer.print(" [0x"); writer.print(id); writer.print("]");
         writer.println();
 
-        if (mStack != null) {
-            mStack.dump(innerPrefix, writer);
+        if (getStack() != null) {
+            getStack().dump(innerPrefix, writer);
         }
         if (mTaskStackView != null) {
             mTaskStackView.dump(innerPrefix, writer);
index 493e618..c1f4c8a 100644 (file)
@@ -157,7 +157,7 @@ public class TaskStackAnimationHelper {
 
             // Get the current transform for the task, which will be used to position it offscreen
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
+                    null, mStackView.useGridLayout());
 
             if (hideTask) {
                 tv.setVisibility(View.INVISIBLE);
@@ -230,7 +230,7 @@ public class TaskStackAnimationHelper {
             // Get the current transform for the task, which will be updated to the final transform
             // to animate to depending on how recents was invoked
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
+                    null, mStackView.useGridLayout());
 
             if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
                 if (task.isLaunchTarget) {
index fce7f9d..f0644a5 100644 (file)
@@ -38,6 +38,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -240,14 +241,14 @@ public class TaskStackLayoutAlgorithm {
     // This is the current system insets
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mSystemInsets = new Rect();
-    // This is the bounds of the stack action above the stack rect
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect mStackActionButtonRect = new Rect();
 
     // The visible ranges when the stack is focused and unfocused
     private Range mUnfocusedRange;
     private Range mFocusedRange;
 
+    // This is the bounds of the stack action above the stack rect
+    @ViewDebug.ExportedProperty(category="recents")
+    private Rect mStackActionButtonRect = new Rect();
     // The base top margin for the stack from the system insets
     @ViewDebug.ExportedProperty(category="recents")
     private int mBaseTopMargin;
@@ -326,7 +327,7 @@ public class TaskStackLayoutAlgorithm {
     @ViewDebug.ExportedProperty(category="recents")
     int mMinTranslationZ;
     @ViewDebug.ExportedProperty(category="recents")
-    int mMaxTranslationZ;
+    public int mMaxTranslationZ;
 
     // Optimization, allows for quick lookup of task -> index
     private SparseIntArray mTaskIndexMap = new SparseIntArray();
@@ -334,6 +335,7 @@ public class TaskStackLayoutAlgorithm {
 
     // The freeform workspace layout
     FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
+    TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
 
     // The transform to place TaskViews at the front and back of the stack respectively
     TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
@@ -344,19 +346,7 @@ public class TaskStackLayoutAlgorithm {
         mContext = context;
         mCb = cb;
         mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
-        mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
-        mBaseTopMargin = getDimensionForDevice(context,
-                R.dimen.recents_layout_top_margin_phone,
-                R.dimen.recents_layout_top_margin_tablet,
-                R.dimen.recents_layout_top_margin_tablet_xlarge);
-        mBaseSideMargin = getDimensionForDevice(context,
-                R.dimen.recents_layout_side_margin_phone,
-                R.dimen.recents_layout_side_margin_tablet,
-                R.dimen.recents_layout_side_margin_tablet_xlarge);
-        mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
-        mFreeformStackGap =
-                res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
-
+        mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
         reloadOnConfigurationChange(context);
     }
 
@@ -381,6 +371,7 @@ public class TaskStackLayoutAlgorithm {
                 R.dimen.recents_layout_initial_top_offset_tablet,
                 R.dimen.recents_layout_initial_top_offset_tablet,
                 R.dimen.recents_layout_initial_top_offset_tablet,
+                R.dimen.recents_layout_initial_top_offset_tablet,
                 R.dimen.recents_layout_initial_top_offset_tablet);
         mBaseInitialBottomOffset = getDimensionForDevice(context,
                 R.dimen.recents_layout_initial_bottom_offset_phone_port,
@@ -388,8 +379,24 @@ public class TaskStackLayoutAlgorithm {
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
+                R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet);
         mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
+        mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
+        mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
+        mBaseTopMargin = getDimensionForDevice(context,
+                R.dimen.recents_layout_top_margin_phone,
+                R.dimen.recents_layout_top_margin_tablet,
+                R.dimen.recents_layout_top_margin_tablet_xlarge,
+                R.dimen.recents_layout_top_margin_tablet);
+        mBaseSideMargin = getDimensionForDevice(context,
+                R.dimen.recents_layout_side_margin_phone,
+                R.dimen.recents_layout_side_margin_tablet,
+                R.dimen.recents_layout_side_margin_tablet_xlarge,
+                R.dimen.recents_layout_side_margin_tablet);
+        mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
+        mFreeformStackGap =
+                res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
     }
 
     /**
@@ -406,6 +413,7 @@ public class TaskStackLayoutAlgorithm {
     public boolean setSystemInsets(Rect systemInsets) {
         boolean changed = !mSystemInsets.equals(systemInsets);
         mSystemInsets.set(systemInsets);
+        mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets);
         return changed;
     }
 
@@ -471,6 +479,9 @@ public class TaskStackLayoutAlgorithm {
 
             updateFrontBackTransforms();
         }
+
+        // Initialize the grid layout
+        mTaskGridLayoutAlgorithm.initialize(displayRect, windowRect);
     }
 
     /**
@@ -722,6 +733,11 @@ public class TaskStackLayoutAlgorithm {
         }
     }
 
+    public Rect getStackActionButtonRect(boolean useGridLayout) {
+        return useGridLayout
+                ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
+    }
+
     /**
      * Returns the TaskViewTransform that would put the task just off the back of the stack.
      */
@@ -826,24 +842,30 @@ public class TaskStackLayoutAlgorithm {
      * is what the view is measured and laid out with.
      */
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
+            TaskViewTransform transformOut, TaskViewTransform frontTransform,
+            boolean useGridLayout) {
         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */, false /* ignoreTaskOverrides */);
+                false /* forceUpdate */, false /* ignoreTaskOverrides */, useGridLayout);
     }
 
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform frontTransform,
-            boolean ignoreTaskOverrides) {
+            boolean ignoreTaskOverrides, boolean useGridLayout) {
         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */, ignoreTaskOverrides);
+                false /* forceUpdate */, ignoreTaskOverrides, useGridLayout);
     }
 
     public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
             TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
-            boolean ignoreTaskOverrides) {
+            boolean ignoreTaskOverrides, boolean useGridLayout) {
         if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
             mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
             return transformOut;
+        } else if (useGridLayout) {
+            int taskIndex = mTaskIndexMap.get(task.key.id);
+            int taskCount = mTaskIndexMap.size();
+            mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
+            return transformOut;
         } else {
             // Return early if we have an invalid index
             int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
@@ -865,10 +887,10 @@ public class TaskStackLayoutAlgorithm {
      */
     public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform frontTransform,
-            Rect windowOverrideRect) {
+            Rect windowOverrideRect, boolean useGridLayout) {
         TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
                 transformOut, frontTransform, true /* forceUpdate */,
-                false /* ignoreTaskOverrides */);
+                false /* ignoreTaskOverrides */, useGridLayout);
         return transformToScreenCoordinates(transform, windowOverrideRect);
     }
 
@@ -1089,9 +1111,9 @@ public class TaskStackLayoutAlgorithm {
      * Retrieves resources that are constant regardless of the current configuration of the device.
      */
     public static int getDimensionForDevice(Context ctx, int phoneResId,
-            int tabletResId, int xlargeTabletResId) {
+            int tabletResId, int xlargeTabletResId, int gridLayoutResId) {
         return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
-                xlargeTabletResId, xlargeTabletResId);
+                xlargeTabletResId, xlargeTabletResId, gridLayoutResId);
     }
 
     /**
@@ -1099,12 +1121,14 @@ public class TaskStackLayoutAlgorithm {
      */
     public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
             int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
-            int xlargeTabletLandResId) {
+            int xlargeTabletLandResId, int gridLayoutResId) {
         RecentsConfiguration config = Recents.getConfiguration();
         Resources res = ctx.getResources();
         boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
                 Configuration.ORIENTATION_LANDSCAPE;
-        if (config.isXLargeScreen) {
+        if (config.isGridEnabled) {
+            return res.getDimensionPixelSize(gridLayoutResId);
+        } else if (config.isXLargeScreen) {
             return res.getDimensionPixelSize(isLandscape
                     ? xlargeTabletLandResId
                     : xlargeTabletPortResId);
index fc580a5..8d91b4f 100644 (file)
@@ -92,6 +92,7 @@ import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
+import com.android.systemui.recents.views.grid.GridTaskView;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -158,6 +159,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
     private int mTaskCornerRadiusPx;
     private int mDividerSize;
     private int mStartTimerIndicatorDuration;
+    private boolean mDraggingOverDockState;
 
     @ViewDebug.ExportedProperty(category="recents")
     private boolean mTaskViewsClipDirty = true;
@@ -279,6 +281,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             }
         });
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        if (ssp.hasFreeformWorkspaceSupport()) {
+            setWillNotDraw(false);
+        }
 
         mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
                 R.drawable.recents_freeform_workspace_bg);
@@ -496,13 +501,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
 
             // Calculate the current and (if necessary) the target transform for the task
             transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
-                    taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
+                    taskTransforms.get(i), frontTransform, ignoreTaskOverrides, useGridLayout());
             if (useTargetStackScroll && !transform.visible) {
                 // If we have a target stack scroll and the task is not currently visible, then we
                 // just update the transform at the new scroll
                 // TODO: Optimize this
-                transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
-                        targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
+                transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll,
+                    new TaskViewTransform(), frontTransformAtTarget, useGridLayout());
                 if (transformAtTarget.visible) {
                     transform.copyFrom(transformAtTarget);
                 }
@@ -733,7 +738,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             } else {
                 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
                         focusState, transform, null, true /* forceUpdate */,
-                        false /* ignoreTaskOverrides */);
+                        false /* ignoreTaskOverrides */, useGridLayout());
             }
             transform.visible = true;
         }
@@ -750,7 +755,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             Task task = tasks.get(i);
             TaskViewTransform transform = transformsOut.get(i);
             mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
-                    true /* forceUpdate */, ignoreTaskOverrides);
+                    true /* forceUpdate */, ignoreTaskOverrides, useGridLayout());
             transform.visible = true;
         }
     }
@@ -779,6 +784,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
      * Updates the clip for each of the task views from back to front.
      */
     private void clipTaskViews() {
+        // We never clip task views in grid layout
+        if (Recents.getConfiguration().isGridEnabled) {
+            return;
+        }
+
         // Update the clip on each task child
         List<TaskView> taskViews = getTaskViews();
         TaskView tmpTv = null;
@@ -1335,14 +1345,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
                     false /* requestViewFocus */);
         }
-
-        // Update the stack action button visibility
-        if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                mStack.getTaskCount() > 0) {
-            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
-        } else {
-            EventBus.getDefault().send(new HideStackActionButtonEvent());
-        }
+        updateStackActionButtonVisibility();
     }
 
     public boolean isTouchPointInView(float x, float y, TaskView tv) {
@@ -1503,7 +1506,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
 
     @Override
     public TaskView createView(Context context) {
-        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
+        if (Recents.getConfiguration().isGridEnabled) {
+            return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false);
+        } else {
+            return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
+        }
     }
 
     @Override
@@ -1560,11 +1567,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         // Bind the task view to the new task
         bindTaskView(tv, task);
 
-        // If the doze trigger has already fired, then update the state for this task view
-        if (mUIDozeTrigger.isAsleep()) {
-            tv.setNoUserInteractionState();
-        }
-
         // Set the new state for this view, including the callbacks and view clipping
         tv.setCallbacks(this);
         tv.setTouchEnabled(true);
@@ -1594,6 +1596,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         // Rebind the task and request that this task's data be filled into the TaskView
         tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect);
 
+        // If the doze trigger has already fired, then update the state for this task view
+        if (mUIDozeTrigger.isAsleep() ||
+                Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+            tv.setNoUserInteractionState();
+        }
+
         // Load the task data
         Recents.getTaskLoader().loadTaskData(task);
     }
@@ -1632,7 +1640,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             relayoutTaskViewsOnNextFrame(animation);
         }
 
-        if (mEnterAnimationComplete) {
+        // In grid layout, the stack action button always remains visible.
+        if (mEnterAnimationComplete && !useGridLayout()) {
             if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     mStack.getTaskCount() > 0) {
@@ -1679,17 +1688,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             return;
         }
 
-        int launchTaskIndex = mStack.indexOfStackTask(mStack.getLaunchTarget());
-        if (launchTaskIndex != -1) {
-            launchTaskIndex = Math.max(0, launchTaskIndex - 1);
-        } else {
-            launchTaskIndex = mStack.getTaskCount() - 1;
-        }
-        if (launchTaskIndex != -1) {
+        final Task launchTask = mStack.getNextLaunchTarget();
+        if (launchTask != null) {
             // Stop all animations
             cancelAllTaskViewAnimations();
 
-            final Task launchTask = mStack.getStackTasks().get(launchTaskIndex);
             float curScroll = mStackScroller.getStackScroll();
             float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(launchTask);
             float absScrollDiff = Math.abs(targetScroll - curScroll);
@@ -1832,7 +1835,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         // Enlarge the dragged view slightly
         float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
-                mTmpTransform, null);
+                mTmpTransform, null, useGridLayout());
         mTmpTransform.scale = finalScale;
         mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
         mTmpTransform.dimAlpha = 0f;
@@ -1853,6 +1856,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
                 Interpolators.FAST_OUT_SLOW_IN);
         boolean ignoreTaskOverrides = false;
         if (event.dropTarget instanceof TaskStack.DockState) {
+            mDraggingOverDockState = true;
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
@@ -1872,6 +1876,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             updateLayoutAlgorithm(true /* boundScroll */);
             ignoreTaskOverrides = true;
         } else {
+            mDraggingOverDockState = false;
             // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
             // task view, so add it back to the ignore set after updating the layout
             removeIgnoreTask(event.task);
@@ -1882,6 +1887,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
     }
 
     public final void onBusEvent(final DragEndEvent event) {
+        mDraggingOverDockState = false;
         // We don't handle drops on the dock regions
         if (event.dropTarget instanceof TaskStack.DockState) {
             // However, we do need to reset the overrides, since the last state of this task stack
@@ -1969,7 +1975,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
                 @Override
                 public void run() {
                     // Start the dozer to trigger to trigger any UI that shows after a timeout
-                    mUIDozeTrigger.startDozing();
+                    if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+                        mUIDozeTrigger.startDozing();
+                    }
 
                     // Update the focused state here -- since we only set the focused task without
                     // requesting view focus in onFirstLayout(), actually request view focus and
@@ -2049,6 +2057,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             }
         }
 
+        // Update the Clear All button in case we're switching in or out of grid layout.
+        updateStackActionButtonVisibility();
+
         // Trigger a new layout and update to the initial state if necessary
         if (event.fromMultiWindow) {
             mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
@@ -2119,6 +2130,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
     }
 
     /**
+     * Check whether we should use the grid layout.
+     * We use the grid layout for Recents iff all the following is true:
+     *  1. Grid-mode is enabled.
+     *  2. The activity is not in multi-window mode.
+     *  3. The user is not dragging a task view over the dock state.
+     * @return True if we should use the grid layout.
+     */
+    public boolean useGridLayout() {
+        return Recents.getConfiguration().isGridEnabled
+            && !((RecentsActivity) mContext).isInMultiWindowMode()
+            && !mDraggingOverDockState;
+    }
+
+    /**
      * Reads current system flags related to accessibility and screen pinning.
      */
     private void readSystemFlags() {
@@ -2128,6 +2153,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
                 Settings.System.LOCK_TO_APP_ENABLED) != 0;
     }
 
+    private void updateStackActionButtonVisibility() {
+        // Always show the button in grid layout.
+        if (useGridLayout() ||
+                (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+                        mStack.getTaskCount() > 0)) {
+            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+        } else {
+            EventBus.getDefault().send(new HideStackActionButtonEvent());
+        }
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "  ";
         String id = Integer.toHexString(System.identityHashCode(this));
index 115e65a..93ab0bd 100644 (file)
@@ -148,7 +148,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
     private ArrayList<Animator> mTmpAnimators = new ArrayList<>();
 
     @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_")
-    TaskViewThumbnail mThumbnailView;
+    protected TaskViewThumbnail mThumbnailView;
     @ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
     TaskViewHeader mHeaderView;
     private View mActionButtonView;
@@ -176,8 +176,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
         super(context, attrs, defStyleAttr, defStyleRes);
         RecentsConfiguration config = Recents.getConfiguration();
         Resources res = context.getResources();
-        mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
-                R.dimen.recents_task_view_shadow_rounded_corners_radius));
+        mViewBounds = createOutlineProvider();
         if (config.fakeShadows) {
             setBackground(new FakeShadowDrawable(res, config));
         }
@@ -194,7 +193,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
      * Called from RecentsActivity when it is relaunched.
      */
     void onReload(boolean isResumingFromVisible) {
-        resetNoUserInteractionState();
+        if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+            resetNoUserInteractionState();
+        }
         if (!isResumingFromVisible) {
             resetViewProperties();
         }
@@ -205,6 +206,12 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
         return mTask;
     }
 
+    /* Create an outline provider to clip and outline the view */
+    protected AnimateableViewBounds createOutlineProvider() {
+        return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize(
+            R.dimen.recents_task_view_shadow_rounded_corners_radius));
+    }
+
     /** Returns the view bounds. */
     AnimateableViewBounds getViewBounds() {
         return mViewBounds;
@@ -232,7 +239,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
     /**
      * Update the task view when the configuration changes.
      */
-    void onConfigurationChanged() {
+    protected void onConfigurationChanged() {
         mHeaderView.onConfigurationChanged();
     }
 
@@ -666,10 +673,16 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
     @Override
     public boolean onLongClick(View v) {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        // Since we are clipping the view to the bounds, manually do the hit test
+        boolean inBounds = false;
         Rect clipBounds = new Rect(mViewBounds.mClipBounds);
-        clipBounds.scale(getScaleX());
-        boolean inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
+        if (!clipBounds.isEmpty()) {
+            // If we are clipping the view to the bounds, manually do the hit test.
+            clipBounds.scale(getScaleX());
+            inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
+        } else {
+            // Otherwise just make sure we're within the view's bounds.
+            inBounds = mDownTouchPos.x <= getWidth() && mDownTouchPos.y <= getHeight();
+        }
         if (v == this && inBounds && !ssp.hasDockedTask()) {
             // Start listening for drag events
             setClipViewInStack(false);
index 691e599..c0cc83f 100644 (file)
@@ -290,14 +290,16 @@ public class TaskViewHeader extends FrameLayout
                 R.dimen.recents_task_view_header_height,
                 R.dimen.recents_task_view_header_height_tablet_land,
                 R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land);
+                R.dimen.recents_task_view_header_height_tablet_land,
+                R.dimen.recents_grid_task_view_header_height);
         int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
                 R.dimen.recents_task_view_header_button_padding,
                 R.dimen.recents_task_view_header_button_padding,
                 R.dimen.recents_task_view_header_button_padding,
                 R.dimen.recents_task_view_header_button_padding_tablet_land,
                 R.dimen.recents_task_view_header_button_padding,
-                R.dimen.recents_task_view_header_button_padding_tablet_land);
+                R.dimen.recents_task_view_header_button_padding_tablet_land,
+                R.dimen.recents_grid_task_view_header_button_padding);
         if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
             mHeaderBarHeight = headerBarHeight;
             mHeaderButtonPadding = headerButtonPadding;
@@ -603,10 +605,7 @@ public class TaskViewHeader extends FrameLayout
                     Constants.Metrics.DismissSourceHeaderButton);
         } else if (v == mMoveTaskButton) {
             TaskView tv = Utilities.findParent(this, TaskView.class);
-            Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID
-                    ? new Rect(mTaskViewRect)
-                    : new Rect();
-            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
+            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
                     mMoveTaskTargetStackId, false));
         } else if (v == mAppInfoView) {
             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
index c46adf1..16521f7 100644 (file)
@@ -60,6 +60,8 @@ public class TaskViewThumbnail extends View {
     @ViewDebug.ExportedProperty(category="recents")
     private float mThumbnailScale;
     private float mFullscreenThumbnailScale;
+    private boolean mSizeToFit = false;
+    private boolean mOverlayHeaderOnThumbnailActionBar = true;
     private ActivityManager.TaskThumbnailInfo mThumbnailInfo;
 
     private int mCornerRadius;
@@ -133,10 +135,12 @@ public class TaskViewThumbnail extends View {
                 (int) (mThumbnailRect.width() * mThumbnailScale));
         int thumbnailHeight = Math.min(viewHeight,
                 (int) (mThumbnailRect.height() * mThumbnailScale));
+
         if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
-            int topOffset = mTaskBar != null
-                    ? mTaskBar.getHeight() - mCornerRadius
-                    : 0;
+            int topOffset = 0;
+            if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) {
+                topOffset = mTaskBar.getHeight() - mCornerRadius;
+            }
 
             // Draw the background, there will be some small overdraw with the thumbnail
             if (thumbnailWidth < viewWidth) {
@@ -230,7 +234,7 @@ public class TaskViewThumbnail extends View {
                 // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing
                 // and only draw the background color
                 mThumbnailScale = 0f;
-            } else if (isStackTask) {
+            } else if (isStackTask && !mSizeToFit) {
                 float invThumbnailScale = 1f / mFullscreenThumbnailScale;
                 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
                     if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -262,6 +266,19 @@ public class TaskViewThumbnail extends View {
         }
     }
 
+    /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */
+    public void setSizeToFit(boolean flag) {
+        mSizeToFit = flag;
+    }
+
+    /**
+     * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or
+     * be stacked just above it.
+     */
+    public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
+        mOverlayHeaderOnThumbnailActionBar = flag;
+    }
+
     /** Updates the clip rect based on the given task bar. */
     void updateClipToTaskBar(View taskBar) {
         mTaskBar = taskBar;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
new file mode 100644 (file)
index 0000000..a029478
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.grid;
+
+import android.view.View;
+import com.android.systemui.recents.views.AnimateableViewBounds;
+
+/* An outline provider for grid-based task views. */
+class AnimateableGridViewBounds extends AnimateableViewBounds {
+
+    public AnimateableGridViewBounds(View source, int cornerRadius) {
+        super(source, cornerRadius);
+    }
+
+    @Override
+    protected void updateClipBounds() {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
new file mode 100644 (file)
index 0000000..6300400
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.grid;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import com.android.systemui.R;
+import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.recents.views.TaskView;
+
+public class GridTaskView extends TaskView {
+
+    /** The height, in pixels, of the header view. */
+    private int mHeaderHeight;
+
+    public GridTaskView(Context context) {
+        this(context, null);
+    }
+
+    public GridTaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mHeaderHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.recents_grid_task_view_header_height);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        // Show the full thumbnail and don't overlap with the header.
+        mThumbnailView.setSizeToFit(true);
+        mThumbnailView.setOverlayHeaderOnThumbnailActionBar(false);
+        mThumbnailView.updateThumbnailScale();
+        mThumbnailView.setTranslationY(mHeaderHeight);
+    }
+
+    @Override
+    protected AnimateableViewBounds createOutlineProvider() {
+        return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize(
+            R.dimen.recents_task_view_shadow_rounded_corners_radius));
+    }
+
+    @Override
+    protected void onConfigurationChanged() {
+        super.onConfigurationChanged();
+        mHeaderHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.recents_grid_task_view_header_height);
+        mThumbnailView.setTranslationY(mHeaderHeight);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
new file mode 100644 (file)
index 0000000..65a8ee2
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 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.grid;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewTransform;
+
+public class TaskGridLayoutAlgorithm  {
+
+    private final String TAG = "TaskGridLayoutAlgorithm";
+    private final int MAX_LAYOUT_TASK_COUNT = 8;
+
+    /** The horizontal padding around the whole recents view. */
+    private int mPaddingLeftRight;
+    /** The vertical padding around the whole recents view. */
+    private int mPaddingTopBottom;
+    /** The padding between task views. */
+    private int mPaddingTaskView;
+
+    private Rect mDisplayRect;
+    private Rect mWindowRect;
+    private Point mScreenSize = new Point();
+
+    private Rect mTaskGridRect;
+
+    /** The height, in pixels, of each task view's title bar. */
+    private int mTitleBarHeight;
+
+    /** The aspect ratio of each task thumbnail, without the title bar. */
+    private float mAppAspectRatio;
+    private Rect mSystemInsets = new Rect();
+
+    public TaskGridLayoutAlgorithm(Context context) {
+        reloadOnConfigurationChange(context);
+    }
+
+    public void reloadOnConfigurationChange(Context context) {
+        Resources res = context.getResources();
+        mPaddingLeftRight = res.getDimensionPixelSize(R.dimen.recents_grid_padding_left_right);
+        mPaddingTopBottom = res.getDimensionPixelSize(R.dimen.recents_grid_padding_top_bottom);
+        mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
+
+        mTaskGridRect = new Rect();
+        mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
+
+        WindowManager windowManager = (WindowManager) context
+                .getSystemService(Context.WINDOW_SERVICE);
+        windowManager.getDefaultDisplay().getRealSize(mScreenSize);
+
+        updateAppAspectRatio();
+    }
+
+    public TaskViewTransform getTransform(int taskIndex, int taskCount,
+        TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+
+        int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
+
+        // We also need to invert the index in order to display the most recent tasks first.
+        int taskLayoutIndex = taskCount - taskIndex - 1;
+
+        int tasksPerLine = layoutTaskCount < 2 ? 1 : (
+                layoutTaskCount < 5 ? 2 : (
+                        layoutTaskCount < 7 ? 3 : 4));
+        int lines = layoutTaskCount < 3 ? 1 : 2;
+
+        int taskWidth, taskHeight;
+        int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight
+                - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
+        int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom
+                - (lines - 1) * mPaddingTaskView) / lines;
+
+        if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
+            // Width bound.
+            taskWidth = maxTaskWidth;
+            taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight);
+        } else {
+            // Height bound.
+            taskHeight = maxTaskHeight;
+            taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio);
+        }
+        int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight
+                - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
+        int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom
+                - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
+
+        mTaskGridRect.set(0, 0, taskWidth, taskHeight);
+
+        int xIndex = taskLayoutIndex % tasksPerLine;
+        int yIndex = taskLayoutIndex / tasksPerLine;
+        int x = emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
+        int y = emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
+        float z = stackLayout.mMaxTranslationZ;
+
+        float dimAlpha = 0f;
+        float viewOutlineAlpha = 0f;
+        boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT);
+
+        // Fill out the transform
+        transformOut.scale = 1f;
+        transformOut.alpha = isTaskViewVisible ? 1f : 0f;
+        transformOut.translationZ = z;
+        transformOut.dimAlpha = dimAlpha;
+        transformOut.viewOutlineAlpha = viewOutlineAlpha;
+        transformOut.rect.set(mTaskGridRect);
+        transformOut.rect.offset(x, y);
+        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+        // We only show the 8 most recent tasks.
+        transformOut.visible = isTaskViewVisible;
+        return transformOut;
+    }
+
+    public void initialize(Rect displayRect, Rect windowRect) {
+        mDisplayRect = displayRect;
+        mWindowRect = windowRect;
+    }
+
+    public void setSystemInsets(Rect systemInsets) {
+        mSystemInsets = systemInsets;
+        updateAppAspectRatio();
+    }
+
+    private void updateAppAspectRatio() {
+        int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right;
+        int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom;
+        mAppAspectRatio = (float) usableWidth / (float) usableHeight;
+    }
+
+    public Rect getStackActionButtonRect() {
+        Rect buttonRect = new Rect(mDisplayRect);
+        buttonRect.right -= mPaddingLeftRight;
+        buttonRect.left += mPaddingLeftRight;
+        buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
+        return buttonRect;
+    }
+}