Merge changes I91c58397,I7243f30b,I0f6e9dbf,I1eb0864a

* changes:
  Adding reveal transition to show app-overlay.
  Fixing issue with multiple focus timer animations running.
  Fixing several regressions with affiliated tasks.
  Starting the dismiss animation in parallel with the gesture.
This commit is contained in:
Winson Chung
2016-01-22 00:11:52 +00:00
committed by Android (Google) Code Review
28 changed files with 1065 additions and 379 deletions

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48.0dp"
android:height="48.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/recents_task_bar_dark_icon_color"
android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48.0dp"
android:height="48.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"
android:fillColor="#FFFFFF"/>
</vector>

View File

@@ -64,12 +64,19 @@
android:background="@drawable/recents_button_bg"
android:visibility="invisible"
android:src="@drawable/recents_dismiss_light" />
<ProgressBar
android:id="@+id/focus_timer_indicator"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="bottom"
android:indeterminateOnly="false"
android:visibility="invisible" />
<!-- The progress indicator shows if auto-paging is enabled -->
<ViewStub android:id="@+id/focus_timer_indicator_stub"
android:inflatedId="@+id/focus_timer_indicator"
android:layout="@layout/recents_task_view_header_progress_bar"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="bottom" />
<!-- The app overlay shows as the user long-presses on the app icon -->
<ViewStub android:id="@+id/app_overlay_stub"
android:inflatedId="@+id/app_overlay"
android:layout="@layout/recents_task_view_header_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.recents.views.TaskViewHeader>

View File

@@ -0,0 +1,53 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/app_icon"
android:contentDescription="@string/recents_app_info_button_label"
android:layout_width="@dimen/recents_task_view_application_icon_size"
android:layout_height="@dimen/recents_task_view_application_icon_size"
android:layout_marginStart="8dp"
android:layout_gravity="center_vertical|start"
android:padding="8dp" />
<TextView
android:id="@+id/app_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="64dp"
android:layout_marginEnd="112dp"
android:textSize="16sp"
android:textColor="#ffffffff"
android:text="@string/recents_empty_message"
android:fontFamily="sans-serif-medium"
android:singleLine="true"
android:maxLines="2"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/app_info"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:layout_gravity="center_vertical|end"
android:padding="12dp"
android:background="@drawable/recents_button_bg"
android:src="@drawable/recents_info_light" />
</FrameLayout>

View File

@@ -0,0 +1,22 @@
<?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.
-->
<ProgressBar
xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminateOnly="false"
android:visibility="gone" />

View File

@@ -35,7 +35,7 @@
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is focused. -->
<item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
<item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
<item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
<!-- Recents: The relative range of visible tasks from the current scroll position

View File

@@ -147,6 +147,9 @@
<!-- The duration for animating the task decorations in after transitioning from an app. -->
<integer name="recents_task_enter_from_app_duration">200</integer>
<!-- The duration for animating the task decorations in after transitioning from an app. -->
<integer name="recents_task_enter_from_affiliated_app_duration">125</integer>
<!-- The duration for animating the task decorations out before transitioning to an app. -->
<integer name="recents_task_exit_to_app_duration">125</integer>
@@ -194,7 +197,7 @@
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is focused. -->
<item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
<item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
<item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
<!-- Recents: The relative range of visible tasks from the current scroll position

View File

@@ -210,7 +210,7 @@
<dimen name="recents_task_view_highlight">1dp</dimen>
<!-- The amount to offset when animating into an affiliate group. -->
<dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
<dimen name="recents_task_view_affiliate_group_enter_offset">32dp</dimen>
<!-- The height of a task view bar. -->
<dimen name="recents_task_bar_height">56dp</dimen>

View File

@@ -367,6 +367,7 @@ public class SwipeHelper implements Gefingerpoken {
}
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
mCallback.onChildDismissed(view);
if (endAction != null) {
endAction.run();
@@ -381,9 +382,17 @@ public class SwipeHelper implements Gefingerpoken {
updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
}
});
prepareDismissAnimation(animView, anim);
anim.start();
}
/**
* Called to update the dismiss animation.
*/
protected void prepareDismissAnimation(View view, Animator anim) {
// Do nothing
}
public void snapChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
@@ -401,14 +410,14 @@ public class SwipeHelper implements Gefingerpoken {
mCallback.onChildSnappedBack(animView);
}
});
updateSnapBackAnimation(anim);
prepareSnapBackAnimation(animView, anim);
anim.start();
}
/**
* Called to update the snap back animation.
*/
protected void updateSnapBackAnimation(Animator anim) {
protected void prepareSnapBackAnimation(View view, Animator anim) {
// Do nothing
}

View File

@@ -183,7 +183,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!plan.hasTasks()) {
loader.preloadTasks(plan, launchState.launchedFromHome);
loader.preloadTasks(plan, -1, launchState.launchedFromHome);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
@@ -192,24 +192,14 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
loader.loadTasks(this, plan, loadOpts);
TaskStack stack = plan.getTaskStack();
ArrayList<Task> tasks = stack.getStackTasks();
int taskCount = stack.getTaskCount();
mRecentsView.setTaskStack(stack);
// Mark the task that is the launch target
int launchTaskIndexInStack = 0;
if (launchState.launchedToTaskId != -1) {
for (int j = 0; j < taskCount; j++) {
Task t = tasks.get(j);
if (t.key.id == launchState.launchedToTaskId) {
t.isLaunchTarget = true;
launchTaskIndexInStack = tasks.size() - j - 1;
break;
}
}
}
// Animate the SystemUI scrims into view
Task launchTarget = stack.getLaunchTarget();
int taskCount = stack.getTaskCount();
int launchTaskIndexInStack = launchTarget != null
? stack.indexOfStackTask(launchTarget)
: 0;
boolean hasStatusBarScrim = taskCount > 0;
boolean animateStatusBarScrim = launchState.launchedFromHome;
boolean hasNavBarScrim = (taskCount > 0) && !config.hasTransposedNavBar;
@@ -527,7 +517,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
launchOpts.loadThumbnails = false;
launchOpts.onlyLoadForCache = true;
RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
loader.preloadTasks(loadPlan, false);
loader.preloadTasks(loadPlan, -1, false);
loader.loadTasks(this, loadPlan, launchOpts);
EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
}

View File

@@ -37,6 +37,8 @@ public class RecentsDebugFlags implements TunerService.Tunable {
public static final boolean EnableSearchBar = false;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// Enables the task affiliations
public static final boolean EnableAffiliatedTaskGroups = true;
// Enables the simulated task affiliations
public static final boolean EnableSimulatedTaskGroups = false;
// Defines the number of mock task affiliations per group

View File

@@ -118,7 +118,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
// Load the next task only if we aren't svelte
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true);
loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
// This callback is made when a new activity is launched and the old one is paused
// so ignore the current activity and try and preload the thumbnail for the
@@ -198,7 +198,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getIconCacheSize();
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
@@ -370,7 +370,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
sInstanceLoadPlan = loader.createLoadPlan(mContext);
if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// We try and draw the thumbnail transition bitmap in parallel before
@@ -399,7 +399,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -451,7 +451,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -756,29 +756,18 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
// Find the running task in the TaskStack
Task task = null;
ArrayList<Task> tasks = stack.getStackTasks();
if (runningTaskId != -1) {
// Otherwise, try and find the task with the
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
Task t = tasks.get(i);
if (t.key.id == runningTaskId) {
task = t;
runningTaskOut.copyFrom(t);
break;
}
}
}
if (task == null) {
Task launchTask = stack.getLaunchTarget();
if (launchTask != null) {
runningTaskOut.copyFrom(launchTask);
} else {
// If no task is specified or we can not find the task just use the front most one
task = tasks.get(tasks.size() - 1);
runningTaskOut.copyFrom(task);
launchTask = stack.getStackFrontMostTask();
runningTaskOut.copyFrom(launchTask);
}
// Get the transform for the running task
stackView.getScroller().setStackScrollToInitialState();
mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
mTmpTransform = stackView.getStackAlgorithm().getStackTransform(launchTask,
stackView.getScroller().getStackScroll(), mTmpTransform, null);
return mTmpTransform;
}
@@ -826,7 +815,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();

View File

