OSDN Git Service

Add dismiss animation for Recents TV and Enable Dismiss
authorSid Soundararajan <ssoundar@google.com>
Fri, 18 Mar 2016 20:42:10 +0000 (13:42 -0700)
committerSid Soundararajan <ssoundar@google.com>
Tue, 22 Mar 2016 18:10:28 +0000 (11:10 -0700)
Redo rect calculations for new positions.

Refactor some code to make a RecentsTvImpl.

Change-Id: Ifa269fe18ea40ea9a102ec0207a6c7ab796e6f77

19 files changed:
packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png [new file with mode: 0644]
packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png [new file with mode: 0644]
packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png [new file with mode: 0644]
packages/SystemUI/res/layout/recents_on_tv.xml
packages/SystemUI/res/layout/recents_tv_task_card_view.xml
packages/SystemUI/res/values/colors_tv.xml
packages/SystemUI/res/values/dimens_tv.xml
packages/SystemUI/res/values/integers_tv.xml
packages/SystemUI/res/values/strings_tv.xml
packages/SystemUI/res/values/values_tv.xml
packages/SystemUI/src/com/android/systemui/recents/Recents.java
packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java

diff --git a/packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png b/packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png
new file mode 100644 (file)
index 0000000..73f5116
Binary files /dev/null and b/packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png b/packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png
new file mode 100644 (file)
index 0000000..787e259
Binary files /dev/null and b/packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png b/packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png
new file mode 100644 (file)
index 0000000..6ebbc83
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png differ
index 567e009..a00c744 100644 (file)
         android:clipChildren="false"
         android:clipToPadding="false"
         android:descendantFocusability="beforeDescendants"
-        android:layout_gravity="center"
-        android:gravity="center"
-        android:paddingStart="@dimen/recents_tv_grid_row_padding"
-        android:paddingEnd="@dimen/recents_tv_grid_row_padding"
+        android:layout_marginTop="@dimen/recents_tv_gird_row_top_margin"
         android:focusable="true" />
-
     <View
         android:id="@+id/pip_shade"
         android:layout_width="match_parent"
index 54e97da..766ef60 100644 (file)
     android:focusableInTouchMode="true"
     android:layout_gravity="center"
     android:layout_centerInParent="true"
+    android:orientation="vertical"
     android:layoutDirection="ltr">
 
     <LinearLayout
+            android:id="@+id/recents_tv_card"
             android:layout_width="@dimen/recents_tv_card_width"
             android:layout_height="wrap_content"
             android:layout_centerInParent="true"
                 android:layout_centerHorizontal="true"
                 android:layout_below="@id/card_title_text" />
     </LinearLayout>
+    <LinearLayout
+            android:id="@+id/card_dismiss"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_gravity="center_horizontal"
+            android:layout_below="@id/recents_tv_card"
+            android:alpha="0.0">
+        <ImageView
+                android:id="@+id/card_dismiss_icon"
+                android:layout_width="@dimen/recents_tv_dismiss_icon_size"
+                android:layout_height="@dimen/recents_tv_dismiss_icon_size"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginTop="@dimen/recents_tv_dismiss_icon_top_margin"
+                android:layout_marginBottom="@dimen/recents_tv_dismiss_icon_bottom_margin"
+                android:src="@drawable/ic_cancel_white_24dp" />
+        <TextView
+                android:id="@+id/card_dismiss_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="@dimen/recents_tv_dismiss_text_size"
+                android:fontFamily="@string/font_roboto_light"
+                android:textColor="@color/recents_tv_dismiss_text_color"
+                android:text="@string/recents_tv_dismiss"
+                android:layout_gravity="center_horizontal" />
+    </LinearLayout>
 </com.android.systemui.recents.tv.views.TaskCardView>
\ No newline at end of file
index af99aae..4126d3c 100644 (file)
@@ -19,4 +19,5 @@
 <resources>
     <color name="recents_tv_card_background_color">#FF37474F</color>
     <color name="recents_tv_card_title_text_color">#CCEEEEEE</color>
+    <color name="recents_tv_dismiss_text_color">#7FEEEEEE</color>
 </resources>
