OSDN Git Service

DO NOT MERGE - 2-dimensional Recents activity.
authorManu Cornet <manucornet@google.com>
Fri, 11 Nov 2016 19:36:08 +0000 (11:36 -0800)
committerManu Cornet <manucornet@google.com>
Fri, 11 Nov 2016 20:57:07 +0000 (12:57 -0800)
This is a simple first version in the spirit of small, incremental CLs.
It is fully functional but the following will come in later changes:

* Split screen support
* Potential animations
* Alt-tab behavior
* Relayout on orientation changes

The new activity is only started when a specific system property is set.

Test: Tested new activity behavior on local Ryu. Added tests for layout logic.
Bug: 32101881
Merged-In: I550f6e7ea0de3937dbf80e5f0294676cfe567d47
Change-Id: I46a537646e98b312d831510e1d331948888ae5ce

packages/SystemUI/AndroidManifest.xml
packages/SystemUI/res/layout-sw600dp/recents_grid.xml [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/Recents.java
packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java [new file with mode: 0644]

index a74fbf8..ba845bd 100644 (file)
             </intent-filter>
         </activity>
 
+        <activity android:name=".recents.grid.RecentsGridActivity"
+                  android:label="@string/accessibility_desc_recent_apps"
+                  android:exported="false"
+                  android:launchMode="singleInstance"
+                  android:excludeFromRecents="true"
+                  android:stateNotNeeded="true"
+                  android:resumeWhilePausing="true"
+                  android:screenOrientation="behind"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:theme="@style/RecentsTheme.Wallpaper">
+            <intent-filter>
+                <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".recents.tv.RecentsTvActivity"
                   android:label="@string/accessibility_desc_recent_apps"
                   android:exported="false"
diff --git a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml b/packages/SystemUI/res/layout-sw600dp/recents_grid.xml
new file mode 100644 (file)
index 0000000..07ec237
--- /dev/null
@@ -0,0 +1,33 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:id="@+id/recents_container">
+    <include layout="@layout/recents_stack_action_button" />
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/recents_view"
+        android:layout_marginLeft="12dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginRight="12dp"
+        android:layout_marginBottom="5dp"
+        android:gravity="center">
+    </FrameLayout>
+</LinearLayout>
+
index ba50c66..eb9beb6 100644 (file)
@@ -57,6 +57,7 @@ import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.grid.RecentsGridImpl;
 import com.android.systemui.recents.tv.RecentsTvImpl;
 import com.android.systemui.stackdivider.Divider;
 
@@ -83,6 +84,7 @@ public class Recents extends SystemUI
     static {
         RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
         RECENTS_ACTIVITIES.add(RecentsTvImpl.RECENTS_TV_ACTIVITY);
+        RECENTS_ACTIVITIES.add(RecentsGridImpl.RECENTS_MOSAIC_ACTIVITY);
     }
 
     // Purely for experimentation
@@ -205,6 +207,8 @@ 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);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
new file mode 100644 (file)
index 0000000..cf45570
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * 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.grid;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsImpl;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
+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.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The main grid recents activity started by the RecentsImpl.
+ */
+public class RecentsGridActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
+    private final static String TAG = "RecentsGridActivity";
+
+    private TaskStack mTaskStack;
+    private List<Task> mTasks = new ArrayList<>();
+    private List<View> mTaskViews = new ArrayList<>();
+    private FrameLayout mRecentsView;
+    private TextView mEmptyView;
+    private View mClearAllButton;
+    private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private Rect mDisplayRect = new Rect();
+    private LayoutInflater mInflater;
+    private boolean mTouchExplorationEnabled;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.recents_grid);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+
+        mInflater = LayoutInflater.from(this);
+        mDisplayOrientation = Utilities.getAppConfiguration(this).orientation;
+        mDisplayRect = ssp.getDisplayRect();
+        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
+
+        mRecentsView = (FrameLayout) findViewById(R.id.recents_view);
+        LinearLayout recentsContainer = (LinearLayout) findViewById(R.id.recents_container);
+        mEmptyView = (TextView) mInflater.inflate(R.layout.recents_empty, recentsContainer, false);
+        mClearAllButton = findViewById(R.id.button);
+
+        FrameLayout.LayoutParams emptyViewLayoutParams = new FrameLayout.LayoutParams(
+            FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
+        emptyViewLayoutParams.gravity = Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
+        mEmptyView.setLayoutParams(emptyViewLayoutParams);
+        mRecentsView.addView(mEmptyView);
+
+        mClearAllButton.setVisibility(View.VISIBLE);
+        LinearLayout.LayoutParams lp =
+                (LinearLayout.LayoutParams) mClearAllButton.getLayoutParams();
+        lp.gravity = Gravity.END;
+
+        mClearAllButton.setOnClickListener(v -> {
+            EventBus.getDefault().send(new DismissAllTaskViewsEvent());
+        });
+
+        mRecentsView.setOnClickListener(v -> {
+            EventBus.getDefault().send(new HideRecentsEvent(
+                    false /* triggeredFromAltTab */, false /* triggeredFromHomeKey */));
+        });
+
+        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY);
+    }
+
+    private TaskView createView() {
+        return (TaskView) mInflater.inflate(R.layout.recents_task_view, mRecentsView, false);
+    }
+
+    private void clearTaskViews() {
+        for (View taskView : mTaskViews) {
+            ViewGroup parent = (ViewGroup) taskView.getParent();
+            if (parent != null) {
+                parent.removeView(taskView);
+            }
+        }
+        mTaskViews.clear();
+    }
+
+    private void updateControlVisibility() {
+        boolean empty = (mTasks.size() == 0);
+        mClearAllButton.setVisibility(empty ? View.INVISIBLE : View.VISIBLE);
+        mEmptyView.setVisibility(empty ? View.VISIBLE : View.INVISIBLE);
+        if (empty) {
+            mEmptyView.bringToFront();
+        }
+    }
+
+    private void updateRecentsTasks() {
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
+        if (plan == null) {
+            plan = loader.createLoadPlan(this);
+        }
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
+        if (!plan.hasTasks()) {
+            loader.preloadTasks(plan, -1, !launchState.launchedFromHome);
+        }
+        int numVisibleTasks = 9;
+        mTaskStack = plan.getTaskStack();
+        RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
+        loadOpts.runningTaskId = launchState.launchedToTaskId;
+        loadOpts.numVisibleTasks = numVisibleTasks;
+        loadOpts.numVisibleTaskThumbnails = numVisibleTasks;
+        loader.loadTasks(this, plan, loadOpts);
+
+        List<Task> stackTasks = mTaskStack.getStackTasks();
+        Collections.reverse(stackTasks);
+        mTasks = stackTasks;
+
+        updateControlVisibility();
+
+        clearTaskViews();
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            TaskView taskView = createView();
+            taskView.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect);
+            Recents.getTaskLoader().loadTaskData(task);
+            taskView.setTouchEnabled(true);
+            // Show dismiss button right away.
+            taskView.startNoUserInteractionAnimation();
+            mTaskViews.add(taskView);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
+        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateRecentsTasks();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        EventBus.getDefault().unregister(this);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Back behaves like the recents button so just trigger a toggle event.
+        EventBus.getDefault().send(new ToggleRecentsEvent());
+    }
+
+    @Override
+    public boolean onPreDraw() {
+        mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
+        int width = mRecentsView.getWidth();
+        int height = mRecentsView.getHeight();
+
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+            mTasks.size(), width, height, false /* allowLineOfThree */, 30 /* padding */);
+        for (int i = 0; i < rects.size(); i++) {
+            Rect rect = rects.get(i);
+            View taskView = mTaskViews.get(i);
+            taskView.setLayoutParams(new FrameLayout.LayoutParams(rect.width(), rect.height()));
+            taskView.setTranslationX(rect.left);
+            taskView.setTranslationY(rect.top);
+            mRecentsView.addView(taskView);
+        }
+        return true;
+    }
+
+    void dismissRecentsToHome() {
+        Intent startMain = new Intent(Intent.ACTION_MAIN);
+        startMain.addCategory(Intent.CATEGORY_HOME);
+        startActivity(startMain);
+    }
+
+    /**** EventBus events ****/
+
+    public final void onBusEvent(HideRecentsEvent event) {
+        if (event.triggeredFromAltTab) {
+            // Do nothing for now.
+        } else if (event.triggeredFromHomeKey) {
+            dismissRecentsToHome();
+        }
+    }
+
+    public final void onBusEvent(ToggleRecentsEvent event) {
+        // Always go back home for simplicity for now. If recents is entered from another app, this
+        // code will eventually need to go back to the original app.
+        dismissRecentsToHome();
+    }
+
+    public final void onBusEvent(DismissTaskViewEvent event) {
+        int taskIndex = mTaskViews.indexOf(event.taskView);
+        if (taskIndex != -1) {
+            mTasks.remove(taskIndex);
+            ((ViewGroup) event.taskView.getParent()).removeView(event.taskView);
+            mTaskViews.remove(taskIndex);
+            EventBus.getDefault().send(
+                    new TaskViewDismissedEvent(event.taskView.getTask(), event.taskView, null));
+        }
+    }
+
+    public final void onBusEvent(TaskViewDismissedEvent event) {
+        mRecentsView.announceForAccessibility(this.getString(
+            R.string.accessibility_recents_item_dismissed, event.task.title));
+        updateControlVisibility();
+
+        EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+
+        MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS,
+                event.task.key.getComponent().toString());
+    }
+
+    public final void onBusEvent(DeleteTaskDataEvent event) {
+        // Remove any stored data from the loader.
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        loader.deleteTaskData(event.task, false);
+
+        // Remove the task from activity manager.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.removeTask(event.task.key.id);
+    }
+
+    public final void onBusEvent(final DismissAllTaskViewsEvent event) {
+        // Keep track of the tasks which will have their data removed.
+        ArrayList<Task> tasks = new ArrayList<>(mTaskStack.getStackTasks());
+        mRecentsView.announceForAccessibility(this.getString(
+                R.string.accessibility_recents_all_items_dismissed));
+        mTaskStack.removeAllTasks();
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
+        }
+        mTasks = new ArrayList<>();
+        updateRecentsTasks();
+
+        MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS_ALL);
+    }
+
+    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (!ssp.hasDockedTask()) {
+            dismissRecentsToHome();
+        }
+    }
+
+    public final void onBusEvent(LaunchNextTaskRequestEvent event) {
+        // Always go back home for simplicity for now. Quick switch will be supported soon.
+        EventBus.getDefault().send(new HideRecentsEvent(false, true));
+    }
+
+    public final void onBusEvent(LaunchTaskEvent event) {
+        startActivity(event.task.key.baseIntent);
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java
new file mode 100644 (file)
index 0000000..ba7a4a7
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.grid;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import com.android.systemui.recents.RecentsImpl;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+
+public class RecentsGridImpl extends RecentsImpl {
+    public static final String RECENTS_MOSAIC_ACTIVITY =
+            "com.android.systemui.recents.grid.RecentsGridActivity";
+
+    public RecentsGridImpl(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
+            boolean isHomeStackVisible, boolean animate, int growTarget) {
+        Intent intent = new Intent();
+        intent.setClassName(RECENTS_PACKAGE, RECENTS_MOSAIC_ACTIVITY);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        EventBus.getDefault().send(new RecentsActivityStartingEvent());
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java
new file mode 100644 (file)
index 0000000..6e2c6c0
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * 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.grid;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class TaskGridLayoutAlgorithm {
+
+    private static final String TAG = "TaskGridLayoutAlgorithm";
+
+    static List<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight,
+            boolean allowLineOfThree, int padding) {
+        return getRectsForTaskCount(count, containerWidth, containerHeight, allowLineOfThree,
+                padding, null);
+    }
+
+    static List<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight,
+            boolean allowLineOfThree, int padding, Rect preCalculatedTile) {
+        int singleLineMaxCount = allowLineOfThree ? 3 : 2;
+        List<Rect> rects = new ArrayList<>(count);
+        boolean landscape = (containerWidth > containerHeight);
+
+        // We support at most 9 tasks in this layout.
+        count = Math.min(count, 9);
+
+        if (count == 0) {
+            return rects;
+        }
+        if (count <= singleLineMaxCount) {
+            if (landscape) {
+                // Single line.
+                int taskWidth = 0;
+                int emptySpace = 0;
+                if (preCalculatedTile != null) {
+                    taskWidth = preCalculatedTile.width();
+                    emptySpace = containerWidth - (count * taskWidth) - (count - 1) * padding;
+                } else {
+                    // Divide available space in equal parts.
+                    taskWidth = (containerWidth - (count - 1) * padding) / count;
+                }
+                for (int i = 0; i < count; i++) {
+                    int left = emptySpace / 2 + i * taskWidth + i * padding;
+                    rects.add(new Rect(left, 0, left + taskWidth, containerHeight));
+                }
+            } else {
+                // Single column. Divide available space in equal parts.
+                int taskHeight = (containerHeight - (count - 1) * padding) / count;
+                for (int i = 0; i < count; i++) {
+                    int top = i * taskHeight + i * padding;
+                    rects.add(new Rect(0, top, containerWidth, top + taskHeight));
+                }
+            }
+        } else if (count < 7) {
+            // Two lines.
+            int lineHeight = (containerHeight - padding) / 2;
+            int lineTaskCount = (int) Math.ceil((double) count / 2);
+            List<Rect> rectsA = getRectsForTaskCount(
+                    lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */, padding,
+                            null);
+            List<Rect> rectsB = getRectsForTaskCount(
+                    count - lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */,
+                            padding, rectsA.get(0));
+            for (Rect rect : rectsB) {
+                rect.offset(0, lineHeight + padding);
+            }
+            rects.addAll(rectsA);
+            rects.addAll(rectsB);
+        } else {
+            // Three lines.
+            int lineHeight = (containerHeight - 2 * padding) / 3;
+            int lineTaskCount = (int) Math.ceil((double) count / 3);
+            List<Rect> rectsA = getRectsForTaskCount(
+                lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */, padding, null);
+            List<Rect> rectsB = getRectsForTaskCount(
+                lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */, padding,
+                        rectsA.get(0));
+            List<Rect> rectsC = getRectsForTaskCount(
+                count - (2 * lineTaskCount), containerWidth, lineHeight,
+                         true /* allowLineOfThree */, padding, rectsA.get(0));
+            for (Rect rect : rectsB) {
+                rect.offset(0, lineHeight + padding);
+            }
+            for (Rect rect : rectsC) {
+                rect.offset(0, 2 * (lineHeight + padding));
+            }
+            rects.addAll(rectsA);
+            rects.addAll(rectsB);
+            rects.addAll(rectsC);
+        }
+        return rects;
+    }
+}
+
index 4ecdd77..115e65a 100644 (file)
@@ -360,12 +360,12 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
     }
 
     /** Enables/disables handling touch on this task view. */