@@ -36,6 +36,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
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.TaskViewAnimation;
import java.util.ArrayList;
import java.util.Calendar;
@@ -143,7 +144,6 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd
private Context mContext;
private LayoutInflater mLayoutInflater;
private final List<Task> mTasks = new ArrayList<>();
private final List<Row> mRows = new ArrayList<>();
private final SparseIntArray mTaskRowCount = new SparseIntArray();
private TaskStack mStack;
@@ -268,8 +268,8 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd
public void onTaskRemoved(Task task, int position) {
// Since this is removed from the history, we need to update the stack as well to ensure
// that the model is correct
mStack.removeTask(task);
// that the model is correct. Since the stack is hidden, we can update it immediately.
mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
removeTaskRow(position);
if (mRows.isEmpty()) {
dismissHistory();

View File

@@ -0,0 +1,52 @@
/*
* 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.misc;
import android.animation.TypeEvaluator;
import android.graphics.RectF;
/**
* This evaluator can be used to perform type interpolation between <code>RectF</code> values.
*/
public class RectFEvaluator implements TypeEvaluator<RectF> {
private RectF mRect = new RectF();
/**
* This function returns the result of linearly interpolating the start and
* end Rect values, with <code>fraction</code> representing the proportion
* between the start and end values. The calculation is a simple parametric
* calculation on each of the separate components in the Rect objects
* (left, top, right, and bottom).
*
* <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start Rect
* @param endValue The end Rect
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
@Override
public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
float left = startValue.left + ((endValue.left - startValue.left) * fraction);
float top = startValue.top + ((endValue.top - startValue.top) * fraction);
float right = startValue.right + ((endValue.right - startValue.right) * fraction);
float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
mRect.set(left, top, right, bottom);
return mRect;
}
}

View File

@@ -30,6 +30,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -554,39 +555,41 @@ public class SystemServicesProxy {
}
}
/** Returns the activity label */
public String getActivityLabel(ActivityInfo info) {
/**
* Returns the activity label, badging if necessary.
*/
public String getBadgedActivityLabel(ActivityInfo info, int userId) {
if (mPm == null) return null;
// If we are mocking, then return a mock label
if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
return "Recent Task";
return "Recent Task: " + userId;
}
return info.loadLabel(mPm).toString();
return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
}
/** Returns the application label */
public String getApplicationLabel(Intent baseIntent, int userId) {
/**
* Returns the application label, badging if necessary.
*/
public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
if (mPm == null) return null;
// If we are mocking, then return a mock label
if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
return "Recent Task";
return "Recent Task App: " + userId;
}
ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId);
CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null;
return (label != null) ? label.toString() : null;
return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
}
/** Returns the content description for a given task */
public String getContentDescription(Intent baseIntent, int userId, String activityLabel,
Resources res) {
String applicationLabel = getApplicationLabel(baseIntent, userId);
if (applicationLabel == null) {
return getBadgedLabel(activityLabel, userId);
}
/**
* Returns the content description for a given task, badging it if necessary. The content
* description joins the app and activity labels.
*/
public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) {
String activityLabel = info.loadLabel(mPm).toString();
String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
: res.getString(R.string.accessibility_recents_task_header,
@@ -609,6 +612,22 @@ public class SystemServicesProxy {
return getBadgedIcon(icon, userId);
}
/**
* Returns the application icon for the ApplicationInfo for a user, badging if
* necessary.
*/
public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
if (mPm == null) return null;
// If we are mocking, then return a mock label
if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
return new ColorDrawable(0xFF666666);
}
Drawable icon = appInfo.loadIcon(mPm);
return getBadgedIcon(icon, userId);
}
/**
* Returns the task description icon, loading and badging it if it necessary.
*/

View File

@@ -25,7 +25,8 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -92,7 +93,7 @@ public class RecentsTaskLoadPlan {
}
/**
* An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
* An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
* to most-recent order.
*/
public synchronized void preloadRawTasks(boolean isTopTaskHome) {
@@ -107,7 +108,7 @@ public class RecentsTaskLoadPlan {
}
/**
* Preloads the list of recent tasks from the system. After this call, the TaskStack will
* Preloads the list of recent tasks from the system. After this call, the TaskStack will
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
*
@@ -115,13 +116,16 @@ public class RecentsTaskLoadPlan {
* - least-recent to most-recent stack tasks
* - least-recent to most-recent freeform tasks
*/
public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
public synchronized void preloadPlan(RecentsTaskLoader loader, int topTaskId,
boolean isTopTaskHome) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
SparseIntArray affiliatedTaskCounts = new SparseIntArray();
String dismissDescFormat = mContext.getString(
R.string.accessibility_recents_item_will_be_dismissed);
long lastStackActiveTime = Prefs.getLong(mContext,
@@ -131,6 +135,17 @@ public class RecentsTaskLoadPlan {
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Affiliated tasks are returned in a specific order from ActivityManager but without a
// lastActiveTime since it hasn't yet been started. However, we later sort the task list
// by lastActiveTime, which rearranges the tasks. For now, we need to workaround this
// by updating the lastActiveTime of this task to the lastActiveTime of the task it is
// affiliated with, in the same order that we encounter it in the original list (just
// its index in the task group for the task it is affiliated with).
if (t.persistentId != t.affiliatedTaskId) {
t.lastActiveTime = affiliatedTasks.get(t.affiliatedTaskId).lastActiveTime +
affiliatedTaskCounts.get(t.affiliatedTaskId, 0) + 1;
}
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
@@ -140,13 +155,14 @@ public class RecentsTaskLoadPlan {
boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
boolean isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
(t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)));
boolean isLaunchTarget = taskKey.id == topTaskId;
if (isStackTask && newLastStackActiveTime < 0) {
newLastStackActiveTime = t.lastActiveTime;
}
// Load the title, icon, and color
String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
String contentDescription = loader.getAndUpdateContentDescription(taskKey, title, res);
String contentDescription = loader.getAndUpdateContentDescription(taskKey, res);
String dismissDescription = String.format(dismissDescFormat, contentDescription);
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
@@ -157,9 +173,11 @@ public class RecentsTaskLoadPlan {
// Add the task to the stack
Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
thumbnail, title, contentDescription, dismissDescription, activityColor,
!isStackTask, t.bounds, t.taskDescription);
!isStackTask, isLaunchTarget, t.bounds, t.taskDescription);
allTasks.add(task);
affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
affiliatedTasks.put(taskKey.id, taskKey);
}
if (newLastStackActiveTime != -1) {
Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,

View File

@@ -314,8 +314,8 @@ public class RecentsTaskLoader {
}
/** Preloads recents tasks using the specified plan to store the output. */
public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
plan.preloadPlan(this, isTopTaskHome);
public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) {
plan.preloadPlan(this, topTaskId, isTopTaskHome);
}
/** Begins loading the heavy task data according to the specified options. */
@@ -436,7 +436,7 @@ public class RecentsTaskLoader {
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
label = ssp.getActivityLabel(activityInfo);
label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
mActivityLabelCache.put(taskKey, label);
return label;
}
@@ -449,8 +449,7 @@ public class RecentsTaskLoader {
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
Resources res) {
String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached content description if it exists
@@ -458,13 +457,11 @@ public class RecentsTaskLoader {
if (label != null) {
return label;
}
// If the given activity label is empty, don't compute or cache the content description
if (activityLabel.isEmpty()) {
return "";
}
label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
if (label != null) {
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
mContentDescriptionCache.put(taskKey, label);
return label;
}

View File

@@ -154,7 +154,8 @@ public class Task {
public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
Bitmap thumbnail, String title, String contentDescription,
String dismissDescription, int colorPrimary, boolean isHistorical,
Rect bounds, ActivityManager.TaskDescription taskDescription) {
boolean isLaunchTarget, Rect bounds,
ActivityManager.TaskDescription taskDescription) {
boolean isInAffiliationGroup = (affiliationTaskId != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
this.key = key;
@@ -170,6 +171,7 @@ public class Task {
Color.WHITE) > 3f;
this.bounds = bounds;
this.taskDescription = taskDescription;
this.isLaunchTarget = isLaunchTarget;
this.isHistorical = isHistorical;
}

View File

@@ -42,6 +42,7 @@ import com.android.systemui.recents.misc.NamedCounter;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskViewAnimation;
import java.util.ArrayList;
import java.util.Collections;
@@ -226,12 +227,12 @@ public class TaskStack {
* Notifies when a task has been removed from the stack.
*/
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask);
Task newFrontMostTask, TaskViewAnimation animation);
/**
* Notifies when a task has been removed from the history.
*/
void onHistoryTaskRemoved(TaskStack stack, Task removedTask);
void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
}
/**
@@ -520,21 +521,24 @@ public class TaskStack {
}
}
/** Removes a task */
public void removeTask(Task t) {
/**
* Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
* how they should update themselves.
*/
public void removeTask(Task t, TaskViewAnimation animation) {
if (mStackTaskList.contains(t)) {
boolean wasFrontMostTask = (getStackFrontMostTask() == t);
removeTaskImpl(mStackTaskList, t);
Task newFrontMostTask = getStackFrontMostTask();
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask, animation);
}
} else if (mHistoryTaskList.contains(t)) {
removeTaskImpl(mHistoryTaskList, t);
if (mCb != null) {
// Notify that a task has been removed
mCb.onHistoryTaskRemoved(this, t);
mCb.onHistoryTaskRemoved(this, t, animation);
}
}
mRawTaskList.remove(t);
@@ -564,7 +568,8 @@ public class TaskStack {
Task task = mRawTaskList.get(i);
if (!newTasksMap.containsKey(task.key)) {
if (notifyStackChanges) {
mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null);
mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
TaskViewAnimation.IMMEDIATE);
}
}
task.setGroup(null);
@@ -843,12 +848,17 @@ public class TaskStack {
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
TaskGrouping group;
int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
IndividualTaskIdOffset + t.key.id;
if (mAffinitiesGroups.containsKey(affiliation)) {
group = getGroupWithAffiliation(affiliation);
if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
IndividualTaskIdOffset + t.key.id;
if (mAffinitiesGroups.containsKey(affiliation)) {
group = getGroupWithAffiliation(affiliation);
} else {
group = new TaskGrouping(affiliation);
addGroup(group);
}
} else {
group = new TaskGrouping(affiliation);
group = new TaskGrouping(t.key.id);
addGroup(group);
}
group.addTask(t);

View File

@@ -513,7 +513,7 @@ public class RecentsView extends FrameLayout {
taskViewRect.right, taskViewRect.bottom);
// Remove the task view after it is docked
mTaskStackView.updateLayout(false /* boundScroll */);
mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
null);
tmpTransform.alpha = 0;
@@ -529,11 +529,14 @@ public class RecentsView extends FrameLayout {
ssp.startTaskInDockedMode(getContext(), event.taskView,
event.task.key.id, dockState.createMode);
mTaskStackView.getStack().removeTask(event.task);
// Animate the stack accordingly
TaskViewAnimation stackAnim = new TaskViewAnimation(
TaskStackView.DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator);
mTaskStackView.getStack().removeTask(event.task, stackAnim);
}
}));
MetricsLogger.action(mContext,
MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP);
} else {

View File

@@ -130,6 +130,7 @@ public class TaskStackAnimationHelper {
// Move the task view slightly lower so we can animate it in
RectF bounds = new RectF(mTmpTransform.rect);
bounds.offset(0, taskViewAffiliateGroupEnterOffset);
tv.setClipViewInStack(false);
tv.setAlpha(0f);
tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top,
(int) bounds.right, (int) bounds.bottom);
@@ -165,6 +166,8 @@ public class TaskStackAnimationHelper {
int taskViewEnterFromAppDuration = res.getInteger(
R.integer.recents_task_enter_from_app_duration);
int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
R.integer.recents_task_enter_from_affiliated_app_duration);
int taskViewEnterFromHomeDuration = res.getInteger(
R.integer.recents_task_enter_from_home_duration);
int taskViewEnterFromHomeStaggerDelay = res.getInteger(
@@ -174,7 +177,7 @@ public class TaskStackAnimationHelper {
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
final TaskView tv = taskViews.get(i);
Task task = tv.getTask();
boolean currentTaskOccludesLaunchTarget = false;
if (launchTargetTask != null) {
@@ -195,8 +198,14 @@ public class TaskStackAnimationHelper {
// Animate the task up if it was occluding the launch target
if (currentTaskOccludesLaunchTarget) {
TaskViewAnimation taskAnimation = new TaskViewAnimation(
taskViewEnterFromAppDuration, PhoneStatusBar.ALPHA_IN,
postAnimationTrigger.decrementOnAnimationEnd());
taskViewEnterFromAffiliatedAppDuration, PhoneStatusBar.ALPHA_IN,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
postAnimationTrigger.decrement();
tv.setClipViewInStack(false);
}
});
postAnimationTrigger.increment();
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
@@ -286,7 +295,7 @@ public class TaskStackAnimationHelper {
} else if (currentTaskOccludesLaunchTarget) {
// Animate this task out of view
TaskViewAnimation taskAnimation = new TaskViewAnimation(
taskViewExitToAppDuration, mFastOutLinearInInterpolator,
taskViewExitToAppDuration, PhoneStatusBar.ALPHA_OUT,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();

View File

@@ -373,7 +373,7 @@ public class TaskStackLayoutAlgorithm {
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
void update(TaskStack stack, ArraySet<Task> ignoreTasksSet) {
void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
@@ -393,7 +393,7 @@ public class TaskStackLayoutAlgorithm {
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (ignoreTasksSet.contains(task)) {
if (ignoreTasksSet.contains(task.key)) {
continue;
}
if (task.isFreeformTask()) {
@@ -434,19 +434,25 @@ public class TaskStackLayoutAlgorithm {
mFreeformLayoutAlgorithm.update(freeformTasks, this);
mInitialScrollP = mMaxScrollP;
} else {
Task launchTask = stack.getLaunchTarget();
int launchTaskIndex = launchTask != null
? stack.indexOfStackTask(launchTask)
: mNumStackTasks - 1;
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
mInitialScrollP = mMinScrollP;
} else if (getDefaultFocusState() > 0f) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchState.launchedFromHome) {
mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, launchTaskIndex));
} else {
mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2);
mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
launchTaskIndex - 1));
}
} else {
float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height();
float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX);
mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX)));
}
}
}
@@ -553,7 +559,8 @@ public class TaskStackLayoutAlgorithm {
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null,
false /* ignoreSingleTaskCase */);
float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
@@ -596,13 +603,21 @@ public class TaskStackLayoutAlgorithm {
return transformOut;
}
return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
frontTransform);
frontTransform, false /* ignoreSingleTaskCase */);
}
}
/** Update/get the transform */
/**
* Update/get the transform.
*
* @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
* into account the special single-task case. This is only used
* internally to ensure that we can calculate the transform for any
* position in the stack.
*/
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform) {
TaskViewTransform transformOut, TaskViewTransform frontTransform,
boolean ignoreSingleTaskCase) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Compute the focused and unfocused offset
@@ -632,7 +647,7 @@ public class TaskStackLayoutAlgorithm {
int y;
float z;
float relP;
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
p = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -762,8 +777,8 @@ public class TaskStackLayoutAlgorithm {
mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin);
float max = mUnfocusedRange.relativeMax +
mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax);
getStackTransform(min, 0f, mBackOfStackTransform, null);
getStackTransform(max, 0f, mFrontOfStackTransform, null);
getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */);
getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */);
mBackOfStackTransform.visible = true;
mFrontOfStackTransform.visible = true;
}