\ No newline at end of file
index 6b153d1..953dd65 100644 (file)
@@ -18,8 +18,8 @@
 -->
 <resources>
     <!-- Dimens for recents card in the recents view on tv -->
-    <dimen name="recents_tv_card_width">268dip</dimen>
-    <dimen name="recents_tv_screenshot_height">151dip</dimen>
+    <dimen name="recents_tv_card_width">240dip</dimen>
+    <dimen name="recents_tv_screenshot_height">135dip</dimen>
     <dimen name="recents_tv_card_extra_badge_size">20dip</dimen>
     <dimen name="recents_tv_banner_width">114dip</dimen>
     <dimen name="recents_tv_banner_height">64dip</dimen>
     <dimen name="recents_tv_text_padding_bottom">12dip</dimen>
 
     <!-- Padding for grid view in recents view on tv -->
-    <dimen name="recents_tv_grid_row_padding">56dip</dimen>
-    <dimen name="recents_tv_gird_row_top_padding">57dip</dimen>
+    <dimen name="recents_tv_gird_row_top_margin">215dip</dimen>
     <dimen name="recents_tv_grid_max_row_height">268dip</dimen>
-    <dimen name="recents_tv_gird_card_spacing">20dip</dimen>
+    <dimen name="recents_tv_gird_card_spacing">8dip</dimen>
+    <dimen name="recents_tv_gird_focused_card_delta">44dip</dimen>
 
     <!-- Values for focus animation -->
     <dimen name="recents_tv_unselected_item_z">6dp</dimen>
 
     <!-- Values for text on recents cards on tv -->
     <dimen name="recents_tv_title_text_size">12sp</dimen>
+
+    <!-- Values for card dismiss state -->
+    <dimen name="recents_tv_dismiss_shift_down">48dip</dimen>
+    <dimen name="recents_tv_dismiss_top_margin">356dip</dimen>
+    <dimen name="recents_tv_dismiss_icon_size">24dip</dimen>
+    <dimen name="recents_tv_dismiss_icon_top_margin">38dip</dimen>
+    <dimen name="recents_tv_dismiss_icon_bottom_margin">1dip</dimen>
+    <dimen name="recents_tv_dismiss_text_size">12sp</dimen>
+
 </resources>
index bfd8f8b..c60c245 100644 (file)
@@ -15,4 +15,6 @@
 -->
 <resources>
     <integer name="item_scale_anim_duration">150</integer>
+    <integer name="dismiss_short_duration">200</integer>
+    <integer name="dismiss_long_duration">400</integer>
 </resources>
\ No newline at end of file
index 0e1fe8f..da6c279 100644 (file)
     <string name="pip_onboarding_description">Press and hold the HOME button to control PIP</string>
     <!-- Button to close picture-in-picture (PIP) onboarding screen. -->
     <string name="pip_onboarding_button">Got it</string>
+    <!-- Dismiss icon description -->
+    <string name="recents_tv_dismiss">Dismiss</string>
     <!-- Font for Recents -->
     <!-- DO NOT TRANSLATE -->
     <string name="font_roboto_regular" translatable="false">sans-serif</string>
+    <!-- DO NOT TRANSLATE -->
+    <string name="font_roboto_light" translatable="false">sans-serif-light</string>
 </resources>
index 6a72e54..bd72c51 100644 (file)
@@ -15,5 +15,5 @@ limitations under the License.
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <item format="float" type="integer" name="unselected_scale">1.0</item>
-    <item format="float" type="integer" name="selected_scale">1.1</item>
+    <item format="float" type="integer" name="selected_scale">1.259</item>
 </resources>
index 2b6ed44..da07aec 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.systemui.recents;
 
 import android.app.ActivityManager;
+import android.app.UiModeManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -53,6 +54,7 @@ import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 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.tv.RecentsTvImpl;
 
 import java.util.ArrayList;
 
