Add dismiss animation for Recents TV and Enable Dismiss

Redo rect calculations for new positions.

Refactor some code to make a RecentsTvImpl.

Change-Id: Ifa269fe18ea40ea9a102ec0207a6c7ab796e6f77
This commit is contained in:
Sid Soundararajan
2016-03-18 13:42:10 -07:00
parent 30db6d9325
commit 4bdb6879bd
19 changed files with 438 additions and 165 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

View File

@@ -28,12 +28,8 @@
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"

View File

@@ -21,9 +21,11 @@
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"
@@ -66,4 +68,30 @@
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>

View 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>

View 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>
@@ -29,10 +29,10 @@
<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>
@@ -43,4 +43,13 @@
<!-- 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>

View 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>

View File

@@ -35,7 +35,11 @@
<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>

View 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>

View 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)) {

View 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.
@@ -971,90 +932,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
mLastToggleTime = SystemClock.elapsedRealtime();
}
/**
* 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.
*/
@@ -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);

View 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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View 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() {

View 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);
}
}

View 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();
}
@@ -108,6 +107,13 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T
return mFocusedTask;
}
/**
* @return - The focused task card view.
*/
public TaskCardView getFocusedTaskCardView() {
return ((TaskCardView)findFocus());
}
/**
* @param task
* @return Child view for given task

View 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);
}
}