View File

@@ -30,6 +30,7 @@ import android.os.Parcelable;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.MutableBoolean;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -77,7 +78,6 @@ import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -100,10 +100,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
private static final int DEFAULT_SYNC_STACK_DURATION = 200;
public static final int DEFAULT_SYNC_STACK_DURATION = 200;
private static final int DRAG_SCALE_DURATION = 175;
private static final float DRAG_SCALE_FACTOR = 1.05f;
private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScroller mStackScroller;
@@ -116,7 +118,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
ArrayList<TaskView> mTaskViews = new ArrayList<>();
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
TaskViewAnimation mDeferredTaskViewUpdateAnimation = null;
ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
DozeTrigger mUIDozeTrigger;
Task mFocusedTask;
@@ -137,7 +140,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int[] mTmpVisibleRange = new int[2];
Rect mTmpRect = new Rect();
ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
ArraySet<Task> mTmpTaskSet = new ArraySet<>();
List<TaskView> mTmpTaskViews = new ArrayList<>();
TaskViewTransform mTmpTransform = new TaskViewTransform();
LayoutInflater mInflater;
@@ -345,30 +347,82 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
* Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
* This call ignores freeform tasks.
* Adds a task to the ignored set.
*/
private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
ArrayList<Task> tasks, float stackScroll,
int[] visibleRangeOut, ArraySet<Task> ignoreTasksSet) {
int taskTransformCount = taskTransforms.size();
void addIgnoreTask(Task task) {
mIgnoreTasks.add(task.key);
}
/**
* Removes a task from the ignored set.
*/
void removeIgnoreTask(Task task) {
mIgnoreTasks.remove(task.key);
}
/**
* Returns whether the specified {@param task} is ignored.
*/
boolean isIgnoredTask(Task task) {
return mIgnoreTasks.contains(task.key);
}
/**
* Computes the task transforms at the current stack scroll for all visible tasks. If a valid
* target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
* visible range includes all tasks at the target stack scroll. This is useful for ensure that
* all views necessary for a transition or animation will be visible at the start.
*
* This call ignores freeform tasks.
*
* @param taskTransforms The set of task view transforms to reuse, this list will be sized to
* match the size of {@param tasks}
* @param tasks The set of tasks for which to generate transforms
* @param curStackScroll The current stack scroll
* @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
* The range of the union of the visible views at the current and
* target stack scrolls will be returned.
* @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
* Transforms will still be calculated for the ignore tasks.
*/
boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) {
int taskCount = tasks.size();
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
// We can reuse the task transforms where possible to reduce object allocation
Utilities.matchTaskListSize(tasks, taskTransforms);
// Update the stack transforms
TaskViewTransform frontTransform = null;
TaskViewTransform frontTransformAtTarget = null;
TaskViewTransform transform = null;
TaskViewTransform transformAtTarget = null;
for (int i = taskCount - 1; i >= 0; i--) {
Task task = tasks.get(i);
if (ignoreTasksSet.contains(task)) {
continue;
// Calculate the current and (if necessary) the target transform for the task
transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
taskTransforms.get(i), frontTransform);
if (useTargetStackScroll && !transform.visible) {
// If we have a target stack scroll and the task is not currently visible, then we
// just update the transform at the new scroll
// TODO: Optimize this
transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
if (transformAtTarget.visible) {
transform.copyFrom(transformAtTarget);
}
}
TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
taskTransforms.get(i), frontTransform);
// For ignore tasks, only calculate the stack transform and skip the calculation of the
// visible stack indices
if (ignoreTasksSet.contains(task.key)) {
continue;
}
// For freeform tasks, only calculate the stack transform and skip the calculation of
// the visible stack indices
@@ -392,7 +446,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
break;
}
}
frontTransform = transform;
frontTransformAtTarget = transformAtTarget;
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
@@ -402,33 +458,48 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
* Updates the children {@link TaskView}s to match the tasks in the current {@link TaskStack}.
* This call does not update the {@link TaskView}s to their position in the layout except when
* they are initially picked up from the pool, when they will be placed in a suitable initial
* position.
* Binds the visible {@link TaskView}s at the given target scroll.
*
* @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>)
*/
private void bindTaskViewsWithStack(ArraySet<Task> ignoreTasksSet) {
final float stackScroll = mStackScroller.getStackScroll();
void bindVisibleTaskViews(float targetStackScroll) {
bindVisibleTaskViews(targetStackScroll, mIgnoreTasks);
}
/**
* Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
* current {@link TaskStack}. This call does not continue on to update their position to the
* computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
* be added/removed from the view hierarchy and placed in the correct Z order and initial
* position (if not currently on screen).
*
* @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
* includes those visible at the current stack scroll, and all at the
* target stack scroll.
* @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible
* {@link TaskView}s
*/
void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) {
final int[] visibleStackRange = mTmpVisibleRange;
// Get all the task transforms
final ArrayList<Task> tasks = mStack.getStackTasks();
final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms,
tasks, stackScroll, visibleStackRange, ignoreTasksSet);
final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms,
tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange,
ignoreTasksSet);
// Return all the invisible children to the pool
final List<TaskView> taskViews = getTaskViews();
final int taskViewCount = taskViews.size();
int lastFocusedTaskIndex = -1;
mTmpTaskViewMap.clear();
mTmpTaskViewMap.ensureCapacity(tasks.size());
List<TaskView> taskViews = getTaskViews();
int lastFocusedTaskIndex = -1;
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
final TaskView tv = taskViews.get(i);
final Task task = tv.getTask();
final int taskIndex = mStack.indexOfStackTask(task);
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = mStack.indexOfStackTask(task);
// Skip ignored tasks
if (ignoreTasksSet.contains(task)) {
if (ignoreTasksSet.contains(task.key)) {
continue;
}
@@ -445,13 +516,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Pick up all the newly visible children
int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0;
for (int i = mStack.getTaskCount() - 1; i >= lastVisStackIndex; i--) {
final Task task = tasks.get(i);
final TaskViewTransform transform = mCurrentTaskTransforms.get(i);
int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0;
for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = mCurrentTaskTransforms.get(i);
// Skip ignored tasks
if (ignoreTasksSet.contains(task)) {
if (ignoreTasksSet.contains(task.key)) {
continue;
}
@@ -502,26 +573,34 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
* Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its
* current position as defined by the {@link TaskStackLayoutAlgorithm}.
* Relayout the the visible {@link TaskView}s to their current transforms as specified by the
* {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
* animations that are current running on those task views, and will ensure that the children
* {@link TaskView}s will match the set of visible tasks in the stack.
*
* @param ignoreTasks the set of tasks to ignore in the relayout
* @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
*/
private void updateTaskViewsToLayout(TaskViewAnimation animation, Task... ignoreTasks) {
// Keep track of the ignore tasks
ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
ignoreTasksSet.clear();
ignoreTasksSet.ensureCapacity(ignoreTasks.length);
Collections.addAll(ignoreTasksSet, ignoreTasks);
void relayoutTaskViews(TaskViewAnimation animation) {
relayoutTaskViews(animation, mIgnoreTasks);
}
/**
* Relayout the the visible {@link TaskView}s to their current transforms as specified by the
* {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
* animations that are current running on those task views, and will ensure that the children
* {@link TaskView}s will match the set of visible tasks in the stack.
*
* @param ignoreTasksSet the set of tasks to ignore in the relayout
*/
void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
// If we had a deferred animation, cancel that
mDeferredTaskViewUpdateAnimation = null;
mDeferredTaskViewLayoutAnimation = null;
// Cancel all task view animations
cancelAllTaskViewAnimations();
// Fetch the current set of TaskViews
bindTaskViewsWithStack(ignoreTasksSet);
// Synchronize the current set of TaskViews
bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet);
// Animate them to their final transforms with the given animation
List<TaskView> taskViews = getTaskViews();
@@ -531,7 +610,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
final int taskIndex = mStack.indexOfStackTask(tv.getTask());
final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
if (ignoreTasksSet.contains(tv.getTask())) {
if (ignoreTasksSet.contains(tv.getTask().key)) {
continue;
}
@@ -542,8 +621,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
*/
private void updateTaskViewsToLayoutOnNextFrame(TaskViewAnimation animation) {
mDeferredTaskViewUpdateAnimation = animation;
void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
mDeferredTaskViewLayoutAnimation = animation;
postInvalidateOnAnimation();
}
@@ -558,13 +637,62 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
* Cancels all {@link TaskView} animations.
* Returns the current task transforms of all tasks, falling back to the stack layout if there
* is no {@link TaskView} for the task.
*/
private void cancelAllTaskViewAnimations() {
public void getCurrentTaskTransforms(ArrayList<Task> tasks,
ArrayList<TaskViewTransform> transformsOut) {
Utilities.matchTaskListSize(tasks, transformsOut);
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
TaskView tv = getChildViewForTask(task);
if (tv != null) {
transform.fillIn(tv);
} else {
mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
transform, null);
}
transform.visible = true;
}
}
/**
* Returns the task transforms for all the tasks in the stack if the stack was at the given
* {@param stackScroll}.
*/
public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks,
ArrayList<TaskViewTransform> transformsOut) {
Utilities.matchTaskListSize(tasks, transformsOut);
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
transform.visible = true;
}
}
/**
* Cancels all {@link TaskView} animations.
*
* @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
*/
void cancelAllTaskViewAnimations() {
cancelAllTaskViewAnimations(mIgnoreTasks);
}
/**
* Cancels all {@link TaskView} animations.
*
* @param ignoreTasksSet The set of tasks to continue running their animations.
*/
void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) {
List<TaskView> taskViews = getTaskViews();
for (int i = taskViews.size() - 1; i >= 0; i--) {
final TaskView tv = taskViews.get(i);
tv.cancelTransformAnimation();
if (!ignoreTasksSet.contains(tv.getTask().key)) {
tv.cancelTransformAnimation();
}
}
}
@@ -577,11 +705,22 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Update the clip on each task child
List<TaskView> taskViews = getTaskViews();
TaskView tmpTv = null;
TaskView prevVisibleTv = null;
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
TaskView frontTv = null;
int clipBottom = 0;
if (mIgnoreTasks.contains(tv.getTask().key)) {
// For each of the ignore tasks, update the translationZ of its TaskView to be
// between the translationZ of the tasks immediately underneath it
if (prevVisibleTv != null) {
tv.setTranslationZ(Math.max(tv.getTranslationZ(),
prevVisibleTv.getTranslationZ() + 0.1f));
}
}
if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
// Find the next view to clip against
for (int j = i + 1; j < taskViewCount; j++) {
@@ -609,33 +748,37 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (!config.useHardwareLayers) {
tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
}
prevVisibleTv = tv;
}
mTaskViewsClipDirty = false;
}
/**
* Updates the layout algorithm min and max virtual scroll bounds.
*
* @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
*/
void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
}
/**
* Updates the min and max virtual scroll bounds.
*
* @param ignoreTasks the set of tasks to ignore in the relayout
* @param ignoreTasksSet the set of tasks to ignore in the relayout
*/
void updateLayout(boolean boundScrollToNewMinMax, Task... ignoreTasks) {
// Keep track of the ingore tasks
ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
ignoreTasksSet.clear();
ignoreTasksSet.ensureCapacity(ignoreTasks.length);
Collections.addAll(ignoreTasksSet, ignoreTasks);
void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
ArraySet<Task.TaskKey> ignoreTasksSet) {
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack, ignoreTasksSet);
// Update the freeform workspace
// Update the freeform workspace background
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
mFreeformWorkspaceBackground.setBounds(mTmpRect);
}
// Debug logging
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
}
@@ -671,8 +814,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Reset the last focused task state if changed
if (mFocusedTask != null) {
resetFocusedTask(mFocusedTask);
// Cancel the timer indicator, if applicable
if (showTimerIndicator) {
final TaskView tv = getChildViewForTask(mFocusedTask);
@@ -680,6 +821,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.getHeaderView().cancelFocusTimerIndicator();
}
}
resetFocusedTask(mFocusedTask);
}
boolean willScroll = false;
@@ -937,41 +1080,27 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
if (mDeferredTaskViewUpdateAnimation != null) {
updateTaskViewsToLayout(mDeferredTaskViewUpdateAnimation);
if (mDeferredTaskViewLayoutAnimation != null) {
relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
mTaskViewsClipDirty = true;
mDeferredTaskViewUpdateAnimation = null;
mDeferredTaskViewLayoutAnimation = null;
}
if (mTaskViewsClipDirty) {
clipTaskViews();
}
}
/**
* Computes the stack and task rects.
*
* @param ignoreTasks the set of tasks to ignore in the relayout
*/
public void computeRects(Rect taskStackBounds, boolean boundScroll, Task... ignoreTasks) {
// Compute the rects in the stack algorithm
mLayoutAlgorithm.initialize(taskStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
// Update the scroll bounds
updateLayout(boundScroll, ignoreTasks);
}
/**
* This is ONLY used from the Recents component to update the dummy stack view for purposes
* of getting the task rect to animate to.
*/
public void updateLayoutForStack(TaskStack stack) {
mStack = stack;
updateLayout(false);
updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
@@ -1002,16 +1131,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// Compute our stack/task rects
computeRects(mStackBounds, false);
// Compute the rects in the stack algorithm
mLayoutAlgorithm.initialize(mStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
// If this is the first layout, then scroll to the front of the stack, then update the
// TaskViews with the stack so that we can lay them out
if (mAwaitingFirstLayout) {
mStackScroller.setStackScrollToInitialState();
}
mTmpTaskSet.clear();
bindTaskViewsWithStack(mTmpTaskSet);
// Rebind all the views, including the ignore ones
bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET);
// Measure each of the TaskViews
mTmpTaskViews.clear();
@@ -1066,7 +1197,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller.boundScroll();
}
}
updateTaskViewsToLayout(TaskViewAnimation.IMMEDIATE);
// Relayout all of the task views including the ignored ones
relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
clipTaskViews();
if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
@@ -1088,9 +1220,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
Task launchTask = mStack.getLaunchTarget();
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
int focusedTaskIndex = launchTask != null
? mStack.indexOfStackTask(launchTask)
: launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
if (focusedTaskIndex != -1) {
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
false /* requestViewFocus */);
@@ -1106,8 +1241,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public boolean isTouchPointInView(float x, float y, TaskView tv) {
return (tv.getLeft() <= x && x <= tv.getRight()) &&
(tv.getTop() <= y && y <= tv.getBottom());
mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
return mTmpRect.contains((int) x, (int) y);
}
/**
* Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
* calculating the scroll position before and after a layout change.
*/
public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
// Ignore deleting tasks
if (mIgnoreTasks.contains(task.key)) {
if (i == tasks.size() - 1) {
isFrontMostTask.value = true;
}
continue;
}
return task;
}
return null;
}
@Override
@@ -1152,70 +1308,38 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onStackTaskAdded(TaskStack stack, Task newTask) {
// Update the min/max scroll and animate other task views into their new positions
updateLayout(true);
updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
}
/**
* We expect that the {@link TaskView} associated with the removed task is already hidden.
*/
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask) {
Task newFrontMostTask, TaskViewAnimation animation) {
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
}
if (!removedTask.isFreeformTask()) {
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
TaskView tv = getChildViewForTask(removedTask);
if (tv != null) {
mViewPool.returnViewToPool(tv);
}
// Get the stack scroll of the task to anchor to (since we are removing something, the
// front most task will be our anchor task)
Task anchorTask = mStack.getStackFrontMostTask();
float prevAnchorTaskScroll = 0;
if (anchorTask != null) {
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Update the min/max scroll and animate other task views into their new positions
updateLayout(true);
if (wasFrontMostTask) {
// Since the max scroll progress is offset from the bottom of the stack, just scroll
// to ensure that the new front most task is now fully visible
mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
} else if (anchorTask != null) {
// Otherwise, offset the scroll by the movement of the anchor task
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
// If we are focused, we don't want the front task to move, but otherwise, we
// allow the back task to move up, and the front task to move back
stackScrollOffset /= 2;
}
mStackScroller.setStackScroll(mStackScroller.getStackScroll() + stackScrollOffset);
mStackScroller.boundScroll();
}
} else {
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
TaskView tv = getChildViewForTask(removedTask);
if (tv != null) {
mViewPool.returnViewToPool(tv);
}
// Update the min/max scroll and animate other task views into their new positions
updateLayout(true);
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
TaskView tv = getChildViewForTask(removedTask);
if (tv != null) {
mViewPool.returnViewToPool(tv);
}
// Animate all the tasks into place
updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
// Remove the task from the ignored set
removeIgnoreTask(removedTask);
// If requested, relayout with the given animation
if (animation != null) {
updateLayoutAlgorithm(true /* boundScroll */);
relayoutTaskViews(animation);
}
// Update the new front most task's action button
if (mScreenPinningEnabled && newFrontMostTask != null) {
@@ -1232,7 +1356,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
public void onHistoryTaskRemoved(TaskStack stack, Task removedTask) {
public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
TaskViewAnimation animation) {
// To be implemented
}
@@ -1316,7 +1441,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onTaskViewClipStateChanged(TaskView tv) {
clipTaskViews();
if (!mTaskViewsClipDirty) {
mTaskViewsClipDirty = true;
invalidate();
}
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@@ -1324,7 +1452,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
mUIDozeTrigger.poke();
updateTaskViewsToLayoutOnNextFrame(animation);
if (animation != null) {
relayoutTaskViewsOnNextFrame(animation);
}
if (shouldShowHistoryButton() &&
prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
@@ -1354,7 +1484,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.dismissTask();
} else {
// Otherwise, remove the task from the stack immediately
mStack.removeTask(t);
mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
}
}
}
@@ -1402,7 +1532,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(TaskViewDismissedEvent event) {
removeTaskViewFromStack(event.taskView);
removeTaskViewFromStack(event.taskView, event.task);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
}
@@ -1419,7 +1549,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Poke the doze trigger on user interaction
mUIDozeTrigger.poke();
if (event.showTimerIndicator && mFocusedTask != null) {
getChildViewForTask(mFocusedTask).getHeaderView().cancelFocusTimerIndicator();
TaskView tv = getChildViewForTask(mFocusedTask);
if (tv != null) {
tv.getHeaderView().cancelFocusTimerIndicator();
}
}
}
@@ -1455,23 +1588,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
TaskViewAnimation animation = new TaskViewAnimation(250, mFastOutSlowInInterpolator);
if (event.dropTarget instanceof TaskStack.DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
addIgnoreTask(event.task);
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
getResources()));
computeRects(mStackBounds, true /* boundScroll */, event.task /* ignoreTask */);
updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
event.task /* ignoreTask */);
mLayoutAlgorithm.initialize(mStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
updateLayoutAlgorithm(true /* boundScroll */);
} else {
// Restore the pre-drag task stack bounds
// Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
// task view, so add it back to the ignore set after updating the layout
mStackBounds.set(mStableStackBounds);
computeRects(mStackBounds, true /* boundScroll */);
updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
event.task /* ignoreTask */);
removeIgnoreTask(event.task);
mLayoutAlgorithm.initialize(mStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
updateLayoutAlgorithm(true /* boundScroll */);
addIgnoreTask(event.task);
}
relayoutTaskViews(animation);
}
public final void onBusEvent(final DragEndEvent event) {
@@ -1487,14 +1626,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (hasChangedStacks) {
// Move the task to the right position in the stack (ie. the front of the stack if
// freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
// freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
// before we update their stack ids, otherwise, the keys will have changed.
if (event.dropTarget == mFreeformWorkspaceDropTarget) {
mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
} else if (event.dropTarget == mStackDropTarget) {
mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
}
updateLayout(true);
updateLayoutAlgorithm(true /* boundScroll */);
// Move the task to the new stack in the system after the animation completes
event.addPostAnimationCallback(new Runnable() {
@@ -1522,11 +1661,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
mTmpTransform, null);
event.getAnimationTrigger().increment();
updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
updateTaskViewToTransform(event.taskView, mTmpTransform,
new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator,
event.getAnimationTrigger().decrementOnAnimationEnd()));
removeIgnoreTask(event.task);
}
public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1593,15 +1733,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
*/
private void removeTaskViewFromStack(TaskView tv) {
Task task = tv.getTask();
private void removeTaskViewFromStack(TaskView tv, Task task) {
// Announce for accessibility
tv.announceForAccessibility(getContext().getString(
R.string.accessibility_recents_item_dismissed, tv.getTask().title));
R.string.accessibility_recents_item_dismissed, task.title));
// Remove the task from the stack
mStack.removeTask(task);
mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
}
/**
@@ -1622,7 +1761,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
* Returns the insert index for the task in the current set of task views. If the given task
* Returns the insert index for the task in the current set of task views. If the given task
* is already in the task view list, then this method returns the insert index assuming it
* is first removed at the previous index.
*

View File

@@ -151,6 +151,7 @@ public class TaskStackViewScroller {
/** Animates the stack scroll into bounds */
ObjectAnimator animateBoundScroll() {
// TODO: Take duration for snap back
float curScroll = getStackScroll();
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {

View File

@@ -21,13 +21,16 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.Log;
import android.util.ArrayMap;
import android.util.MutableBoolean;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
@@ -37,19 +40,25 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.misc.RectFEvaluator;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.ArrayList;
import java.util.List;
/* Handles touch events for a TaskStackView. */
/**
* Handles touch events for a TaskStackView.
*/
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
private static final String TAG = "TaskStackViewTouchHandler";
private static final boolean DEBUG = false;
private static final int INACTIVE_POINTER_ID = -1;
private static int INACTIVE_POINTER_ID = -1;
private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
Context mContext;
TaskStackView mSv;
@@ -74,6 +83,15 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
final int mWindowTouchSlop;
private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
// The current and final set of task transforms, sized to match the list of tasks in the stack
private ArrayList<Task> mCurrentTasks = new ArrayList<>();
private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
private TaskViewTransform mTmpTransform = new TaskViewTransform();
private float mTargetStackScroll;
SwipeHelper mSwipeHelper;
boolean mInterceptedBySwipeHelper;
@@ -97,8 +115,14 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
@Override
protected void updateSnapBackAnimation(Animator anim) {
protected void prepareDismissAnimation(View v, Animator anim) {
mSwipeHelperAnimations.put(v, anim);
}
@Override
protected void prepareSnapBackAnimation(View v, Animator anim) {
anim.setInterpolator(mSv.mFastOutSlowInInterpolator);
mSwipeHelperAnimations.put(v, anim);
}
};
mSwipeHelper.setDisableHardwareLayers(true);
@@ -119,21 +143,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
}
/** Returns the view at the specified coordinates */
TaskView findViewAtPoint(int x, int y) {
List<TaskView> taskViews = mSv.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
if (tv.getVisibility() == View.VISIBLE) {
if (mSv.isTouchPointInView(x, y, tv)) {
return tv;
}
}
}
return null;
}
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Pass through to swipe helper if we are swiping
@@ -179,6 +188,15 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mScroller.stopBoundScrollAnimation();
Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
// Finish any existing task animations from the delete
mSv.cancelAllTaskViewAnimations();
// Finish any of the swipe helper animations
ArrayMap<View, Animator> existingAnimators = new ArrayMap<>(mSwipeHelperAnimations);
for (int i = 0; i < existingAnimators.size(); i++) {
existingAnimators.get(existingAnimators.keyAt(i)).end();
}
mSwipeHelperAnimations.clear();
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
@@ -214,9 +232,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
float curScrollP = mDownScrollP + deltaP;
mScroller.setStackScroll(curScrollP);
if (DEBUG) {
Log.d(TAG, "scroll: " + curScrollP);
}
mStackViewScrolledEvent.updateY(y - mLastY);
EventBus.getDefault().send(mStackViewScrolledEvent);
}
@@ -343,12 +358,19 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
@Override
public View getChildAtPosition(MotionEvent ev) {
return findViewAtPoint((int) ev.getX(), (int) ev.getY());
TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
if (tv != null && canChildBeDismissed(tv)) {
return tv;
}
return null;
}
@Override
public boolean canChildBeDismissed(View v) {
return true;
// Disallow dismissing an already dismissed task
TaskView tv = (TaskView) v;
return !mSwipeHelperAnimations.containsKey(v) &&
(mSv.getStack().indexOfStackTask(tv.getTask()) != -1);
}
@Override
@@ -364,34 +386,113 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
// Add this task to the set of tasks we are deleting
mSv.addIgnoreTask(tv.getTask());
// Determine if we are animating the other tasks while dismissing this task
mCurrentTasks = mSv.getStack().getStackTasks();
MutableBoolean isFrontMostTask = new MutableBoolean(false);
Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
TaskStackViewScroller stackScroller = mSv.getScroller();
if (anchorTask != null) {
// Get the current set of task transforms
mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
// Get the stack scroll of the task to anchor to (since we are removing something, the
// front most task will be our anchor task)
float prevAnchorTaskScroll = 0;
boolean pullStackForward = mCurrentTasks.size() > 0;
if (pullStackForward) {
prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
}
// Calculate where the views would be without the deleting tasks
mSv.updateLayoutAlgorithm(false /* boundScroll */);
float newStackScroll = stackScroller.getStackScroll();
if (isFrontMostTask.value) {
// Bound the stack scroll to pull tasks forward if necessary
newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
} else if (pullStackForward) {
// Otherwise, offset the scroll by the movement of the anchor task
float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
if (mSv.getStackAlgorithm().getFocusState() !=
TaskStackLayoutAlgorithm.STATE_FOCUSED) {
// If we are focused, we don't want the front task to move, but otherwise, we
// allow the back task to move up, and the front task to move back
stackScrollOffset /= 2;
}
newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
+ stackScrollOffset);
}
// Pick up the newly visible views, not including the deleting tasks
mSv.bindVisibleTaskViews(newStackScroll);
// Get the final set of task transforms (with task removed)
mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms);
// Set the target to scroll towards upon dismissal
mTargetStackScroll = newStackScroll;
/*
* Post condition: All views that will be visible as a part of the gesture are retrieved
* and at their initial positions. The stack is still at the current
* scroll, but the layout is updated without the task currently being
* dismissed.
*/
}
}
@Override
public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
updateTaskViewTransforms(getDismissFraction(v));
return true;
}
/**
* Called after the {@link TaskView} is finished animating away.
*/
@Override
public void onChildDismissed(View v) {
TaskView tv = (TaskView) v;
// Re-enable clipping with the stack (we will reuse this view)
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
// Update the scroll to the final scroll position from onBeginDrag()
mSv.getScroller().setStackScroll(mTargetStackScroll, null);
// Remove the task view from the stack
EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
// Stop tracking this deletion animation
mSwipeHelperAnimations.remove(v);
// Keep track of deletions by keyboard
MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
Constants.Metrics.DismissSourceSwipeGesture);
}
/**
* Called after the {@link TaskView} is finished animating back into the list.
* onChildDismissed() calls.
*/
@Override
public void onChildSnappedBack(View v) {
TaskView tv = (TaskView) v;
// Re-enable clipping with the stack
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
// Stop tracking this deleting task, and update the layout to include this task again. The
// stack scroll does not need to be reset, since the scroll has not actually changed in
// onBeginDrag().
mSv.removeIgnoreTask(tv.getTask());
mSv.updateLayoutAlgorithm(false /* boundScroll */);
mSwipeHelperAnimations.remove(v);
}
@Override
@@ -414,4 +515,59 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return 0;
}
/**
* Interpolates the non-deleting tasks to their final transforms from their current transforms.
*/
private void updateTaskViewTransforms(float dismissFraction) {
List<TaskView> taskViews = mSv.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
if (mSv.isIgnoredTask(task)) {
continue;
}
int taskIndex = mCurrentTasks.indexOf(task);
TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
mTmpTransform.copyFrom(fromTransform);
// We only really need to interpolate the bounds, progress and translation
mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
toTransform.rect));
mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
mTmpTransform.translationZ = fromTransform.translationZ +
(toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
}
}
/** Returns the view at the specified coordinates */
private TaskView findViewAtPoint(int x, int y) {
List<Task> tasks = mSv.getStack().getStackTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
TaskView tv = mSv.getChildViewForTask(tasks.get(i));
if (tv != null && tv.getVisibility() == View.VISIBLE) {
if (mSv.isTouchPointInView(x, y, tv)) {
return tv;
}
}
}
return null;
}
/**
* Returns the fraction which we should interpolate the other task views based on the dismissal
* of this given task.
*
* TODO: We can interpolate this to adjust when the other tasks should respond to the dismissal
*/
private float getDismissFraction(View v) {
float fraction = Math.min(1f, Math.abs(v.getTranslationX() / mSv.getWidth()));
return STACK_TRANSFORM_INTERPOLATOR.getInterpolation(fraction);
}
}