@@ -182,7 +184,13 @@ public class Recents extends SystemUI
         sTaskLoader = new RecentsTaskLoader(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
         mHandler = new Handler();
-        mImpl = new RecentsImpl(mContext);
+        UiModeManager uiModeManager = (UiModeManager) mContext.
+                getSystemService(Context.UI_MODE_SERVICE);
+        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+            mImpl = new RecentsTvImpl(mContext);
+        } else {
+            mImpl = new RecentsImpl(mContext);
+        }
 
         // Check if there is a recents override package
         if ("userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE)) {
index 9be24de..880fe10 100644 (file)
@@ -21,12 +21,10 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ITaskStackListener;
-import android.app.UiModeManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -66,7 +64,6 @@ import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskGrouping;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.tv.views.TaskCardView;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackView;
 import com.android.systemui.recents.views.TaskStackViewScroller;
@@ -96,10 +93,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
 
     public final static String RECENTS_PACKAGE = "com.android.systemui";
     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
-    public final static String RECENTS_TV_ACTIVITY = "com.android.systemui.recents.tv.RecentsTvActivity";
-
-    //Used to store tv or non-tv activty for use in creating intents.
-    private final String mRecentsIntentActivityName;
 
     /**
      * An implementation of ITaskStackListener, that allows us to listen for changes to the system
@@ -158,16 +151,15 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         }
     }
 
-    private static RecentsTaskLoadPlan sInstanceLoadPlan;
+    protected static RecentsTaskLoadPlan sInstanceLoadPlan;
 
-    Context mContext;
-    Handler mHandler;
+    protected Context mContext;
+    protected Handler mHandler;
     TaskStackListenerImpl mTaskStackListener;
     RecentsAppWidgetHost mAppWidgetHost;
-    boolean mCanReuseTaskStackViews = true;
+    protected boolean mCanReuseTaskStackViews = true;
     boolean mDraggingInRecents;
     boolean mLaunchedWhileDocking;
-    private boolean mIsRunningOnTv;
 
     // Task launching
     Rect mSearchBarBounds = new Rect();
@@ -182,11 +174,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
     // Header (for transition)
     TaskViewHeader mHeaderBar;
     final Object mHeaderBarLock = new Object();
-    TaskStackView mDummyStackView;
+    protected TaskStackView mDummyStackView;
 
     // Variables to keep track of if we need to start recents after binding
-    boolean mTriggeredFromAltTab;
-    long mLastToggleTime;
+    protected boolean mTriggeredFromAltTab;
+    protected long mLastToggleTime;
     DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
         @Override
         public void run() {
@@ -197,7 +189,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         }
     });
 
-    Bitmap mThumbnailTransitionBitmapCache;
+    protected Bitmap mThumbnailTransitionBitmapCache;
     Task mThumbnailTransitionBitmapCacheKey;
 
     public RecentsImpl(Context context) {
@@ -227,16 +219,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
         launchOpts.onlyLoadForCache = true;
         loader.loadTasks(mContext, plan, launchOpts);
-
-        //Manager used to determine if we are running on tv or not
-        UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
-        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
-            mRecentsIntentActivityName = RECENTS_TV_ACTIVITY;
-            mIsRunningOnTv = true;
-        } else {
-            mRecentsIntentActivityName = RECENTS_ACTIVITY;
-            mIsRunningOnTv = false;
-        }
     }
 
     public void onBootCompleted() {
@@ -729,7 +711,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
     /**
      * Creates the activity options for a unknown state->recents transition.
      */
-    private ActivityOptions getUnknownTransitionActivityOptions() {
+    protected ActivityOptions getUnknownTransitionActivityOptions() {
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit,
@@ -739,7 +721,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
     /**
      * Creates the activity options for a home->recents transition.
      */
-    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
+    protected ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
         if (fromSearchHome) {
             return ActivityOptions.makeCustomAnimation(mContext,
                     R.anim.recents_from_search_launcher_enter,
@@ -797,22 +779,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         }
     }
 
-    /**
-     * Creates the activity options for an app->recents transition on TV.
-     */
-    private ActivityOptions getThumbnailTransitionActivityOptionsForTV(
-            ActivityManager.RunningTaskInfo topTask) {
-        Bitmap thumbnail = mThumbnailTransitionBitmapCache;
-        Rect rect = TaskCardView.getStartingCardThumbnailRect(mContext);
-        if (thumbnail != null) {
-            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    null, (int) rect.left, (int) rect.top,
-                    (int) rect.width(), (int) rect.height(), mHandler, null);
-        }
-        // If both the screenshot and thumbnail fails, then just fall back to the default transition
-        return getUnknownTransitionActivityOptions();
-    }
-
     private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
             TaskViewTransform toTransform) {
         Bitmap thumbnail;
@@ -888,15 +854,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
     /**
      * Shows the recents activity
      */
-    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+    protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
             boolean isTopTaskHome, boolean animate) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
 
-        // If we are on TV, divert to a different helper method
-        if (mIsRunningOnTv) {
-            setUpAndStartTvRecents(topTask, isTopTaskHome, animate);
-            return;
-        }
         // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
         // should always preload the tasks now. If we are dragging in recents, reload them as
         // the stacks might have changed.
@@ -972,90 +933,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
     }
 
     /**
-     * Used to set up the animations of Tv Recents, then start the Recents Activity.
-     * TODO: Add the Transitions for Home -> Recents TV
-     * TODO: Shift Transition code to separate class under /tv directory and access
-     *              from here
-     */
-    private void setUpAndStartTvRecents(ActivityManager.RunningTaskInfo topTask,
-                                      boolean isTopTaskHome, boolean animate) {
-        RecentsTaskLoader loader = Recents.getTaskLoader();
-
-        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
-        // should always preload the tasks now. If we are dragging in recents, reload them as
-        // the stacks might have changed.
-        if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
-            // Create a new load plan if preloadRecents() was never triggered
-            sInstanceLoadPlan = loader.createLoadPlan(mContext);
-        }
-        if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
-        }
-        TaskStack stack = sInstanceLoadPlan.getTaskStack();
-
-        // Update the header bar if necessary
-        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
-
-        // Prepare the dummy stack for the transition
-        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
-                mDummyStackView.computeStackVisibilityReport();
-
-        if (!animate) {
-            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
-            startRecentsActivity(topTask, opts, false /* fromHome */,
-                    false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
-            return;
-        }
-
-        boolean hasRecentTasks = stack.getTaskCount() > 0;
-        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
-
-        if (useThumbnailTransition) {
-            // Try starting with a thumbnail transition
-            ActivityOptions opts = getThumbnailTransitionActivityOptionsForTV(topTask);
-            if (opts != null) {
-                startRecentsActivity(topTask, opts, false /* fromHome */,
-                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
-            } else {
-                // Fall through below to the non-thumbnail transition
-                useThumbnailTransition = false;
-            }
-        }
-
-        if (!useThumbnailTransition) {
-            // If there is no thumbnail transition, but is launching from home into recents, then
-            // use a quick home transition and do the animation from home
-            if (hasRecentTasks) {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                String homeActivityPackage = ssp.getHomeActivityPackageName();
-                String searchWidgetPackage = null;
-                if (RecentsDebugFlags.Static.EnableSearchBar) {
-                    searchWidgetPackage = Prefs.getString(mContext,
-                            Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
-                } else {
-                    AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
-                    if (searchWidgetInfo != null) {
-                        searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
-                    }
-                }
-
-                // Determine whether we are coming from a search owned home activity
-                boolean fromSearchHome = (homeActivityPackage != null) &&
-                        homeActivityPackage.equals(searchWidgetPackage);
-                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
-                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
-                        false /* fromThumbnail */, stackVr);
-            } else {
-                // Otherwise we do the normal fade from an unknown source
-                ActivityOptions opts = getUnknownTransitionActivityOptions();
-                startRecentsActivity(topTask, opts, true /* fromHome */,
-                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
-            }
-        }
-        mLastToggleTime = SystemClock.elapsedRealtime();
-    }
-
-    /**
      * Starts the recents activity.
      */
     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
@@ -1078,7 +955,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
         launchState.launchedWhileDocking = mLaunchedWhileDocking;
 
         Intent intent = new Intent();
-        intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
+        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
index 532e796..f7807e5 100644 (file)
@@ -47,7 +47,6 @@ import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -73,6 +72,7 @@ import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
+import com.android.systemui.recents.tv.RecentsTvImpl;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -280,7 +280,7 @@ public class SystemServicesProxy {
             // Check if the front most activity is recents
             if ((topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
                     (topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY) ||
-                    topActivity.getClassName().equals(RecentsImpl.RECENTS_TV_ACTIVITY)))) {
+                    topActivity.getClassName().equals(RecentsTvImpl.RECENTS_TV_ACTIVITY)))) {
                 if (isHomeTopMost != null) {
                     isHomeTopMost.value = false;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
new file mode 100644 (file)
index 0000000..9fd5d55
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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.tv;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import com.android.systemui.recents.*;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.tv.views.TaskCardView;
+
+public class RecentsTvImpl extends RecentsImpl{
+    public final static String RECENTS_TV_ACTIVITY =
+            "com.android.systemui.recents.tv.RecentsTvActivity";
+
+    public RecentsTvImpl(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+            boolean isTopTaskHome, boolean animate) {
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+
+        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
+        // should always preload the tasks now. If we are dragging in recents, reload them as
+        // the stacks might have changed.
+        if (mTriggeredFromAltTab || sInstanceLoadPlan == null) {
+            // Create a new load plan if preloadRecents() was never triggered
+            sInstanceLoadPlan = loader.createLoadPlan(mContext);
+        }
+        if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
+            loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
+        }
+        TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+        if (!animate) {
+            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
+            startRecentsActivity(topTask, opts, false /* fromHome */, false /* fromThumbnail*/);
+            return;
+        }
+
+        boolean hasRecentTasks = stack.getTaskCount() > 0;
+        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
+
+        if (useThumbnailTransition) {
+            // Try starting with a thumbnail transition
+            ActivityOptions opts = getThumbnailTransitionActivityOptionsForTV(topTask);
+            if (opts != null) {
+                startRecentsActivity(topTask, opts, false /* fromHome */, true /* fromThumbnail */);
+            } else {
+                // Fall through below to the non-thumbnail transition
+                useThumbnailTransition = false;
+            }
+        }
+
+        if (!useThumbnailTransition) {
+            // If there is no thumbnail transition, but is launching from home into recents, then
+            // use a quick home transition and do the animation from home
+            if (hasRecentTasks) {
+                SystemServicesProxy ssp = Recents.getSystemServices();
+                ActivityOptions opts = getHomeTransitionActivityOptions(false);
+                startRecentsActivity(topTask, opts, true /* fromHome */, false /* fromThumbnail */);
+            } else {
+                // Otherwise we do the normal fade from an unknown source
+                ActivityOptions opts = getUnknownTransitionActivityOptions();
+                startRecentsActivity(topTask, opts, true /* fromHome */, false /* fromThumbnail */);
+            }
+        }
+        mLastToggleTime = SystemClock.elapsedRealtime();
+    }
+
+    protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+            ActivityOptions opts, boolean fromHome, boolean fromThumbnail) {
+        // Update the configuration based on the launch options
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
+        launchState.launchedFromHome = fromHome;
+        launchState.launchedFromSearchHome = false;
+        launchState.launchedFromApp = fromThumbnail;
+        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+        launchState.launchedWithAltTab = mTriggeredFromAltTab;
+        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+        launchState.launchedHasConfigurationChanged = false;
+
+        Intent intent = new Intent();
+        intent.setClassName(RECENTS_PACKAGE, RECENTS_TV_ACTIVITY);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+
+        if (opts != null) {
+            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
+        } else {
+            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        }
+        mCanReuseTaskStackViews = true;
+        EventBus.getDefault().send(new RecentsActivityStartingEvent());
+    }
+
+    /**
+     * Creates the activity options for an app->recents transition on TV.
+     */
+    private ActivityOptions getThumbnailTransitionActivityOptionsForTV(
+            ActivityManager.RunningTaskInfo topTask) {
+        Bitmap thumbnail = mThumbnailTransitionBitmapCache;
+        Rect rect = TaskCardView.getStartingCardThumbnailRect(mContext);
+        if (thumbnail != null) {
+            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+                    null, (int) rect.left, (int) rect.top,
+                    (int) rect.width(), (int) rect.height(), mHandler, null);
+        }
+        // If both the screenshot and thumbnail fails, then just fall back to the default transition
+        return getUnknownTransitionActivityOptions();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java
new file mode 100644 (file)
index 0000000..8996d0b
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.tv.animations;
+
+
+import android.animation.Animator;
+import android.content.res.Resources;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.systemui.recents.tv.views.TaskCardView;
+
+import com.android.systemui.R;
+
+public class DismissAnimationsHolder {
+    private LinearLayout mDismissArea;
+    private LinearLayout mTaskCardView;
+    private FastOutSlowInInterpolator mFastOutSlowIn;
+    private int mCardYDelta;
+    private long mShortDuration;
+    private long mLongDuration;
+
+    public DismissAnimationsHolder(TaskCardView taskCardView) {
+        mTaskCardView = (LinearLayout) taskCardView.findViewById(R.id.recents_tv_card);
+        mDismissArea = (LinearLayout) taskCardView.findViewById(R.id.card_dismiss);
+        mFastOutSlowIn = new FastOutSlowInInterpolator();
+
+        Resources res = taskCardView.getResources();
+        mCardYDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_shift_down);
+        mShortDuration =  res.getInteger(R.integer.dismiss_short_duration);
+        mLongDuration =  res.getInteger(R.integer.dismiss_long_duration);
+    }
+
+    public void startEnterAnimation() {
+        mDismissArea.animate().setDuration(mShortDuration);
+        mDismissArea.animate().setInterpolator(mFastOutSlowIn);
+        mDismissArea.animate().alpha(1.0f);
+
+        mTaskCardView.animate().setDuration(mShortDuration);
+        mTaskCardView.animate().setInterpolator(mFastOutSlowIn);
+        mTaskCardView.animate().translationYBy(mCardYDelta);
+        mTaskCardView.animate().alpha(0.5f);
+    }
+
+    public void startExitAnimation() {
+        mDismissArea.animate().setDuration(mShortDuration);
+        mDismissArea.animate().setInterpolator(mFastOutSlowIn);
+        mDismissArea.animate().alpha(0.0f);
+
+        mTaskCardView.animate().setDuration(mShortDuration);
+        mTaskCardView.animate().setInterpolator(mFastOutSlowIn);
+        mTaskCardView.animate().translationYBy(-mCardYDelta);
+        mTaskCardView.animate().alpha(1.0f);
+    }
+
+    public void startDismissAnimation(Animator.AnimatorListener listener) {
+        mDismissArea.animate().setDuration(mShortDuration);
+        mDismissArea.animate().setInterpolator(mFastOutSlowIn);
+        mDismissArea.animate().alpha(0.0f);
+
+        mTaskCardView.animate().setDuration(mLongDuration);
+        mTaskCardView.animate().setInterpolator(mFastOutSlowIn);
+        mTaskCardView.animate().translationYBy(mCardYDelta);
+        mTaskCardView.animate().alpha(0.0f);
+        mTaskCardView.animate().setListener(listener);
+    }
+}
index 365b29d..888561c 100644 (file)
@@ -33,6 +33,8 @@ public class ViewFocusAnimator implements View.OnFocusChangeListener {
     private final float mSelectedScaleDelta;
     private final float mUnselectedZ;
     private final float mSelectedZDelta;
+    private final float mUnselectedSpacing;
+    private final float mSelectedSpacingDelta;
     private final int mAnimDuration;
     private final Interpolator mFocusInterpolator;
 
@@ -57,6 +59,9 @@ public class ViewFocusAnimator implements View.OnFocusChangeListener {
         mUnselectedZ = res.getDimensionPixelOffset(R.dimen.recents_tv_unselected_item_z);
         mSelectedZDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_selected_item_z_delta);
 
+        mUnselectedSpacing = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_card_spacing);
+        mSelectedSpacingDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_focused_card_delta);
+
         mAnimDuration = res.getInteger(R.integer.item_scale_anim_duration);
 
         mFocusInterpolator = new AccelerateDecelerateInterpolator();
