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:
24
packages/SystemUI/res/drawable/recents_info_dark.xml
Normal file
24
packages/SystemUI/res/drawable/recents_info_dark.xml
Normal 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>
|
||||
24
packages/SystemUI/res/drawable/recents_info_light.xml
Normal file
24
packages/SystemUI/res/drawable/recents_info_light.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user