View File

@@ -162,6 +162,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
/** Resets this TaskView for reuse. */
void reset() {
mHeaderView.reset();
resetViewProperties();
resetNoUserInteractionState();
setClipViewInStack(false);
@@ -459,16 +460,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void showActionButton(boolean fadeIn, int fadeInDuration) {
mActionButtonView.setVisibility(View.VISIBLE);
if (fadeIn) {
if (mActionButtonView.getAlpha() < 1f) {
mActionButtonView.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(fadeInDuration)
.setInterpolator(PhoneStatusBar.ALPHA_IN)
.start();
}
if (fadeIn && mActionButtonView.getAlpha() < 1f) {
mActionButtonView.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(fadeInDuration)
.setInterpolator(PhoneStatusBar.ALPHA_IN)
.start();
} else {
mActionButtonView.setScaleX(1f);
mActionButtonView.setScaleY(1f);
@@ -484,29 +483,27 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
*/
public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
final Animator.AnimatorListener animListener) {
if (fadeOut) {
if (mActionButtonView.getAlpha() > 0f) {
if (scaleDown) {
float toScale = 0.9f;
mActionButtonView.animate()
.scaleX(toScale)
.scaleY(toScale);
}
if (fadeOut && mActionButtonView.getAlpha() > 0f) {
if (scaleDown) {
float toScale = 0.9f;
mActionButtonView.animate()
.alpha(0f)
.setDuration(fadeOutDuration)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
.withEndAction(new Runnable() {
@Override
public void run() {
if (animListener != null) {
animListener.onAnimationEnd(null);
}
mActionButtonView.setVisibility(View.INVISIBLE);
}
})
.start();
.scaleX(toScale)
.scaleY(toScale);
}
mActionButtonView.animate()
.alpha(0f)
.setDuration(fadeOutDuration)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
.withEndAction(new Runnable() {
@Override
public void run() {
if (animListener != null) {
animListener.onAnimationEnd(null);
}
mActionButtonView.setVisibility(View.INVISIBLE);
}
})
.start();
} else {
mActionButtonView.setAlpha(0f);
mActionButtonView.setVisibility(View.INVISIBLE);

View File

@@ -16,22 +16,27 @@
package com.android.systemui.recents.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.support.v4.graphics.ColorUtils;
import android.os.CountDownTimer;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewStub;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -39,10 +44,10 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
@@ -59,6 +64,8 @@ public class TaskViewHeader extends FrameLayout
implements View.OnClickListener, View.OnLongClickListener {
private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f;
private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
private static final int OVERLAY_REVEAL_DURATION = 250;
private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
/**
@@ -69,8 +76,6 @@ public class TaskViewHeader extends FrameLayout
private Paint mHighlightPaint = new Paint();
private Paint mBackgroundPaint = new Paint();
private float[] mTmpHSL = new float[3];
public HighlightColorDrawable() {
mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
mBackgroundPaint.setAntiAlias(true);
@@ -122,11 +127,16 @@ public class TaskViewHeader extends FrameLayout
Task mTask;
// Header views
ImageView mMoveTaskButton;
ImageView mDismissButton;
ImageView mIconView;
TextView mTitleView;
int mMoveTaskTargetStackId = INVALID_STACK_ID;
ImageView mMoveTaskButton;
ImageView mDismissButton;
ViewStub mAppOverlayViewStub;
FrameLayout mAppOverlayView;
ImageView mAppIconView;
ImageView mAppInfoView;
TextView mAppTitleView;
ViewStub mFocusTimerIndicatorStub;
ProgressBar mFocusTimerIndicator;
// Header drawables
@@ -140,21 +150,26 @@ public class TaskViewHeader extends FrameLayout
Drawable mDarkFreeformIcon;
Drawable mLightFullscreenIcon;
Drawable mDarkFullscreenIcon;
Drawable mLightInfoIcon;
Drawable mDarkInfoIcon;
int mTaskBarViewLightTextColor;
int mTaskBarViewDarkTextColor;
int mMoveTaskTargetStackId = INVALID_STACK_ID;
// Header background
private HighlightColorDrawable mBackground;
private HighlightColorDrawable mOverlayBackground;
private float[] mTmpHSL = new float[3];
// Header dim, which is only used when task view hardware layers are not used
private Paint mDimLayerPaint = new Paint();
Interpolator mFastOutSlowInInterpolator;
Interpolator mFastOutLinearInInterpolator;
Interpolator mLinearOutSlowInInterpolator;
long mFocusIndicatorProgress;
private CountDownTimer mFocusTimerCountDown;
long mFocusTimerDuration;
private long mFocusTimerDuration;
public TaskViewHeader(Context context) {
this(context, null);
@@ -184,36 +199,45 @@ public class TaskViewHeader extends FrameLayout
mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_linear_in);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.linear_out_slow_in);
// Configure the background and dim
mBackground = new HighlightColorDrawable();
mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
setBackground(mBackground);
mOverlayBackground = new HighlightColorDrawable();
mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
mDimLayerPaint.setAntiAlias(true);
mFocusTimerDuration = res.getInteger(R.integer.recents_auto_advance_duration);
}
/**
* Resets this header along with the TaskView.
*/
public void reset() {
hideAppOverlay(true /* immediate */);
}
@Override
protected void onFinishInflate() {
// Initialize the icon and description views
mIconView = (ImageView) findViewById(R.id.icon);
mIconView.setClickable(false);
mIconView.setOnLongClickListener(this);
mTitleView = (TextView) findViewById(R.id.title);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
mDismissButton.setOnClickListener(this);
mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
mFocusTimerIndicator = (ProgressBar) findViewById(R.id.focus_timer_indicator);
// Hide the backgrounds if they are ripple drawables
if (mIconView.getBackground() instanceof RippleDrawable) {
mIconView.setBackground(null);
}
mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
}
/**
@@ -228,6 +252,7 @@ public class TaskViewHeader extends FrameLayout
mTaskViewRect.set(0, 0, width, height);
boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask();
int appIconWidth = mIconView.getMeasuredWidth();
int activityDescWidth = (mTask != null)
? (int) mTitleView.getPaint().measureText(mTask.title)
@@ -239,19 +264,20 @@ public class TaskViewHeader extends FrameLayout
// Priority-wise, we show the activity icon first, the dismiss icon if there is room, the
// move-task icon if there is room, and then finally, the activity label if there is room
if (width < (appIconWidth + dismissIconWidth)) {
if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) {
mTitleView.setVisibility(View.INVISIBLE);
if (updateMoveTaskButton) {
mMoveTaskButton.setVisibility(View.INVISIBLE);
}
mDismissButton.setVisibility(View.INVISIBLE);
} else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth)) {
} else if (isFreeformTask && width < (appIconWidth + dismissIconWidth +
moveTaskIconWidth)) {
mTitleView.setVisibility(View.INVISIBLE);
if (updateMoveTaskButton) {
mMoveTaskButton.setVisibility(View.INVISIBLE);
}
mDismissButton.setVisibility(View.VISIBLE);
} else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
} else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
activityDescWidth)) {
mTitleView.setVisibility(View.INVISIBLE);
if (updateMoveTaskButton) {
@@ -272,11 +298,6 @@ public class TaskViewHeader extends FrameLayout
invalidate();
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || (who == mBackground);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -288,6 +309,10 @@ public class TaskViewHeader extends FrameLayout
/** Starts the focus timer. */
public void startFocusTimerIndicator() {
if (mFocusTimerIndicator == null) {
return;
}
mFocusTimerIndicator.setVisibility(View.VISIBLE);
mFocusTimerIndicator.setMax((int) mFocusTimerDuration);
if (mFocusTimerCountDown == null) {
@@ -308,7 +333,11 @@ public class TaskViewHeader extends FrameLayout
/** Cancels the focus timer. */
public void cancelFocusTimerIndicator() {
if (mFocusTimerCountDown != null && mFocusTimerIndicator != null) {
if (mFocusTimerIndicator == null) {
return;
}
if (mFocusTimerCountDown != null) {
mFocusTimerCountDown.cancel();
mFocusTimerIndicator.setProgress(0);
mFocusTimerIndicator.setVisibility(View.INVISIBLE);
@@ -337,6 +366,10 @@ public class TaskViewHeader extends FrameLayout
private void updateBackgroundColor(float dimAlpha) {
if (mTask != null) {
mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha);
// TODO: Consider using the saturation of the color to adjust the lightness as well
ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL);
mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
}
}
@@ -382,10 +415,15 @@ public class TaskViewHeader extends FrameLayout
mMoveTaskButton.setOnClickListener(this);
}
mFocusTimerIndicator.getProgressDrawable()
.setColorFilter(
getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
PorterDuff.Mode.SRC_IN);
if (Recents.getDebugFlags().isFastToggleIndicatorEnabled()) {
if (mFocusTimerIndicator == null) {
mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate();
}
mFocusTimerIndicator.getProgressDrawable()
.setColorFilter(
getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
PorterDuff.Mode.SRC_IN);
}
// In accessibility, a single click on the focused app info button will show it
if (ssp.isTouchExplorationEnabled()) {
@@ -447,8 +485,11 @@ public class TaskViewHeader extends FrameLayout
@Override
public void onClick(View v) {
if (v == mIconView) {
// In accessibility, a single click on the focused app info button will show it
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isTouchExplorationEnabled()) {
// In accessibility, a single click on the focused app info button will show it
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
}
} else if (v == mDismissButton) {
TaskView tv = Utilities.findParent(this, TaskView.class);
tv.dismissTask();
@@ -463,15 +504,92 @@ public class TaskViewHeader extends FrameLayout
: new Rect();
EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
mMoveTaskTargetStackId, false));
} else if (v == mAppInfoView) {
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
} else if (v == mAppIconView) {
hideAppOverlay(false /* immediate */);
}
}
@Override
public boolean onLongClick(View v) {
if (v == mIconView) {
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
showAppOverlay();
return true;
} else if (v == mAppIconView) {
hideAppOverlay(false /* immediate */);
return true;
}
return false;
}
/**
* Shows the application overlay.
*/
private void showAppOverlay() {
// Skip early if the task is invalid
SystemServicesProxy ssp = Recents.getSystemServices();
ComponentName cn = mTask.key.getComponent();
int userId = mTask.key.userId;
ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
if (activityInfo == null) {
return;
}
// Inflate the overlay if necessary
if (mAppOverlayView == null) {
mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate();
mAppOverlayView.setBackground(mOverlayBackground);
mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
mAppIconView.setOnClickListener(this);
mAppIconView.setOnLongClickListener(this);
mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
mAppInfoView.setOnClickListener(this);
mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
}
// Update the overlay contents for the current app
mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo, userId));
mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
? mLightInfoIcon
: mDarkInfoIcon);
mAppOverlayView.setVisibility(View.VISIBLE);
int x = mIconView.getLeft() + mIconView.getWidth() / 2;
int y = mIconView.getTop() + mIconView.getHeight() / 2;
Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
getWidth());
revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
revealAnim.start();
}
/**
* Hide the application overlay.
*/
private void hideAppOverlay(boolean immediate) {
// Skip if we haven't even loaded the overlay yet
if (mAppOverlayView == null) {
return;
}
if (immediate) {
mAppOverlayView.setVisibility(View.GONE);
} else {
int x = mIconView.getLeft() + mIconView.getWidth() / 2;
int y = mIconView.getTop() + mIconView.getHeight() / 2;
Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
getWidth(), 0);
revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
revealAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAppOverlayView.setVisibility(View.GONE);
}
});
revealAnim.start();
}
}
}

View File

@@ -88,11 +88,38 @@ public class TaskViewTransform {
public float alpha = 1f;
public boolean visible = false;
float p = 0f;
// This is the relative task progress of this task, relative to the stack scroll at which this
// transform was computed
public float p = 0f;
// This is a window-space rect used for positioning the task in the stack and freeform workspace
public RectF rect = new RectF();
/**
* Fills int this transform from the state of the given TaskView.
*/
public void fillIn(TaskView tv) {
translationZ = tv.getTranslationZ();
scale = tv.getScaleX();
alpha = tv.getAlpha();
visible = true;
p = tv.getTaskProgress();
rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
}
/**
* Copies the transform state from another {@link TaskViewTransform}.
*/
public void copyFrom(TaskViewTransform other) {
translationZ = other.translationZ;
scale = other.scale;
alpha = other.alpha;
visible = other.visible;
p = other.p;
rect.set(other.rect);
}
/**
* Resets the current transform.
*/