@@ -85,10 +90,14 @@ public class ViewFocusAnimator implements View.OnFocusChangeListener {
 
         float scale = mUnselectedScale + (level * mSelectedScaleDelta);
         float z = mUnselectedZ + (level * mSelectedZDelta);
+        float spacing = mUnselectedSpacing + (level * mSelectedSpacingDelta);
 
         mTargetView.setScaleX(scale);
         mTargetView.setScaleY(scale);
         mTargetView.setZ(z);
+
+        mTargetView.setPadding((int) spacing, mTargetView.getPaddingTop(),
+                (int) spacing, mTargetView.getPaddingBottom());
     }
 
     public float getFocusProgress() {
index 5775b60..3343aec 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.recents.tv.views;
 
+import android.animation.Animator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -22,12 +23,14 @@ import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.Display;
+import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.systemui.R;
+import com.android.systemui.recents.tv.animations.DismissAnimationsHolder;
 import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
 import com.android.systemui.recents.model.Task;
 
@@ -37,8 +40,10 @@ public class TaskCardView extends LinearLayout {
     private TextView mTitleTextView;
     private ImageView mBadgeView;
     private Task mTask;
+    private boolean mDismissState;
 
     private ViewFocusAnimator mViewFocusAnimator;
+    private DismissAnimationsHolder mDismissAnimationsHolder;
 
     public TaskCardView(Context context) {
         this(context, null);
@@ -51,6 +56,7 @@ public class TaskCardView extends LinearLayout {
     public TaskCardView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mViewFocusAnimator = new ViewFocusAnimator(this);
+        mDismissState = false;
     }
 
     @Override
@@ -58,6 +64,7 @@ public class TaskCardView extends LinearLayout {
         mThumbnailView = (ImageView) findViewById(R.id.card_view_thumbnail);
         mTitleTextView = (TextView) findViewById(R.id.card_title_text);
         mBadgeView = (ImageView) findViewById(R.id.card_extra_badge);
+        mDismissAnimationsHolder = new DismissAnimationsHolder(this);
     }
 
     public void init(Task task) {
@@ -98,13 +105,23 @@ public class TaskCardView extends LinearLayout {
 
         int width = res.getDimensionPixelOffset(R.dimen.recents_tv_card_width);
         int widthDelta = (int) (width * scale - width);
-        int height = (int) (res.getDimensionPixelOffset(
-                R.dimen.recents_tv_screenshot_height) * scale);
-        int padding = res.getDimensionPixelOffset(R.dimen.recents_tv_grid_row_padding);
+        int height = res.getDimensionPixelOffset(R.dimen.recents_tv_screenshot_height);
+        int heightDelta = (int) (height * scale - height);
+        int topMargin = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_row_top_margin);
 
-        int headerHeight = (int) ((res.getDimensionPixelOffset(
-                R.dimen.recents_tv_card_extra_badge_size) +
-                res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom)) * scale);
+        int headerHeight = res.getDimensionPixelOffset(R.dimen.recents_tv_card_extra_badge_size) +
+                res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom);
+        int headerHeightDelta = (int) (headerHeight * scale - headerHeight);
+
+        int dismissAreaHeight =
+                res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_top_margin) +
+                res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_bottom_margin) +
+                res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_size) +
+                res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_text_size);
+
+        int dismissAreaHeightDelta = (int) (dismissAreaHeight * scale - dismissAreaHeight);
+
+        int totalHeightDelta = heightDelta + headerHeightDelta + dismissAreaHeightDelta;
 
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = wm.getDefaultDisplay();
@@ -113,9 +130,72 @@ public class TaskCardView extends LinearLayout {
         int screenWidth = size.x;
         int screenHeight = size.y;
 
-        return new Rect(screenWidth - width - padding - widthDelta / 2,
-                screenHeight / 2 - height / 2 + headerHeight / 2,
-                screenWidth - padding + widthDelta / 2,
-                screenHeight / 2 + height / 2 + headerHeight / 2);
+        return new Rect(screenWidth / 2 - width / 2 - widthDelta / 2,
+                topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale),
+                screenWidth / 2 + width / 2 + widthDelta / 2,
+                topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale) +
+                        (int) (height * scale));
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_DOWN : {
+                if (!isInDismissState()) {
+                    setDismissState(true);
+                    return true;
+                }
+                break;
+            }
+            case KeyEvent.KEYCODE_DPAD_UP : {
+                if (isInDismissState()) {
+                    setDismissState(false);
+                    return true;
+                }
+                break;
+            }
+
+            //Eat right and left key presses when we are in dismiss state
+            case KeyEvent.KEYCODE_DPAD_LEFT : {
+                if (isInDismissState()) {
+                    return true;
+                }
+                break;
+            }
+            case KeyEvent.KEYCODE_DPAD_RIGHT : {
+                if (isInDismissState()) {
+                    return true;
+                }
+                break;
+            }
+            default:
+                break;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void setDismissState(boolean dismissState) {
+        if (mDismissState != dismissState) {
+            mDismissState = dismissState;
+            if (dismissState) {
+                mDismissAnimationsHolder.startEnterAnimation();
+            } else {
+                mDismissAnimationsHolder.startExitAnimation();
+            }
+        }
+    }
+
+    public boolean isInDismissState() {
+        return mDismissState;
+    }
+
+    public void startDismissTaskAnimation(Animator.AnimatorListener listener) {
+        mDismissAnimationsHolder.startDismissAnimation(listener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        setDismissState(false);
     }
 }