-    void setTouchEnabled(boolean enabled) {
+    public 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() {
+    public void startNoUserInteractionAnimation() {
         mHeaderView.startNoUserInteractionAnimation();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java
new file mode 100644 (file)
index 0000000..e5a74ae
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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.grid;
+
+import android.graphics.Rect;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.systemui.SysuiTestCase;
+
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+@SmallTest
+public class TaskGridLayoutAlgorithmTest extends SysuiTestCase {
+
+    public void testOneTile() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                1, 1000, 500, false /* allowLineOfThree */, 0 /* padding */);
+        assertEquals(1, rects.size());
+        Rect singleRect = rects.get(0);
+        assertEquals(1000, singleRect.width());
+    }
+
+    public void testTwoTilesLandscape() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                2, 1200, 500, false /* allowLineOfThree */, 0 /* padding */);
+        assertEquals(2, rects.size());
+        for (Rect rect : rects) {
+            assertEquals(600, rect.width());
+            assertEquals(500, rect.height());
+        }
+    }
+
+    public void testTwoTilesLandscapeWithPadding() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                2, 1200, 500, false /* allowLineOfThree */, 10 /* padding */);
+        assertEquals(2, rects.size());
+        Rect rectA = rects.get(0);
+        Rect rectB = rects.get(1);
+        assertEquals(595, rectA.width());
+        assertEquals(595, rectB.width());
+        assertEquals(605, rectB.left);
+    }
+
+    public void testTwoTilesPortrait() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                2, 500, 1200, false /* allowLineOfThree */, 0 /* padding */);
+        assertEquals(2, rects.size());
+        for (Rect rect : rects) {
+            assertEquals(500, rect.width());
+            assertEquals(600, rect.height());
+        }
+    }
+
+    public void testThreeTiles() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                3, 1200, 500, false /* allowLineOfThree */, 0 /* padding */);
+        assertEquals(3, rects.size());
+        for (Rect rect : rects) {
+            assertEquals(600, rect.width());
+            assertEquals(250, rect.height());
+        }
+        // The third tile should be on the second line, in the middle.
+        Rect rectC = rects.get(2);
+        assertEquals(300, rectC.left);
+        assertEquals(250, rectC.top);
+    }
+
+    public void testFourTiles() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                4, 1200, 500, false /* allowLineOfThree */, 0 /* padding */);
+        assertEquals(4, rects.size());
+        for (Rect rect : rects) {
+            assertEquals(600, rect.width());
+            assertEquals(250, rect.height());
+        }
+        Rect rectD = rects.get(3);
+        assertEquals(600, rectD.left);
+        assertEquals(250, rectD.top);
+    }
+
+    public void testNineTiles() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                9, 1200, 600, false /* allowLineOfThree */, 0 /* padding */);
+        assertEquals(9, rects.size());
+        for (Rect rect : rects) {
+            assertEquals(400, rect.width());
+            assertEquals(200, rect.height());
+        }
+        Rect rectE = rects.get(4);
+        assertEquals(400, rectE.left);
+        assertEquals(200, rectE.top);
+        Rect rectI = rects.get(8);
+        assertEquals(800, rectI.left);
+        assertEquals(400, rectI.top);
+    }
+}
+