index 4458639..29a72da 100644 (file)
@@ -41,7 +41,6 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T
     private ArrayList<TaskCardView> mTaskViews = new ArrayList<>();
     private Task mFocusedTask;
 
-
     public TaskStackHorizontalGridView(Context context) {
         this(context, null);
     }
@@ -53,7 +52,7 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T
     @Override
     protected void onAttachedToWindow() {
         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-        setItemMargin((int) getResources().getDimension(R.dimen.recents_tv_gird_card_spacing));
+        setWindowAlignment(WINDOW_ALIGN_NO_EDGE);
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         super.onAttachedToWindow();
     }
@@ -109,6 +108,13 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T
     }
 
     /**
+     * @return - The focused task card view.
+     */
+    public TaskCardView getFocusedTaskCardView() {
+        return ((TaskCardView)findFocus());
+    }
+
+    /**
      * @param task
      * @return Child view for given task
      */
index fba424e..3788719 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.recents.tv.views;
 
+import android.animation.Animator;
 import android.app.Activity;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -25,6 +26,7 @@ import android.view.ViewGroup;
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.LaunchTvTaskEvent;
+import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
 import com.android.systemui.recents.model.Task;
 
 import java.util.ArrayList;
@@ -39,7 +41,7 @@ public class TaskStackHorizontalViewAdapter extends
     private static final String TAG = "TaskStackViewAdapter";
     private List<Task> mTaskList;
 
-    static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
+    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
         private TaskCardView mTaskCardView;
         private Task mTask;
         public ViewHolder(View v) {
@@ -58,9 +60,14 @@ public class TaskStackHorizontalViewAdapter extends
         @Override
         public void onClick(View v) {
             try {
-                EventBus.getDefault().send(new LaunchTvTaskEvent(mTaskCardView, mTask,
-                        null, INVALID_STACK_ID));
-                ((Activity)(v.getContext())).finish();
+                if (mTaskCardView.isInDismissState()) {
+                    mTaskCardView.startDismissTaskAnimation(
+                            getRemoveAtListener(getAdapterPosition(), mTaskCardView));
+                } else {
+                    EventBus.getDefault().send(new LaunchTvTaskEvent(mTaskCardView, mTask,
+                            null, INVALID_STACK_ID));
+                    ((Activity) (v.getContext())).finish();
+                }
             } catch (Exception e) {
                 Log.e(TAG, v.getContext()
                         .getString(R.string.recents_launch_error_message, mTask.title), e);
@@ -97,4 +104,31 @@ public class TaskStackHorizontalViewAdapter extends
     public int getItemCount() {
         return mTaskList.size();
     }
+
+    private Animator.AnimatorListener getRemoveAtListener(final int position,
+                                                          final TaskCardView taskCardView) {
+        return new Animator.AnimatorListener() {
+
+            @Override
+            public void onAnimationStart(Animator animation) { }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                removeAt(position);
+                EventBus.getDefault().send(new DeleteTaskDataEvent(taskCardView.getTask()));
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) { }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) { }
+        };
+
+    }
+
+    private void removeAt(int position) {
+        mTaskList.remove(position);
+        notifyItemRemoved(position);
+    }
 }