Merge branch 'ub-launcher3-master' into pi-dev
Bug: 74794600
Test: manual test
Change-Id: Id34a70abfa5de9dd741848efa3b82fae608c0df0
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 02b4379..d531a46 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -36,8 +36,13 @@
android:restoreAnyVersion="true"
android:supportsRtl="true" >
- <service android:name="com.android.quickstep.TouchInteractionService"
- android:exported="true" />
+ <service
+ android:name="com.android.quickstep.TouchInteractionService"
+ android:permission="android.permission.STATUS_BAR_SERVICE" >
+ <intent-filter>
+ <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
+ </intent-filter>
+ </service>
<!-- STOPSHIP: Change exported to false once all the integration is complete.
It is set to true so that the activity can be started from command line -->
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index d5859a7..9006831 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
new file mode 100644
index 0000000..c416844
--- /dev/null
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2018 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.
+-->
+<com.android.quickstep.RecentsRootView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.quickstep.FallbackRecentsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
+
+</com.android.quickstep.RecentsRootView>
\ No newline at end of file
diff --git a/quickstep/res/layout/longpress_options_menu.xml b/quickstep/res/layout/longpress_options_menu.xml
deleted file mode 100644
index 9cf0fcf..0000000
--- a/quickstep/res/layout/longpress_options_menu.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<com.android.launcher3.uioverrides.OptionsPopupView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="96dp"
- android:background="?attr/popupColorPrimary"
- android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="horizontal"
- launcher:layout_ignoreInsets="true">
-
- <FrameLayout
- android:id="@+id/wallpaper_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:background="?android:attr/selectableItemBackground">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:drawablePadding="4dp"
- android:drawableTint="?android:attr/textColorPrimary"
- android:drawableTop="@drawable/ic_wallpaper"
- android:fontFamily="sans-serif-condensed"
- android:gravity="center"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:text="@string/wallpaper_button_text"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp"/>
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/widget_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:background="?android:attr/selectableItemBackground">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:drawablePadding="4dp"
- android:drawableTint="?android:attr/textColorPrimary"
- android:drawableTop="@drawable/ic_widget"
- android:fontFamily="sans-serif-condensed"
- android:gravity="center"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:text="@string/widget_button_text"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp"/>
-
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/settings_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:background="?android:attr/selectableItemBackground">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:drawablePadding="4dp"
- android:drawableTint="?android:attr/textColorPrimary"
- android:drawableTop="@drawable/ic_setting"
- android:fontFamily="sans-serif-condensed"
- android:gravity="center"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:text="@string/settings_button_text"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp"/>
-
- </FrameLayout>
-
-</com.android.launcher3.uioverrides.OptionsPopupView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 9f4f8a1..54a90cf 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.RecentsView
+<com.android.quickstep.views.LauncherRecentsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
@@ -24,8 +24,4 @@
android:alpha="0.0"
android:visibility="invisible" >
- <com.android.launcher3.uioverrides.WorkspaceCard
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
-</com.android.quickstep.RecentsView>
\ No newline at end of file
+</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 91b6aa3..0ac2b11 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,12 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.quickstep.views.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp">
- <com.android.quickstep.TaskThumbnailView
+ <com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -29,4 +29,4 @@
android:layout_width="@dimen/task_thumbnail_icon_size"
android:layout_height="@dimen/task_thumbnail_icon_size"
android:layout_gravity="top|center_horizontal" />
-</com.android.quickstep.TaskView>
\ No newline at end of file
+</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index 6e3fb4f..b846665 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.TaskMenuView
+<com.android.quickstep.views.TaskMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
@@ -33,4 +33,4 @@
android:paddingTop="18dp"
android:drawablePadding="8dp"
android:gravity="center_horizontal"/>
-</com.android.quickstep.TaskMenuView>
\ No newline at end of file
+</com.android.quickstep.views.TaskMenuView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 0956048..8497191 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,24 +16,21 @@
<resources>
- <dimen name="options_menu_icon_size">24dp</dimen>
-
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<dimen name="task_menu_background_radius">12dp</dimen>
<dimen name="task_corner_radius">2dp</dimen>
<dimen name="task_fade_length">20dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
+ <dimen name="recents_page_fade_length">100dp</dimen>
+ <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
+ loading full resolution screenshots. -->
+ <dimen name="recents_fast_fling_velocity">600dp</dimen>
<dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
<dimen name="quickstep_fling_min_velocity">250dp</dimen>
- <dimen name="workspace_overview_offset_x">-24dp</dimen>
-
- <!-- TODO: This can be calculated using other resource values -->
- <dimen name="all_apps_search_box_full_height">90dp</dimen>
-
<!-- Launcher app transition -->
<dimen name="content_trans_y">25dp</dimen>
<dimen name="workspace_trans_y">80dp</dimen>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index ba99d81..2bd9f8f 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -16,5 +16,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+
+ <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
</resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 0fe29e3..ba0cbfa 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,7 +16,8 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
@@ -31,11 +32,13 @@
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
@@ -47,14 +50,18 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.shortcuts.DeepShortcutTextView;
+import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.quickstep.RecentsAnimationInterpolator;
import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -74,12 +81,16 @@
private static final String TAG = "LauncherTransition";
private static final int REFRESH_RATE_MS = 16;
+ private static final int STATUS_BAR_TRANSITION_DURATION = 120;
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+ private static final int APP_LAUNCH_DURATION = 500;
+ // Use a shorter duration for x or y translation to create a curve effect
+ private static final int APP_LAUNCH_CURVED_DURATION = 233;
private static final int RECENTS_LAUNCH_DURATION = 336;
- private static final int LAUNCHER_RESUME_START_DELAY = 150;
+ private static final int LAUNCHER_RESUME_START_DELAY = 100;
private static final int CLOSING_TRANSITION_DURATION_MS = 350;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@@ -151,6 +162,7 @@
@Override
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
+ TaskView taskView = findTaskViewToLaunch(launcher, v);
try {
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
@Override
@@ -160,8 +172,8 @@
// processed before the next frame.
postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
final boolean removeTrackingView;
- LauncherTransitionAnimator animator =
- composeRecentsLaunchAnimator(v, targets);
+ LauncherTransitionAnimator animator = composeRecentsLaunchAnimator(
+ taskView == null ? v : taskView, targets);
if (animator != null) {
// We are animating the task view directly, do not remove it after
removeTrackingView = false;
@@ -201,8 +213,10 @@
}
};
- return ActivityOptionsCompat.makeRemoteAnimation(
- new RemoteAnimationAdapterCompat(runner, 500, 380));
+ int duration = taskView != null ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
+ int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
+ return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+ runner, duration, statusBarTransitionDelay));
} catch (NoClassDefFoundError e) {
// Gracefully fall back to default launch options if the user's platform doesn't
// have the latest changes.
@@ -212,19 +226,65 @@
}
/**
+ * Try to find a TaskView that corresponds with the component of the launched view.
+ *
+ * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
+ * Otherwise, we will assume we are using a normal app transition, but it's possible that the
+ * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
+ */
+ private TaskView findTaskViewToLaunch(Launcher launcher, View v) {
+ if (v instanceof TaskView) {
+ return (TaskView) v;
+ }
+ if (!launcher.isInState(LauncherState.OVERVIEW)) {
+ return null;
+ }
+ if (v.getTag() instanceof ItemInfo) {
+ ItemInfo itemInfo = (ItemInfo) v.getTag();
+ ComponentName componentName = itemInfo.getTargetComponent();
+ if (componentName != null) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ for (int i = 0; i < recentsView.getChildCount(); i++) {
+ TaskView taskView = (TaskView) recentsView.getPageAt(i);
+ if (recentsView.isTaskViewVisible(taskView)) {
+ Task task = taskView.getTask();
+ if (componentName.equals(task.key.getComponent())) {
+ return taskView;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Composes the animations for a launch from the recents list if possible.
*/
private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
RemoteAnimationTargetCompat[] targets) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
+ MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
+ if (v instanceof TaskView) {
+ // We already found a task view to launch, so use that for the animation.
+ TaskView taskView = (TaskView) v;
+ return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
+ getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
+ }
+
+ // It's possible that the launched view can still be resolved to a visible task view, check
+ // the task id of the opening task and see if we can find a match.
+
// Ensure recents is actually visible
- if (!mLauncher.isInState(LauncherState.OVERVIEW)) {
+ if (!mLauncher.getStateManager().getState().overviewUi) {
return null;
}
// Resolve the opening task id
int openingTaskId = -1;
for (RemoteAnimationTargetCompat target : targets) {
- if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+ if (target.mode == MODE_OPENING) {
openingTaskId = target.taskId;
break;
}
@@ -237,15 +297,40 @@
// If the opening task id is not currently visible in overview, then fall back to normal app
// icon launch animation
- RecentsView recentsView = mLauncher.getOverviewPanel();
TaskView taskView = recentsView.getTaskView(openingTaskId);
if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
return null;
}
// Found a visible recents task that matches the opening app, lets launch the app from there
- return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
- getRecentsWindowAnimator(taskView, targets));
+ Animator launcherAnim;
+ AnimatorListenerAdapter windowAnimEndListener;
+ if (launcherClosing) {
+ launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
+ windowAnimEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+ mLauncher.getStateManager().reapplyState();
+ }
+ };
+ } else {
+ AnimatorPlaybackController controller =
+ mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
+ controller.dispatchOnStart();
+ launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
+ windowAnimEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLauncher.getStateManager().goToState(NORMAL, false);
+ }
+ };
+ }
+
+ Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
+ windowAnim.addListener(windowAnimEndListener);
+ return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
}
/**
@@ -260,15 +345,16 @@
int launchedTaskIndex = recentsView.indexOfChild(v);
int centerTaskIndex = recentsView.getCurrentPage();
boolean launchingCenterTask = launchedTaskIndex == centerTaskIndex;
+ boolean isRtl = recentsView.isRtl();
if (launchingCenterTask) {
- if (launchedTaskIndex - 1 >= recentsView.getFirstTaskIndex()) {
+ if (launchedTaskIndex - 1 >= 0) {
TaskView adjacentPage1 = (TaskView) recentsView.getPageAt(launchedTaskIndex - 1);
ObjectAnimator adjacentTask1ScaleAndTranslate =
LauncherAnimUtils.ofPropertyValuesHolder(adjacentPage1,
new PropertyListBuilder()
.scale(adjacentPage1.getScaleX() * mRecentsScale)
.translationY(mRecentsTransY)
- .translationX(mIsRtl ? mRecentsTransX : -mRecentsTransX)
+ .translationX(isRtl ? mRecentsTransX : -mRecentsTransX)
.build());
launcherAnimator.play(adjacentTask1ScaleAndTranslate);
}
@@ -279,11 +365,11 @@
new PropertyListBuilder()
.scale(adjacentTask2.getScaleX() * mRecentsScale)
.translationY(mRecentsTransY)
- .translationX(mIsRtl ? -mRecentsTransX : mRecentsTransX)
+ .translationX(isRtl ? -mRecentsTransX : mRecentsTransX)
.build());
launcherAnimator.play(adjacentTask2ScaleAndTranslate);
}
- } else if (centerTaskIndex >= recentsView.getFirstTaskIndex()) {
+ } else {
// We are launching an adjacent task, so parallax the center and other adjacent task.
TaskView centerTask = (TaskView) recentsView.getPageAt(centerTaskIndex);
float translationX = Math.abs(v.getTranslationX());
@@ -291,11 +377,11 @@
LauncherAnimUtils.ofPropertyValuesHolder(centerTask,
new PropertyListBuilder()
.scale(v.getScaleX())
- .translationX(mIsRtl ? -translationX : translationX)
+ .translationX(isRtl ? -translationX : translationX)
.build());
launcherAnimator.play(centerTaskParallaxToRight);
int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - launchedTaskIndex);
- if (otherAdjacentTaskIndex >= recentsView.getFirstTaskIndex()
+ if (otherAdjacentTaskIndex >= 0
&& otherAdjacentTaskIndex < recentsView.getPageCount()) {
TaskView otherAdjacentTask = (TaskView) recentsView.getPageAt(
otherAdjacentTaskIndex);
@@ -303,7 +389,7 @@
LauncherAnimUtils.ofPropertyValuesHolder(otherAdjacentTask,
new PropertyListBuilder()
.translationX(otherAdjacentTask.getTranslationX()
- + (mIsRtl ? -translationX : translationX))
+ + (isRtl ? -translationX : translationX))
.build());
launcherAnimator.play(otherAdjacentTaskParallaxToRight);
}
@@ -314,7 +400,7 @@
launcherAnimator.play(allAppsSlideOut);
Workspace workspace = mLauncher.getWorkspace();
- float[] workspaceScaleAndTranslation = LauncherState.NORMAL
+ float[] workspaceScaleAndTranslation = NORMAL
.getWorkspaceScaleAndTranslation(mLauncher);
Animator recenterWorkspace = LauncherAnimUtils.ofPropertyValuesHolder(
workspace, new PropertyListBuilder()
@@ -324,9 +410,6 @@
launcherAnimator.play(recenterWorkspace);
CellLayout currentWorkspacePage = (CellLayout) workspace.getPageAt(
workspace.getCurrentPage());
- Animator hideWorkspaceScrim = ObjectAnimator.ofInt(
- currentWorkspacePage.getScrimBackground(), DRAWABLE_ALPHA, 0);
- launcherAnimator.play(hideWorkspaceScrim);
launcherAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnimator.setDuration(RECENTS_LAUNCH_DURATION);
@@ -337,7 +420,7 @@
* @return Animator that controls the window of the opening targets for the recents launch
* animation.
*/
- private ValueAnimator getRecentsWindowAnimator(TaskView v,
+ private ValueAnimator getRecentsWindowAnimator(TaskView v, MutableBoolean skipLauncherChanges,
RemoteAnimationTargetCompat[] targets) {
Rect taskViewBounds = new Rect();
mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
@@ -373,13 +456,15 @@
final float percent = animation.getAnimatedFraction();
TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
- v.setScaleX(tw.taskScale);
- v.setScaleY(tw.taskScale);
- v.setTranslationX(tw.taskX);
- v.setTranslationY(tw.taskY);
- // Defer fading out the view until after the app window gets faded in
- v.setAlpha(getValue(1f, 0f, 75, 75,
- appAnimator.getDuration() * percent, Interpolators.LINEAR));
+ if (!skipLauncherChanges.value) {
+ v.setScaleX(tw.taskScale);
+ v.setScaleY(tw.taskScale);
+ v.setTranslationX(tw.taskX);
+ v.setTranslationY(tw.taskY);
+ // Defer fading out the view until after the app window gets faded in
+ v.setAlpha(getValue(1f, 0f, 75, 75,
+ appAnimator.getDuration() * percent, Interpolators.LINEAR));
+ }
matrix.setScale(tw.winScale, tw.winScale);
matrix.postTranslate(tw.winX, tw.winY);
@@ -401,7 +486,10 @@
matrix.postTranslate(target.position.x, target.position.y);
t.setMatrix(target.leash, matrix);
t.setWindowCrop(target.leash, crop);
- t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
+
+ if (!skipLauncherChanges.value) {
+ t.deferTransactionUntil(target.leash, surface, frameNumber);
+ }
}
if (isFirstFrame) {
t.show(target.leash);
@@ -413,13 +501,6 @@
isFirstFrame = false;
}
});
- appAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Make sure recents gets fixed up by resetting task alphas and scales, etc.
- mLauncher.getStateManager().reapplyState();
- }
- });
return appAnimator;
}
@@ -497,26 +578,40 @@
* @return Animator that controls the icon used to launch the target.
*/
private AnimatorSet getIconAnimator(View v) {
- boolean isBubbleTextView = v instanceof BubbleTextView;
+ final boolean isBubbleTextView = v instanceof BubbleTextView;
mFloatingView = new View(mLauncher);
if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
// Create a copy of the app icon
- ItemInfoWithIcon info = (ItemInfoWithIcon) v.getTag();
- FastBitmapDrawable d = DrawableFactory.get(mLauncher).newIcon(info);
- d.setIsDisabled(info.isDisabled());
- mFloatingView.setBackground(d);
+ mFloatingView.setBackground(
+ DrawableFactory.get(mLauncher).newIcon((ItemInfoWithIcon) v.getTag()));
}
// Position the floating view exactly on top of the original
Rect rect = new Rect();
- mDragLayer.getDescendantRectRelativeToSelf(v, rect);
- int viewLocationStart = mIsRtl
+ final boolean isDeepShortcutTextView = v instanceof DeepShortcutTextView
+ && v.getParent() != null && v.getParent() instanceof DeepShortcutView;
+ if (isDeepShortcutTextView) {
+ // Deep shortcut views have their icon drawn in a sibling view.
+ DeepShortcutView view = (DeepShortcutView) v.getParent();
+ mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
+ } else {
+ mDragLayer.getDescendantRectRelativeToSelf(v, rect);
+ }
+ final int viewLocationStart = mIsRtl
? mDeviceProfile.widthPx - rect.right
: rect.left;
- int viewLocationTop = rect.top;
+ final int viewLocationTop = rect.top;
- if (isBubbleTextView) {
- ((BubbleTextView) v).getIconBounds(rect);
+ float startScale = 1f;
+ if (isBubbleTextView && !isDeepShortcutTextView) {
+ BubbleTextView btv = (BubbleTextView) v;
+ btv.getIconBounds(rect);
+ Drawable dr = btv.getIcon();
+ if (dr instanceof FastBitmapDrawable) {
+ startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
+ }
+ } else {
+ rect.set(0, 0, rect.width(), rect.height());
}
LayoutParams lp = new LayoutParams(rect.width(), rect.height());
lp.ignoreInsets = true;
@@ -544,8 +639,8 @@
// Adjust the duration to change the "curve" of the app icon to the center.
boolean isBelowCenterY = lp.topMargin < centerY;
- x.setDuration(isBelowCenterY ? 500 : 233);
- y.setDuration(isBelowCenterY ? 233 : 500);
+ x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
+ y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
appIconAnimatorSet.play(x);
@@ -556,14 +651,10 @@
float maxScaleX = mDeviceProfile.widthPx / (float) rect.width();
float maxScaleY = mDeviceProfile.heightPx / (float) rect.height();
float scale = Math.max(maxScaleX, maxScaleY);
- ObjectAnimator sX = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_X, 1f, scale);
- ObjectAnimator sY = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_Y, 1f, scale);
- sX.setDuration(500);
- sY.setDuration(500);
- sX.setInterpolator(Interpolators.EXAGGERATED_EASE);
- sY.setInterpolator(Interpolators.EXAGGERATED_EASE);
- appIconAnimatorSet.play(sX);
- appIconAnimatorSet.play(sY);
+ ObjectAnimator scaleAnim = ObjectAnimator
+ .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
+ scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
+ appIconAnimatorSet.play(scaleAnim);
// Fade out the app icon.
ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
@@ -580,7 +671,13 @@
*/
private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
Rect bounds = new Rect();
- if (v instanceof BubbleTextView) {
+ boolean isDeepShortcutTextView = v instanceof DeepShortcutTextView
+ && v.getParent() != null && v.getParent() instanceof DeepShortcutView;
+ if (isDeepShortcutTextView) {
+ // Deep shortcut views have their icon drawn in a sibling view.
+ DeepShortcutView view = (DeepShortcutView) v.getParent();
+ mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
+ } else if (v instanceof BubbleTextView) {
((BubbleTextView) v).getIconBounds(bounds);
} else {
mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
@@ -591,7 +688,7 @@
Matrix matrix = new Matrix();
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
- appAnimator.setDuration(500);
+ appAnimator.setDuration(APP_LAUNCH_DURATION);
appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
boolean isFirstFrame = true;
@@ -713,7 +810,7 @@
postAtFrontOfQueueAsynchronously(handler, () -> {
if ((Utilities.getPrefs(mLauncher)
.getBoolean("pref_use_screenshot_for_swipe_up", false)
- && mLauncher.isInState(LauncherState.OVERVIEW))
+ && mLauncher.getStateManager().getState().overviewUi)
|| !launcherIsATargetWithMode(targets, MODE_OPENING)) {
// We use a separate transition for Overview mode. And we can skip the
// animation in cases where Launcher is not in the set of opening targets.
@@ -750,7 +847,7 @@
Matrix matrix = new Matrix();
float height = mLauncher.getDeviceProfile().heightPx;
float width = mLauncher.getDeviceProfile().widthPx;
- float endX = (Utilities.isRtl(mLauncher.getResources()) ? -width : width) * 1.16f;
+ float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
diff --git a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
index aec2869..ab9234b 100644
--- a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
+++ b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
@@ -27,11 +27,20 @@
*/
public class LauncherTransitionAnimator {
+ private final MutableBoolean mLauncherAnimCancelState;
+
private AnimatorSet mAnimatorSet;
private Animator mLauncherAnimator;
private Animator mWindowAnimator;
LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
+ this(launcherAnimator, windowAnimator, new MutableBoolean(false));
+ }
+
+
+ LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator,
+ MutableBoolean launcherAnimCancelState) {
+ mLauncherAnimCancelState = launcherAnimCancelState;
if (launcherAnimator != null) {
mLauncherAnimator = launcherAnimator;
}
@@ -50,6 +59,7 @@
public void cancel() {
mAnimatorSet.cancel();
+ mLauncherAnimCancelState.value = true;
}
public boolean isRunning() {
@@ -58,6 +68,7 @@
public void finishLauncherAnimation() {
if (mLauncherAnimator != null) {
+ mLauncherAnimCancelState.value = true;
mLauncherAnimator.end();
}
}
diff --git a/quickstep/src/com/android/launcher3/MutableBoolean.java b/quickstep/src/com/android/launcher3/MutableBoolean.java
new file mode 100644
index 0000000..7538217
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/MutableBoolean.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 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.launcher3;
+
+public class MutableBoolean {
+ public boolean value;
+
+ public MutableBoolean(boolean value) {
+ this.value = value;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 426fe35..31261d9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,6 +25,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -32,7 +33,8 @@
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
+ | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -57,7 +59,8 @@
@Override
public String getDescription(Launcher launcher) {
- return launcher.getString(R.string.all_apps_button_label);
+ AllAppsContainerView appsView = launcher.getAppsView();
+ return appsView.getDescription();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index 541c6bb..97ac3e6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -31,13 +31,12 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.VerticalSwipeController;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
class EventLogTags {
private EventLogTags() {
@@ -143,11 +142,6 @@
}
@Override
- protected void initSprings() {
- mSpringHandlers = new SpringAnimationHandler[0];
- }
-
- @Override
protected float getShiftRange() {
return getShiftRange(mLauncher);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
new file mode 100644
index 0000000..9541d0d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.launcher3.uioverrides;
+
+import com.android.launcher3.Launcher;
+import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Extension of overview state used for QuickScrub
+ */
+public class FastOverviewState extends OverviewState {
+
+ private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
+ | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
+
+ private static final boolean DEBUG_DIFFERENT_UI = false;
+
+ public FastOverviewState(int id) {
+ super(id, QuickScrubController.QUICK_SWITCH_START_DURATION, STATE_FLAGS);
+ }
+
+ @Override
+ public void onStateTransitionEnd(Launcher launcher) {
+ super.onStateTransitionEnd(launcher);
+ RecentsView recentsView = launcher.getOverviewPanel();
+ recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
+ }
+
+ @Override
+ public float getHoseatAlpha(Launcher launcher) {
+ if (DEBUG_DIFFERENT_UI) {
+ return 0;
+ }
+ return super.getHoseatAlpha(launcher);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java b/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java
deleted file mode 100644
index 2d5eb5a..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2018 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.launcher3.uioverrides;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.util.TouchController;
-import com.android.quickstep.TouchInteractionService;
-
-/**
- * Consumes touches when quick scrub is enabled.
- */
-public class IgnoreTouchesInQuickScrub implements TouchController {
-
- public IgnoreTouchesInQuickScrub() {
- }
-
- @Override
- public boolean onControllerTouchEvent(MotionEvent ev) {
- return true;
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- return TouchInteractionService.isQuickScrubEnabled();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java b/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
deleted file mode 100644
index c089d06..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2018 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.launcher3.uioverrides;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Outline;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.widget.Toast;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.RevealOutlineAnimation;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.widget.WidgetsFullSheet;
-
-/**
- * Popup shown on long pressing an empty space in launcher
- */
-public class OptionsPopupView extends AbstractFloatingView implements OnClickListener {
-
- private final float mOutlineRadius;
- private final Launcher mLauncher;
- private final PointF mTouchPoint = new PointF();
-
- private final GradientView mGradientView;
-
- protected Animator mOpenCloseAnimator;
-
- public OptionsPopupView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
- setClipToOutline(true);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
- }
- });
-
- mLauncher = Launcher.getLauncher(context);
-
- mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
- R.layout.widgets_bottom_sheet_scrim, mLauncher.getDragLayer(), false);
- mGradientView.setProgress(1, false);
- mGradientView.setAlpha(0);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- findViewById(R.id.wallpaper_button).setOnClickListener(this);
- findViewById(R.id.widget_button).setOnClickListener(this);
- findViewById(R.id.settings_button).setOnClickListener(this);
- }
-
- @Override
- public void onClick(View view) {
- if (view.getId() == R.id.wallpaper_button) {
- mLauncher.onClickWallpaperPicker(null);
- close(true);
- } else if (view.getId() == R.id.widget_button) {
- if (mLauncher.getPackageManager().isSafeMode()) {
- Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
- } else {
- WidgetsFullSheet.show(mLauncher, true /* animated */);
- close(true);
- }
- } else if (view.getId() == R.id.settings_button) {
- mLauncher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
- .setPackage(mLauncher.getPackageName())
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- close(true);
- }
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
- if (mLauncher.getDragLayer().isEventOverView(this, ev)) {
- return false;
- }
- close(true);
- return true;
- }
-
- @Override
- protected void handleClose(boolean animate) {
- if (animate) {
- animateClose();
- } else {
- closeComplete();
- }
- }
-
- protected void animateClose() {
- if (!mIsOpen) {
- return;
- }
- mIsOpen = false;
-
- final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
- closeAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration));
-
- // Rectangular reveal (reversed).
- final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
- .createRevealAnimator(this, true);
- closeAnim.play(revealAnim);
-
- Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
- fadeOut.setInterpolator(Interpolators.DEACCEL);
- closeAnim.play(fadeOut);
-
- Animator gradientAlpha = ObjectAnimator.ofFloat(mGradientView, ALPHA, 0);
- gradientAlpha.setInterpolator(Interpolators.DEACCEL);
- closeAnim.play(gradientAlpha);
-
- closeAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- closeComplete();
- }
- });
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel();
- }
- mOpenCloseAnimator = closeAnim;
- closeAnim.start();
- }
-
- /**
- * Closes the popup without animation.
- */
- private void closeComplete() {
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel();
- mOpenCloseAnimator = null;
- }
- mIsOpen = false;
- mLauncher.getDragLayer().removeView(this);
- mLauncher.getDragLayer().removeView(mGradientView);
- }
-
- @Override
- public void logActionCommand(int command) {
- // TODO:
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_OPTIONS_POPUP) != 0;
- }
-
- private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
- Rect startRect = new Rect();
- startRect.offset((int) (mTouchPoint.x - lp.x), (int) (mTouchPoint.y - lp.y));
-
- Rect endRect = new Rect(0, 0, lp.width, lp.height);
- if (getOutlineProvider() instanceof RevealOutlineAnimation) {
- ((RevealOutlineAnimation) getOutlineProvider()).getOutline(endRect);
- }
-
- return new RoundedRectRevealOutlineProvider
- (mOutlineRadius, mOutlineRadius, startRect, endRect);
- }
-
- private void animateOpen() {
- mIsOpen = true;
- final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
- openAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration));
-
- final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
- .createRevealAnimator(this, false);
- openAnim.play(revealAnim);
-
- Animator gradientAlpha = ObjectAnimator.ofFloat(mGradientView, ALPHA, 1);
- gradientAlpha.setInterpolator(Interpolators.ACCEL);
- openAnim.play(gradientAlpha);
-
- mOpenCloseAnimator = openAnim;
-
- openAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- }
- });
- openAnim.start();
- }
-
- public static void show(Launcher launcher, float x, float y) {
- DragLayer dl = launcher.getDragLayer();
- OptionsPopupView view = (OptionsPopupView) launcher.getLayoutInflater()
- .inflate(R.layout.longpress_options_menu, dl, false);
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) view.getLayoutParams();
-
- int maxWidth = dl.getWidth();
- int maxHeight = dl.getHeight();
- if (x <= 0 || y <= 0 || x >= maxWidth || y >= maxHeight) {
- x = maxWidth / 2;
- y = maxHeight / 2;
- }
- view.mTouchPoint.set(x, y);
-
- int height = lp.height;
-
- // Find a good width;
- int childCount = view.getChildCount();
- int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
- int widthSpec = MeasureSpec.makeMeasureSpec(maxWidth / childCount, MeasureSpec.AT_MOST);
- int maxChildWidth = 0;
-
- for (int i = 0; i < childCount; i ++) {
- View child = ((ViewGroup) view.getChildAt(i)).getChildAt(0);
- child.measure(widthSpec, heightSpec);
- maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth());
- }
- Rect insets = dl.getInsets();
- int margin = (int) (2 * view.getElevation());
-
- int width = Math.min(maxWidth - insets.left - insets.right - 2 * margin,
- maxChildWidth * childCount);
- lp.width = width;
-
- // Position is towards the finger
- lp.customPosition = true;
- lp.x = Utilities.boundToRange((int) (x - width / 2), insets.left + margin,
- maxWidth - insets.right - width - margin);
- lp.y = Utilities.boundToRange((int) (y - height / 2), insets.top + margin,
- maxHeight - insets.bottom - height - margin);
-
- launcher.getDragLayer().addView(view.mGradientView);
- launcher.getDragLayer().addView(view);
- view.animateOpen();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 9ba2308..abb4ecf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,20 +16,17 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
-import android.content.Context;
import android.graphics.Rect;
import android.view.View;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
/**
* Definition for overview state
@@ -37,27 +34,26 @@
public class OverviewState extends LauncherState {
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE;
+ | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
public OverviewState(int id) {
- super(id, ContainerType.TASKSWITCHER, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+ this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+ }
+
+ protected OverviewState(int id, int transitionDuration, int stateFlags) {
+ super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
}
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
Rect pageRect = new Rect();
- RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
- RecentsView rv = launcher.getOverviewPanel();
+ RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
return super.getWorkspaceScaleAndTranslation(launcher);
}
- float overlap = 0;
- if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
- overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
- }
- return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
+ return getScaleAndTranslationForPageRect(launcher, pageRect);
}
@Override
@@ -73,8 +69,8 @@
}
@Override
- public float getVerticalProgress(Launcher launcher) {
- return getVerticalProgress(launcher.getDeviceProfile(), launcher);
+ public void onStateTransitionEnd(Launcher launcher) {
+ launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
}
@Override
@@ -83,51 +79,25 @@
}
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- final int centerPage = launcher.getWorkspace().getNextPage();
- return new PageAlphaProvider(ACCEL_2) {
+ return new PageAlphaProvider(DEACCEL_2) {
@Override
public float getPageAlpha(int pageIndex) {
- return pageIndex != centerPage ? 0 : 1f;
+ return 0;
}
};
}
- public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
- Rect pageRect) {
+ public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
Workspace ws = launcher.getWorkspace();
float childWidth = ws.getNormalChildWidth();
- float childHeight = ws.getNormalChildHeight();
- float scale = pageRect.height() / childHeight;
+ float scale = pageRect.width() / childWidth;
Rect insets = launcher.getDragLayer().getInsets();
float halfHeight = ws.getExpectedHeight() / 2;
float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
float translationY = pageRect.top - childTop;
- // Align the workspace horizontally centered with the task rect
- float halfWidth = ws.getExpectedWidth() / 2;
- float childCenter = halfWidth -
- scale * (halfWidth - ws.getPaddingLeft() - insets.left - childWidth / 2);
- float translationX = pageRect.centerX() - childCenter;
-
- if (Utilities.isRtl(launcher.getResources())) {
- translationX -= offsetX / scale;
- } else {
- translationX += offsetX / scale;
- }
-
- return new float[] {scale, translationX, translationY};
- }
-
- public static float getVerticalProgress(DeviceProfile grid, Context context) {
- if (!grid.isVerticalBarLayout()) {
- return 1f;
- }
-
- float total = grid.heightPx;
- float searchHeight = total - grid.availableHeightPx +
- context.getResources().getDimension(R.dimen.all_apps_search_box_full_height);
- return 1 - (searchHeight / total);
+ return new float[] {scale, 0, translationY};
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
index 468a561..c8b54ad 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -15,10 +15,14 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.MotionEvent;
@@ -32,19 +36,13 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import com.android.quickstep.PendingAnimation;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
/**
* Touch controller for swipe interaction in Overview state
@@ -65,6 +63,7 @@
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
+ private PendingAnimation mPendingAnimation;
private AnimatorPlaybackController mCurrentAnimation;
private boolean mCurrentAnimationIsGoingUp;
@@ -132,28 +131,21 @@
mTaskBeingDragged = null;
mSwipeDownEnabled = true;
- int currentPage = mRecentsView.getCurrentPage();
- if (currentPage == 0) {
- // User is on home tile
+ View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
+ if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) {
+ // The tile can be dragged down to open the task.
+ mTaskBeingDragged = (TaskView) view;
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ mStartingTarget = LauncherLogProto.ItemType.TASK;
+ } else if (isEventOverHotseat(ev)) {
+ // The hotseat is being dragged
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ mSwipeDownEnabled = false;
+ mStartingTarget = ContainerType.HOTSEAT;
} else {
- View view = mRecentsView.getChildAt(currentPage);
- if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
- view instanceof TaskView) {
- // The tile can be dragged down to open the task.
- mTaskBeingDragged = (TaskView) view;
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
- mStartingTarget = LauncherLogProto.ItemType.TASK;
- } else if (isEventOverHotseat(ev)) {
- // The hotseat is being dragged
- directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
- mSwipeDownEnabled = false;
- mStartingTarget = ContainerType.HOTSEAT;
- } else {
- mNoIntercept = true;
- mStartingTarget = ContainerType.WORKSPACE;
- return false;
- }
+ mNoIntercept = true;
+ mStartingTarget = ContainerType.WORKSPACE;
+ return false;
}
}
@@ -185,6 +177,11 @@
if (mCurrentAnimation != null) {
mCurrentAnimation.setPlayFraction(0);
}
+ if (mPendingAnimation != null) {
+ mPendingAnimation.finish(false);
+ mPendingAnimation = null;
+ }
+
mCurrentAnimationIsGoingUp = goingUp;
float range = mLauncher.getAllAppsController().getShiftRange();
long maxDuration = (long) (2 * range);
@@ -201,19 +198,11 @@
}
} else {
if (goingUp) {
- AnimatorSet anim = new AnimatorSet();
- ObjectAnimator translate = ObjectAnimator.ofFloat(
- mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
- translate.setInterpolator(LINEAR);
- translate.setDuration(maxDuration);
- anim.play(translate);
-
- ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
- alpha.setInterpolator(DEACCEL_1_5);
- alpha.setDuration(maxDuration);
- anim.play(alpha);
- mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
- mEndDisplacement = -mTaskBeingDragged.getBottom();
+ mPendingAnimation = mRecentsView
+ .createTaskDismissAnimation(mTaskBeingDragged, maxDuration);
+ mCurrentAnimation = AnimatorPlaybackController
+ .wrap(mPendingAnimation.anim, maxDuration);
+ mEndDisplacement = -mTaskBeingDragged.getHeight();
} else {
AnimatorSet anim = new AnimatorSet();
// TODO: Setup a zoom animation
@@ -299,15 +288,17 @@
}
private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
+ if (mPendingAnimation != null) {
+ mPendingAnimation.finish(wasSuccess);
+ mPendingAnimation = null;
+ }
if (mTaskBeingDragged == null) {
LauncherState state = wasSuccess ?
(mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
mLauncher.getStateManager().goToState(state, false);
} else if (wasSuccess) {
- if (mCurrentAnimationIsGoingUp) {
- mRecentsView.onTaskDismissed(mTaskBeingDragged);
- } else {
+ if (!mCurrentAnimationIsGoingUp) {
mTaskBeingDragged.launchTask(false);
mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 7d371e9..b7f79b3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,7 +16,7 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -28,84 +28,71 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.PagedView;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
public class RecentsViewStateController implements StateHandler {
private final Launcher mLauncher;
private final RecentsView mRecentsView;
- private final WorkspaceCard mWorkspaceCard;
private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::onTransitionProgress);
// The fraction representing the visibility of the RecentsView. This allows delaying the
// overall transition while the RecentsView is being shown or hidden.
private final AnimatedFloat mVisibilityMultiplier = new AnimatedFloat(this::onVisibilityProgress);
- private boolean mIsRecentsScrollingToFirstTask;
+ private boolean mIsRecentsSlidingInOrOut;
public RecentsViewStateController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
- mRecentsView.setStateController(this);
-
- mWorkspaceCard = (WorkspaceCard) mRecentsView.getChildAt(0);
- mWorkspaceCard.setup(launcher);
}
@Override
public void setState(LauncherState state) {
- mWorkspaceCard.setWorkspaceScrollingEnabled(state == OVERVIEW);
- setVisibility(state == OVERVIEW);
- setTransitionProgress(state == OVERVIEW ? 1 : 0);
- if (state == OVERVIEW) {
- for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
- ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
- }
- mRecentsView.updateCurveProperties();
+ setVisibility(state.overviewUi);
+ setTransitionProgress(state.overviewUi ? 1 : 0);
+ if (state.overviewUi) {
+ mRecentsView.resetTaskVisuals();
}
}
@Override
public void setStateWithAnimation(final LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
- boolean settingEnabled = Utilities.getPrefs(mLauncher)
- .getBoolean("pref_scroll_to_first_task", false);
- mIsRecentsScrollingToFirstTask = mLauncher.isInState(NORMAL) && toState == OVERVIEW
- && settingEnabled;
- // TODO: Instead of animating the workspace translationX, move the contents
- mWorkspaceCard.setWorkspaceScrollingEnabled(mIsRecentsScrollingToFirstTask);
+ LauncherState fromState = mLauncher.getStateManager().getState();
+ mIsRecentsSlidingInOrOut = fromState == NORMAL && toState.overviewUi
+ || fromState.overviewUi && toState == NORMAL;
// Scroll to the workspace card before changing to the NORMAL state.
int currPage = mRecentsView.getCurrentPage();
- if (toState == NORMAL && currPage != 0 && !config.userControlled) {
+ if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
int durationPerPage = maxSnapDuration / 10;
int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
mRecentsView.snapToPage(0, snapDuration);
- builder.setStartDelay(snapDuration);
+ // Let the snapping animation play for a bit before we translate off screen.
+ builder.setStartDelay(snapDuration / 4);
}
ObjectAnimator progressAnim =
- mTransitionProgress.animateToValue(toState == OVERVIEW ? 1 : 0);
+ mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
progressAnim.setDuration(config.duration);
progressAnim.setInterpolator(Interpolators.LINEAR);
progressAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- mWorkspaceCard.setWorkspaceScrollingEnabled(toState == OVERVIEW);
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
}
});
builder.play(progressAnim);
- ObjectAnimator visibilityAnim = animateVisibility(toState == OVERVIEW);
+ ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
visibilityAnim.setDuration(config.duration);
visibilityAnim.setInterpolator(Interpolators.LINEAR);
builder.play(visibilityAnim);
@@ -144,11 +131,14 @@
private void onTransitionProgress() {
applyProgress();
- if (mIsRecentsScrollingToFirstTask) {
- int scrollForFirstTask = mRecentsView.getScrollForPage(mRecentsView.getFirstTaskIndex());
- int scrollForPage0 = mRecentsView.getScrollForPage(0);
- mRecentsView.setScrollX((int) (mTransitionProgress.value * scrollForFirstTask
- + (1 - mTransitionProgress.value) * scrollForPage0));
+ if (mIsRecentsSlidingInOrOut) {
+ float interpolatedProgress = ACCEL.getInterpolation(mTransitionProgress.value);
+ // Slide in from the side as we swipe.
+ int translation = mRecentsView.getWidth();
+ if (mRecentsView.isRtl()) {
+ translation = -translation;
+ }
+ mRecentsView.setTranslationX(translation * (1 - interpolatedProgress));
}
}
@@ -158,5 +148,9 @@
private void applyProgress() {
mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
+ if (mIsRecentsSlidingInOrOut) {
+ // While animating into recents, update the visible task data as needed
+ mRecentsView.loadVisibleTaskData();
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index 2695054..c8d75dc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.animation.Animator;
@@ -27,7 +26,6 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.support.animation.SpringAnimation;
import android.util.Log;
import android.view.MotionEvent;
@@ -38,11 +36,9 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -52,8 +48,6 @@
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TouchInteractionService;
-import java.util.ArrayList;
-
/**
* Handles vertical touch gesture on the DragLayer
*/
@@ -112,8 +106,6 @@
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
- private SpringAnimationHandler[] mSpringHandlers;
-
public TwoStepSwipeController(Launcher l) {
mLauncher = l;
mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
@@ -156,29 +148,6 @@
}
}
- private void initSprings() {
- AllAppsContainerView appsView = mLauncher.getAppsView();
-
- SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
- if (handler == null) {
- mSpringHandlers = new SpringAnimationHandler[0];
- return;
- }
-
- ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
- handlers.add(handler);
-
- SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
- if (searchSpring != null) {
- SpringAnimationHandler searchHandler =
- new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
- searchHandler.add(searchSpring, true /* setDefaultValues */);
- handlers.add(searchHandler);
- }
-
- mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
- }
-
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -214,10 +183,6 @@
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
-
- if (mSpringHandlers == null) {
- initSprings();
- }
}
if (mNoIntercept) {
@@ -230,9 +195,6 @@
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
- for (SpringAnimationHandler h : mSpringHandlers) {
- h.addMovement(ev);
- }
return mDetector.onTouchEvent(ev);
}
@@ -283,10 +245,6 @@
mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
updatePauseDetectorRangeFlag();
}
-
- for (SpringAnimationHandler h : mSpringHandlers) {
- h.skipToEnd();
- }
}
private float getShiftRange() {
@@ -329,13 +287,6 @@
targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
}
- if (fling && targetState == ALL_APPS) {
- for (SpringAnimationHandler h : mSpringHandlers) {
- // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
- h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
- }
- }
-
float endProgress;
if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 7bd4366..9051cfb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,37 +18,29 @@
import static com.android.launcher3.LauncherState.NORMAL;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.PointF;
+import android.view.View;
import android.view.View.AccessibilityDelegate;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.BitmapRenderer;
+import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
-import com.android.quickstep.RecentsView;
-import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.views.RecentsView;
public class UiFactory {
- private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
- "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
-
- public static final boolean USE_HARDWARE_BITMAP = false; // FeatureFlags.IS_DOGFOOD_BUILD;
-
public static TouchController[] createTouchControllers(Launcher launcher) {
if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
return new TouchController[] {
- new IgnoreTouchesInQuickScrub(),
new EdgeSwipeController(launcher),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
} else {
return new TouchController[] {
- new IgnoreTouchesInQuickScrub(),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
}
@@ -64,28 +56,40 @@
new RecentsViewStateController(launcher)};
}
- public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
- OptionsPopupView.show(launcher, touchPoint.x, touchPoint.y);
- }
-
public static void onLauncherStateOrFocusChanged(Launcher launcher) {
- OverviewInteractionState.setBackButtonVisible(launcher, launcher == null
- || !launcher.isInState(NORMAL) || !launcher.hasWindowFocus());
- }
-
- public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
- BitmapRenderer renderer) {
- if (USE_HARDWARE_BITMAP && !forceSoftwareRenderer) {
- return RecentsTransition.createHardwareBitmap(width, height, renderer::render);
- } else {
- Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- renderer.render(new Canvas(result));
- return result;
+ boolean shouldBackButtonBeVisible = launcher == null
+ || !launcher.isInState(NORMAL)
+ || !launcher.hasWindowFocus();
+ if (!shouldBackButtonBeVisible) {
+ // Show the back button if there is a floating view visible.
+ DragLayer dragLayer = launcher.getDragLayer();
+ for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+ View child = dragLayer.getChildAt(i);
+ if (child instanceof AbstractFloatingView) {
+ shouldBackButtonBeVisible = true;
+ break;
+ }
+ }
}
+ OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
}
public static void resetOverview(Launcher launcher) {
RecentsView recents = launcher.getOverviewPanel();
recents.reset();
}
+
+ public static void onStart(Launcher launcher) {
+ RecentsModel model = RecentsModel.getInstance(launcher);
+ if (model != null) {
+ model.onStart();
+ }
+ }
+
+ public static void onTrimMemory(Launcher launcher, int level) {
+ RecentsModel model = RecentsModel.getInstance(launcher);
+ if (model != null) {
+ model.onTrimMemory(level);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
deleted file mode 100644
index 8533502..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
-
-public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
-
- private final Rect mTempRect = new Rect();
-
- private Launcher mLauncher;
- private Workspace mWorkspace;
-
- private float mLinearInterpolationForPage2 = 1;
- private float mTranslateXPage0, mTranslateXPage1;
- private float mExtraScrollShift;
-
- private boolean mIsWorkspaceScrollingEnabled;
-
- public WorkspaceCard(Context context) {
- this(context, null);
- }
-
- public WorkspaceCard(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setOnClickListener(this);
- }
-
- /**
- * Draw nothing.
- */
- @Override
- public void draw(Canvas canvas) { }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- // Initiate data
- mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
- mLauncher.getDeviceProfile(), mLauncher, mTempRect);
-
- float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
- mTranslateXPage0 = scale[1];
- mTranslateXPage1 = OverviewState
- .getScaleAndTranslationForPageRect(mLauncher,
- getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
- mTempRect)[1];
-
- mExtraScrollShift = 0;
- if (mWorkspace != null && getWidth() > 0) {
- float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
- mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
- setScaleX(workspaceWidth / getWidth());
- }
- }
-
- @Override
- public void onClick(View view) {
- mLauncher.getStateManager().goToState(NORMAL);
- }
-
- public void setup(Launcher launcher) {
- mLauncher = launcher;
- mWorkspace = mLauncher.getWorkspace();
- }
-
- public void setWorkspaceScrollingEnabled(boolean isEnabled) {
- mIsWorkspaceScrollingEnabled = isEnabled;
- }
-
- @Override
- public int onPageScroll(ScrollState scrollState) {
- float factor = scrollState.linearInterpolation;
- float translateX = scrollState.distanceFromScreenCenter;
- if (mIsWorkspaceScrollingEnabled) {
- float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
- mWorkspace.setTranslationX(shift + mTranslateXPage0);
- translateX += shift;
- }
-
- setTranslationX(translateX);
-
- // If the workspace card is still the first page, shift all the other pages.
- if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
- scrollState.prevPageExtraWidth = 0;
- } else if (mLinearInterpolationForPage2 > 0) {
- scrollState.prevPageExtraWidth = mExtraScrollShift *
- (1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
- } else {
- scrollState.prevPageExtraWidth = mExtraScrollShift;
- }
- return SCROLL_TYPE_WORKSPACE;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 214b3f3..84dfa45 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -77,6 +77,12 @@
}
}
+ public void finishAnimation() {
+ if (mValueAnimator != null && mValueAnimator.isRunning()) {
+ mValueAnimator.end();
+ }
+ }
+
public ObjectAnimator getCurrentAnimation() {
return mValueAnimator;
}
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
index b3ebd77..5871a6d 100644
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
@@ -23,6 +23,7 @@
public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
protected Runnable mGestureEndCallback;
+ protected boolean mIsGoingToHome;
public void setGestureEndCallback(Runnable gestureEndCallback) {
mGestureEndCallback = gestureEndCallback;
diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
new file mode 100644
index 0000000..b92678a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 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.quickstep;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.view.Choreographer;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+/**
+ * A TouchConsumer which defers all events on the UIThread until the consumer is created.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class DeferredTouchConsumer implements TouchConsumer {
+
+ private final VelocityTracker mVelocityTracker;
+ private final DeferredTouchProvider mTouchProvider;
+
+ private MotionEventQueue mMyQueue;
+ private TouchConsumer mTarget;
+
+ public DeferredTouchConsumer(DeferredTouchProvider touchProvider) {
+ mVelocityTracker = VelocityTracker.obtain();
+ mTouchProvider = touchProvider;
+ }
+
+ @Override
+ public void accept(MotionEvent event) {
+ mTarget.accept(event);
+ }
+
+ @Override
+ public void reset() {
+ mTarget.reset();
+ }
+
+ @Override
+ public void updateTouchTracking(int interactionType) {
+ mTarget.updateTouchTracking(interactionType);
+ }
+
+ @Override
+ public void onQuickScrubEnd() {
+ mTarget.onQuickScrubEnd();
+ }
+
+ @Override
+ public void onQuickScrubProgress(float progress) {
+ mTarget.onQuickScrubProgress(progress);
+ }
+
+ @Override
+ public void preProcessMotionEvent(MotionEvent ev) {
+ mVelocityTracker.addMovement(ev);
+ }
+
+ @Override
+ public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
+ mMyQueue = queue;
+ return null;
+ }
+
+ @Override
+ public void deferInit() {
+ mTarget = mTouchProvider.createTouchConsumer(mVelocityTracker);
+ mTarget.getIntrimChoreographer(mMyQueue);
+ }
+
+ @Override
+ public boolean forceToLauncherConsumer() {
+ return mTarget.forceToLauncherConsumer();
+ }
+
+ @Override
+ public boolean deferNextEventToMainThread() {
+ // If our target is still null, defer the next target as well
+ TouchConsumer target = mTarget;
+ return target == null ? true : target.deferNextEventToMainThread();
+ }
+
+ public interface DeferredTouchProvider {
+
+ TouchConsumer createTouchConsumer(VelocityTracker tracker);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
new file mode 100644
index 0000000..22f6e0c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.quickstep;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.quickstep.views.RecentsView;
+
+public class FallbackRecentsView extends RecentsView<RecentsActivity> implements Insettable {
+
+ public FallbackRecentsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setOverviewStateEnabled(true);
+ }
+
+ @Override
+ protected void onAllTasksRemoved() {
+ mActivity.finish();
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ Rect padding = getPadding(dp, getContext());
+ verticalCenter(padding, dp);
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ }
+
+ public static void verticalCenter(Rect padding, DeviceProfile dp) {
+ Rect insets = dp.getInsets();
+ int totalSpace = (padding.top + padding.bottom - insets.top - insets.bottom) / 2;
+ padding.top = insets.top + totalSpace;
+ padding.bottom = insets.bottom + totalSpace;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
new file mode 100644
index 0000000..12757c0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.quickstep;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.util.InstantAppResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of InstantAppResolver using platform APIs
+ */
+@SuppressWarnings("unused")
+public class InstantAppResolverImpl extends InstantAppResolver {
+
+ private static final String TAG = "InstantAppResolverImpl";
+ public static final String COMPONENT_CLASS_MARKER = "@instantapp";
+
+ private final PackageManager mPM;
+
+ public InstantAppResolverImpl(Context context)
+ throws NoSuchMethodException, ClassNotFoundException {
+ mPM = context.getPackageManager();
+ }
+
+ @Override
+ public boolean isInstantApp(ApplicationInfo info) {
+ return info.isInstantApp();
+ }
+
+ @Override
+ public boolean isInstantApp(AppInfo info) {
+ ComponentName cn = info.getTargetComponent();
+ return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
+ }
+
+ @Override
+ public List<ApplicationInfo> getInstantApps() {
+ try {
+ List<ApplicationInfo> result = new ArrayList<>();
+ for (InstantAppInfo iai : mPM.getInstantApps()) {
+ ApplicationInfo info = iai.getApplicationInfo();
+ if (info != null) {
+ result.add(info);
+ }
+ }
+ return result;
+ } catch (SecurityException se) {
+ Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling API: getInstantApps", e);
+ }
+ return super.getInstantApps();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 6e92d83..94b6faa 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -53,6 +53,8 @@
ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_RESET =
ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
+ private static final int ACTION_DEFER_INIT =
+ ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
private final EventArray mEmptyArray = new EventArray();
private final Object mExecutionLock = new Object();
@@ -76,10 +78,10 @@
public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
mMainChoreographer = choreographer;
mConsumer = consumer;
-
mCurrentChoreographer = mMainChoreographer;
mCurrentRunnable = mMainFrameCallback;
- setInterimChoreographerLocked(consumer.getIntrimChoreographer(this));
+
+ setInterimChoreographer(consumer.getIntrimChoreographer(this));
}
public void setInterimChoreographer(Choreographer choreographer) {
@@ -156,6 +158,9 @@
case ACTION_RESET:
mConsumer.reset();
break;
+ case ACTION_DEFER_INIT:
+ mConsumer.deferInit();
+ break;
default:
Log.e(TAG, "Invalid virtual event: " + event.getAction());
}
@@ -204,6 +209,14 @@
queueVirtualAction(ACTION_RESET, 0);
}
+ public void deferInit() {
+ queueVirtualAction(ACTION_DEFER_INIT, 0);
+ }
+
+ public TouchConsumer getConsumer() {
+ return mConsumer;
+ }
+
private static class EventArray extends ArrayList<MotionEvent> {
public int lastEventAction = ACTION_CANCEL;
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index ff7d434..89c9d16 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -17,7 +17,6 @@
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
import static com.android.quickstep.TouchConsumer.isInteractionQuick;
@@ -51,6 +50,8 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -319,13 +320,14 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float progress, long duration) {
+ mIsGoingToHome = Float.compare(progress, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
- ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
+ mStateCallback.setState(mIsGoingToHome
+ ? STATE_SCALED_SNAPSHOT_RECENTS : STATE_SCALED_SNAPSHOT_APP);
}
});
anim.start();
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index 431fb30..f875bb7 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -16,19 +16,20 @@
package com.android.quickstep;
import android.annotation.TargetApi;
+import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.os.Build.VERSION_CODES;
import android.os.UserHandle;
import android.util.LruCache;
import android.util.SparseArray;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.systemui.shared.recents.model.IconLoader;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
@@ -40,11 +41,13 @@
public class NormalizedIconLoader extends IconLoader {
private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
+ private final DrawableFactory mDrawableFactory;
private LauncherIcons mLauncherIcons;
public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
LruCache<ComponentName, ActivityInfo> activityInfoCache) {
super(context, iconCache, activityInfoCache);
+ mDrawableFactory = DrawableFactory.get(context);
}
@Override
@@ -53,7 +56,7 @@
BitmapInfo info = mDefaultIcons.get(userId);
if (info == null) {
info = getBitmapInfo(Resources.getSystem()
- .getDrawable(android.R.drawable.sym_def_app_icon), userId);
+ .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
mDefaultIcons.put(userId, info);
}
@@ -62,23 +65,31 @@
}
@Override
- protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
- return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
+ protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
+ return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
+ false));
}
- private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
+ private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
+ int primaryColor, boolean isInstantApp) {
if (mLauncherIcons == null) {
mLauncherIcons = LauncherIcons.obtain(mContext);
}
+ mLauncherIcons.setWrapperBackgroundColor(primaryColor);
// User version code O, so that the icon is always wrapped in an adaptive icon container.
return mLauncherIcons.createBadgedIconBitmap(drawable, UserHandle.of(userId),
- Build.VERSION_CODES.O);
+ Build.VERSION_CODES.O, isInstantApp);
}
@Override
- protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
- return createBadgedDrawable(
- activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), userId);
+ protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
+ TaskDescription desc) {
+ BitmapInfo bitmapInfo = getBitmapInfo(
+ activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
+ userId,
+ desc.getPrimaryColor(),
+ activityInfo.applicationInfo.isInstantApp());
+ return mDrawableFactory.newIcon(bitmapInfo, activityInfo);
}
}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 488cd72..c96f6d7 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,9 +23,11 @@
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.quickstep.RemoteRunnable.executeSafely;
+import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
+import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.Context;
@@ -37,10 +39,9 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.metrics.LogMaker;
+import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
-import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
import android.view.Display;
@@ -63,50 +64,20 @@
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-class EventLogTags {
- private EventLogTags() {
- } // don't instantiate
-
- /** 524292 sysui_multi_action (content|4) */
- public static final int SYSUI_MULTI_ACTION = 524292;
-
- public static void writeSysuiMultiAction(Object[] content) {
- android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
- }
-}
-
-class MetricsLogger {
- private static MetricsLogger sMetricsLogger;
-
- private static MetricsLogger getLogger() {
- if (sMetricsLogger == null) {
- sMetricsLogger = new MetricsLogger();
- }
- return sMetricsLogger;
- }
-
- protected void saveLog(Object[] rep) {
- EventLogTags.writeSysuiMultiAction(rep);
- }
-
- public void write(LogMaker content) {
- if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
- content.setType(4/*MetricsEvent.TYPE_ACTION*/);
- }
- saveLog(content.serialize());
- }
-}
-
/**
* Touch consumer for handling events originating from an activity other than Launcher
*/
+@TargetApi(Build.VERSION_CODES.P)
public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
private static final String TAG = "ActivityTouchConsumer";
private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
+ private static final int[] DEFERRED_HIT_TARGETS = DEBUG_SHOW_OVERVIEW_BUTTON
+ ? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
private final RunningTaskInfo mRunningTask;
private final RecentsModel mRecentsModel;
@@ -115,6 +86,7 @@
private final MainThreadExecutor mMainThreadExecutor;
private final Choreographer mBackgroundThreadChoreographer;
+ private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
@@ -124,26 +96,24 @@
private BaseSwipeInteractionHandler mInteractionHandler;
private int mDisplayRotation;
private Rect mStableInsets = new Rect();
- private @HitTarget int mDownHitTarget = HIT_TARGET_NONE;
private VelocityTracker mVelocityTracker;
private MotionEventQueue mEventQueue;
-
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private boolean mIsGoingToHome;
public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
- @HitTarget int downHitTarget) {
+ @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
super(base);
mRunningTask = runningTaskInfo;
mRecentsModel = recentsModel;
mHomeIntent = homeIntent;
- mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker = velocityTracker;
mISystemUiProxy = systemUiProxy;
mMainThreadExecutor = mainThreadExecutor;
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
- mDownHitTarget = downHitTarget;
+ mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
}
@Override
@@ -162,7 +132,7 @@
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
- if (!isUsingScreenShot() && mDownHitTarget != HIT_TARGET_BACK) {
+ if (!isUsingScreenShot() && !mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
@@ -204,7 +174,7 @@
if (isUsingScreenShot()) {
startTouchTrackingForScreenshotAnimation();
- } else if (mDownHitTarget == HIT_TARGET_BACK) {
+ } else if (mIsDeferredDownTarget) {
// If we deferred starting the window animation on touch down, then
// start tracking now
startTouchTrackingForWindowAnimation(ev.getEventTime());
@@ -312,7 +282,7 @@
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
// Create the shared handler
final WindowTransformSwipeHandler handler =
- new WindowTransformSwipeHandler(mRunningTask, this);
+ new WindowTransformSwipeHandler(mRunningTask, this, touchTimeMs);
// Preload the plan
mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -350,16 +320,6 @@
TraceHelper.endSection("RecentsController", "Finishing no handler");
controller.finish(false /* toHome */);
}
-
- // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
- // "Recents" activity for app transition tests.
- final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
- builder.setPackageName("com.android.systemui");
- builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
- "com.android.systemui.recents.RecentsActivity");
- builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
- SystemClock.uptimeMillis() - touchTimeMs);
- mMetricsLogger.write(builder);
}
public void onAnimationCanceled() {
@@ -417,6 +377,7 @@
if (mInteractionHandler != null) {
final BaseSwipeInteractionHandler handler = mInteractionHandler;
mInteractionHandler = null;
+ mIsGoingToHome = handler.mIsGoingToHome;
mMainThreadExecutor.execute(handler::reset);
}
}
@@ -471,4 +432,15 @@
}
}
}
+
+ @Override
+ public boolean forceToLauncherConsumer() {
+ return mIsGoingToHome;
+ }
+
+ @Override
+ public boolean deferNextEventToMainThread() {
+ // TODO: Consider also check if the eventQueue is using mainThread of not.
+ return mInteractionHandler != null;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
new file mode 100644
index 0000000..031624a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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.quickstep;
+
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Helper class to handle various atomic commands for switching between Overview.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class OverviewCommandHelper extends InternalStateHandler {
+
+ private static final boolean DEBUG_START_FALLBACK_ACTIVITY = DEBUG_SHOW_OVERVIEW_BUTTON;
+
+ private final Context mContext;
+ private final ActivityManagerWrapper mAM;
+
+ public final Intent homeIntent;
+ public final ComponentName launcher;
+
+ private long mLastToggleTime;
+
+ public OverviewCommandHelper(Context context) {
+ mContext = context;
+ mAM = ActivityManagerWrapper.getInstance();
+
+ homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(context.getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
+ launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+ // Clear the packageName as system can fail to dedupe it b/64108432
+ homeIntent.setComponent(launcher).setPackage(null);
+ }
+
+ public void onOverviewToggle() {
+ if (DEBUG_START_FALLBACK_ACTIVITY) {
+ mContext.startActivity(new Intent(mContext, RecentsActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ return;
+ }
+
+ long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
+ mLastToggleTime = SystemClock.elapsedRealtime();
+
+ if (isOverviewAlmostVisible()) {
+ boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
+ startNonLauncherTask(isQuickTap ? 2 : 1);
+ } else {
+ Intent intent = addToIntent(new Intent(homeIntent));
+ mContext.startActivity(intent);
+ initWhenReady();
+ }
+ }
+
+ private void startNonLauncherTask(int backStackCount) {
+ for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
+ backStackCount--;
+ if (backStackCount == 0) {
+ mAM.startActivityFromRecents(rti.id, null);
+ }
+ }
+ }
+
+ private boolean isOverviewAlmostVisible() {
+ if (clearReference()) {
+ return true;
+ }
+ if (!mAM.getRunningTask().topActivity.equals(launcher)) {
+ return false;
+ }
+ Launcher launcher = getLauncher();
+ return launcher != null && launcher.isStarted() && launcher.isInState(OVERVIEW);
+ }
+
+ private Launcher getLauncher() {
+ return (Launcher) LauncherAppState.getInstance(mContext).getModel().getCallback();
+ }
+
+ @Override
+ protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+ AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+ launcher.getStateManager().goToState(OVERVIEW, alreadyOnHome);
+ clearReference();
+ return false;
+ }
+
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 3c68281..4af89bf 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,7 +15,9 @@
*/
package com.android.quickstep;
+import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
import android.content.Context;
import android.os.Handler;
@@ -60,7 +62,7 @@
}
};
- private static int sFlags;
+ private static int sFlags = DEBUG_SHOW_OVERVIEW_BUTTON ? FLAG_SHOW_OVERVIEW_BUTTON : 0;
public static void setBackButtonVisible(Context context, boolean visible) {
updateFlagOnUi(context, FLAG_HIDE_BACK_BUTTON, !visible);
diff --git a/quickstep/src/com/android/quickstep/PendingAnimation.java b/quickstep/src/com/android/quickstep/PendingAnimation.java
new file mode 100644
index 0000000..d22ef61
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/PendingAnimation.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 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.quickstep;
+
+import android.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to keep track of a running animation.
+ *
+ * This class allows attaching end callbacks to an animation is intended to be used with
+ * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
+ * AnimationListeners are not properly dispatched.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class PendingAnimation {
+
+ private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();
+
+ public final AnimatorSet anim;
+
+ public PendingAnimation(AnimatorSet anim) {
+ this.anim = anim;
+ }
+
+ public void finish(boolean isSuccess) {
+ for (Consumer<Boolean> listeners : mEndListeners) {
+ listeners.accept(isSuccess);
+ }
+ mEndListeners.clear();
+ }
+
+ public void addEndListener(Consumer<Boolean> listener) {
+ mEndListeners.add(listener);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index f28d51c..a154b29 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -19,13 +19,14 @@
import android.view.HapticFeedbackConstants;
import com.android.launcher3.Alarm;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.Utilities;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
/**
* Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -35,8 +36,7 @@
*/
public class QuickScrubController implements OnAlarmListener {
- public static final int QUICK_SWITCH_START_DURATION = 133;
- public static final int QUICK_SWITCH_SNAP_DURATION = 120;
+ public static final int QUICK_SWITCH_START_DURATION = 210;
private static final boolean ENABLE_AUTO_ADVANCE = true;
private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -47,15 +47,17 @@
private final Alarm mAutoAdvanceAlarm;
private final RecentsView mRecentsView;
- private final Launcher mLauncher;
+ private final BaseActivity mActivity;
private boolean mInQuickScrub;
private int mQuickScrubSection;
- private int mStartPage;
+ private boolean mStartedFromHome;
private boolean mHasAlarmRun;
+ private boolean mQuickSwitched;
+ private boolean mFinishedTransitionToQuickScrub;
- public QuickScrubController(Launcher launcher, RecentsView recentsView) {
- mLauncher = launcher;
+ public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
+ mActivity = activity;
mRecentsView = recentsView;
if (ENABLE_AUTO_ADVANCE) {
mAutoAdvanceAlarm = new Alarm();
@@ -65,10 +67,14 @@
public void onQuickScrubStart(boolean startingFromHome) {
mInQuickScrub = true;
- mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
+ mStartedFromHome = startingFromHome;
mQuickScrubSection = 0;
mHasAlarmRun = false;
- mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+ mQuickSwitched = false;
+ mFinishedTransitionToQuickScrub = false;
+
+ snapToNextTaskIfAvailable();
+ mActivity.getUserEventDispatcher().resetActionDurationMillis();
}
public void onQuickScrubEnd() {
@@ -78,11 +84,7 @@
}
int page = mRecentsView.getNextPage();
Runnable launchTaskRunnable = () -> {
- if (page < mRecentsView.getFirstTaskIndex()) {
- mRecentsView.getPageAt(page).performClick();
- } else {
- ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
- }
+ ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
};
int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
* QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
@@ -93,8 +95,8 @@
// No page move needed, just launch it
launchTaskRunnable.run();
}
- mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
- ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
+ mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
+ ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
ContainerType.WORKSPACE : ContainerType.APP);
}
@@ -102,7 +104,9 @@
int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
if (quickScrubSection != mQuickScrubSection) {
int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
- goToPageWithHaptic(pageToGoTo);
+ if (mFinishedTransitionToQuickScrub) {
+ goToPageWithHaptic(pageToGoTo);
+ }
if (ENABLE_AUTO_ADVANCE) {
if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
mAutoAdvanceAlarm.setAlarm(mHasAlarmRun
@@ -116,36 +120,45 @@
}
public void onQuickSwitch() {
- for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
- TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
- if (taskView.getTask().key.id != mRecentsView.getRunningTaskId()) {
- Runnable launchTaskRunnable = () -> taskView.launchTask(true);
- if (mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION)) {
- // Snap to the new page then launch it
- mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
- } else {
- // No need to move page, just launch task directly
- launchTaskRunnable.run();
- }
- break;
- }
- }
- mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
- ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
- ContainerType.WORKSPACE : ContainerType.APP);
+ mQuickSwitched = true;
+ quickSwitchIfReady();
}
- public void snapToPageForCurrentQuickScrubSection() {
- if (mInQuickScrub) {
- goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
+ public void onFinishedTransitionToQuickScrub() {
+ mFinishedTransitionToQuickScrub = true;
+ quickSwitchIfReady();
+ }
+
+ /**
+ * Immediately launches the current task (which we snapped to in onQuickScrubStart) if we've
+ * gotten the onQuickSwitch callback and the transition to quick scrub has completed.
+ */
+ private void quickSwitchIfReady() {
+ if (mQuickSwitched && mFinishedTransitionToQuickScrub) {
+ onQuickScrubEnd();
+ mActivity.getUserEventDispatcher().logActionOnControl(Touch.FLING,
+ ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
+ ContainerType.WORKSPACE : ContainerType.APP);
+ }
+ }
+
+ public void snapToNextTaskIfAvailable() {
+ if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
+ int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
+ goToPageWithHaptic(toPage, QUICK_SWITCH_START_DURATION);
}
}
private void goToPageWithHaptic(int pageToGoTo) {
- pageToGoTo = Utilities.boundToRange(pageToGoTo, mStartPage, mRecentsView.getPageCount() - 1);
+ goToPageWithHaptic(pageToGoTo, -1);
+ }
+
+ private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
+ pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
if (pageToGoTo != mRecentsView.getNextPage()) {
- int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
- * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
+ int duration = overrideDuration > -1 ? overrideDuration
+ : Math.abs(pageToGoTo - mRecentsView.getNextPage())
+ * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
mRecentsView.snapToPage(pageToGoTo, duration);
mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -158,7 +171,7 @@
if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
&& currPage < mRecentsView.getPageCount() - 1) {
goToPageWithHaptic(currPage + 1);
- } else if (mQuickScrubSection == 0 && currPage > mStartPage) {
+ } else if (mQuickScrubSection == 0 && currPage > 0) {
goToPageWithHaptic(currPage - 1);
}
mHasAlarmRun = true;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index f92d773..598c34d 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,34 +15,29 @@
*/
package com.android.quickstep;
-import android.app.ListActivity;
import android.os.Bundle;
-import android.os.UserHandle;
-import android.support.annotation.Nullable;
-import android.widget.ArrayAdapter;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
/**
* A simple activity to show the recently launched tasks
*/
-public class RecentsActivity extends ListActivity {
-
- private ArrayAdapter<Task> mAdapter;
+public class RecentsActivity extends BaseActivity {
@Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
- plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
- UserHandle.myUserId());
+ // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
+ // activity.
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ setDeviceProfile(appState != null
+ ? appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this)
+ : new InvariantDeviceProfile(this).getDeviceProfile(this));
- mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
- mAdapter.addAll(plan.getTaskStack().getTasks());
- setListAdapter(mAdapter);
+ setContentView(R.layout.fallback_recents_activity);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 7fe7751..392b73f 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -16,6 +16,8 @@
package com.android.quickstep;
import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -51,7 +53,6 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel extends TaskStackChangeListener {
-
// We do not need any synchronization for this variable as its only written on UI thread.
private static RecentsModel INSTANCE;
@@ -83,10 +84,16 @@
private int mTaskChangeId;
private ISystemUiProxy mSystemUiProxy;
private boolean mClearAssistCacheOnStackChange = true;
+ private final boolean mPreloadTasksInBackground;
private RecentsModel(Context context) {
mContext = context;
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mPreloadTasksInBackground = !activityManager.isLowRamDevice();
+ mMainThreadExecutor = new MainThreadExecutor();
+
Resources res = context.getResources();
mRecentsTaskLoader = new RecentsTaskLoader(mContext,
res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
@@ -100,8 +107,6 @@
}
};
mRecentsTaskLoader.startLoader(mContext);
-
- mMainThreadExecutor = new MainThreadExecutor();
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
mTaskChangeId = 1;
@@ -162,6 +167,31 @@
}
}
+ @Override
+ public void onTaskStackChangedBackground() {
+ int userId = UserHandle.myUserId();
+ if (!mPreloadTasksInBackground || !checkCurrentUserId(userId, false /* debug */)) {
+ // TODO: Only register this for the current user
+ return;
+ }
+
+ // Preload a fixed number of task icons/thumbnails in the background
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
+ launchOpts.numVisibleTasks = 2;
+ launchOpts.numVisibleTaskThumbnails = 2;
+ launchOpts.onlyLoadForCache = true;
+ launchOpts.onlyLoadPausedActivities = true;
+ launchOpts.loadThumbnails = true;
+ PreloadOptions preloadOpts = new PreloadOptions();
+ preloadOpts.loadTitles = false;
+ plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
+ mRecentsTaskLoader.loadTasks(plan, launchOpts);
+ }
+
public boolean isLoadPlanValid(int resultId) {
return mTaskChangeId == resultId;
}
@@ -178,6 +208,19 @@
return mSystemUiProxy;
}
+ public void onStart() {
+ mRecentsTaskLoader.startLoader(mContext);
+ mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
+ }
+
+ public void onTrimMemory(int level) {
+ if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
+ // We already stop the loader in UI_HIDDEN, so stop the high res loader as well
+ mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
+ }
+ mRecentsTaskLoader.onTrimMemory(level);
+ }
+
@WorkerThread
public void preloadAssistData(int taskId, Bundle data) {
mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/RecentsRootView.java b/quickstep/src/com/android/quickstep/RecentsRootView.java
new file mode 100644
index 0000000..3c69dbf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsRootView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 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.quickstep;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class RecentsRootView extends InsettableFrameLayout {
+
+ private final BaseActivity mActivity;
+
+ public RecentsRootView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mActivity = BaseActivity.fromContext(context);
+ }
+
+ @TargetApi(23)
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ // Update device profile before notifying the children.
+ mActivity.getDeviceProfile().updateInsets(insets);
+ setInsets(insets);
+ return true; // I'll take it from here
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
+ // modifying child layout params.
+ if (!insets.equals(mInsets)) {
+ super.setInsets(insets);
+ }
+ setBackground(insets.top == 0 ? null
+ : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
deleted file mode 100644
index ec0716c..0000000
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/*
- * Copyright (C) 2017 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.quickstep;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.TaskView.CURVE_FACTOR;
-import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
-
-import android.animation.LayoutTransition;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.OverviewState;
-import com.android.launcher3.uioverrides.RecentsViewStateController;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.ArrayList;
-
-/**
- * A list of recent tasks.
- */
-public class RecentsView extends PagedView implements Insettable {
-
- private static final Rect sTempStableInsets = new Rect();
-
- public static final int SCROLL_TYPE_NONE = 0;
- public static final int SCROLL_TYPE_TASK = 1;
- public static final int SCROLL_TYPE_WORKSPACE = 2;
-
- private final Launcher mLauncher;
- private QuickScrubController mQuickScrubController;
- private final ScrollState mScrollState = new ScrollState();
- private boolean mOverviewStateEnabled;
- private boolean mTaskStackListenerRegistered;
- private LayoutTransition mLayoutTransition;
- private Runnable mNextPageSwitchRunnable;
-
- /**
- * TODO: Call reloadIdNeeded in onTaskStackChanged.
- */
- private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
- @Override
- public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
- for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
- final TaskView taskView = (TaskView) getChildAt(i);
- if (taskView.getTask().key.id == taskId) {
- taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
- return;
- }
- }
- }
- };
-
- private RecentsViewStateController mStateController;
- private int mFirstTaskIndex;
-
- private final RecentsModel mModel;
- private int mLoadPlanId = -1;
-
- // Only valid until the launcher state changes to NORMAL
- private int mRunningTaskId = -1;
-
- private Bitmap mScrim;
- private Paint mFadePaint;
- private Shader mFadeShader;
- private Matrix mFadeMatrix;
- private boolean mScrimOnLeft;
-
- private boolean mFirstTaskIconScaledDown = false;
-
- public RecentsView(Context context) {
- this(context, null);
- }
-
- public RecentsView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
- enableFreeScroll(true);
- setupLayoutTransition();
- setClipToOutline(true);
-
- mLauncher = Launcher.getLauncher(context);
- mQuickScrubController = new QuickScrubController(mLauncher, this);
- mModel = RecentsModel.getInstance(context);
-
- mScrollState.isRtl = mIsRtl;
- }
-
- public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
- for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
- final TaskView taskView = (TaskView) getChildAt(i);
- if (taskView.getTask().key.id == taskId) {
- taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
- taskView.setAlpha(1);
- return taskView;
- }
- }
- return null;
- }
-
- private void setupLayoutTransition() {
- // We want to show layout transitions when pages are deleted, to close the gap.
- mLayoutTransition = new LayoutTransition();
- mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
- mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
-
- mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
- mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
- setLayoutTransition(mLayoutTransition);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mFirstTaskIndex = getPageCount();
- }
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- updateTaskStackListenerState();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- updateTaskStackListenerState();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- updateTaskStackListenerState();
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- DeviceProfile dp = Launcher.getLauncher(getContext()).getDeviceProfile();
- Rect padding = getPadding(dp, getContext());
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- lp.bottomMargin = padding.bottom;
- setLayoutParams(lp);
-
- setPadding(padding.left, padding.top, padding.right, 0);
-
- if (dp.isVerticalBarLayout()) {
- boolean wasScrimOnLeft = mScrimOnLeft;
- mScrimOnLeft = dp.isSeascape();
-
- if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
- Drawable scrim = getContext().getDrawable(mScrimOnLeft
- ? R.drawable.recents_horizontal_scrim_left
- : R.drawable.recents_horizontal_scrim_right);
- if (scrim instanceof BitmapDrawable) {
- mScrim = ((BitmapDrawable) scrim).getBitmap();
- mFadePaint = new Paint();
- mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
- mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
- mFadeMatrix = new Matrix();
- } else {
- mScrim = null;
- }
- }
- } else {
- mScrim = null;
- mFadePaint = null;
- mFadeShader = null;
- mFadeMatrix = null;
- }
- }
-
- public int getFirstTaskIndex() {
- return mFirstTaskIndex;
- }
-
- public boolean isTaskViewVisible(TaskView tv) {
- // For now, just check if it's the active task or an adjacent task
- return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
- }
-
- public TaskView getTaskView(int taskId) {
- for (int i = getFirstTaskIndex(); i < getChildCount(); i++) {
- TaskView tv = (TaskView) getChildAt(i);
- if (tv.getTask().key.id == taskId) {
- return tv;
- }
- }
- return null;
- }
-
- public void setStateController(RecentsViewStateController stateController) {
- mStateController = stateController;
- }
-
- public RecentsViewStateController getStateController() {
- return mStateController;
- }
-
- public void setOverviewStateEnabled(boolean enabled) {
- mOverviewStateEnabled = enabled;
- updateTaskStackListenerState();
- }
-
- public void setNextPageSwitchRunnable(Runnable r) {
- mNextPageSwitchRunnable = r;
- }
-
- @Override
- protected void onPageEndTransition() {
- super.onPageEndTransition();
- if (mNextPageSwitchRunnable != null) {
- mNextPageSwitchRunnable.run();
- mNextPageSwitchRunnable = null;
- }
- }
-
- private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
- final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
- if (stack == null) {
- removeAllViews();
- return;
- }
-
- int oldChildCount = getChildCount();
-
- // Ensure there are as many views as there are tasks in the stack (adding and trimming as
- // necessary)
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
- setLayoutTransition(null);
-
- final int requiredChildCount = tasks.size() + mFirstTaskIndex;
- for (int i = getChildCount(); i < requiredChildCount; i++) {
- final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
- addView(taskView);
- }
- while (getChildCount() > requiredChildCount) {
- final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
- removeView(taskView);
- loader.unloadTaskData(taskView.getTask());
- }
- setLayoutTransition(mLayoutTransition);
-
- // Rebind and reset all task views
- for (int i = tasks.size() - 1; i >= 0; i--) {
- final Task task = tasks.get(i);
- final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
- taskView.bind(task);
- taskView.resetVisualProperties();
- loader.loadTaskData(task);
- }
- updateCurveProperties();
- applyIconScale(false /* animate */);
-
- if (oldChildCount != getChildCount()) {
- mQuickScrubController.snapToPageForCurrentQuickScrubSection();
- }
- }
-
- private void updateTaskStackListenerState() {
- boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
- && getWindowVisibility() == VISIBLE;
- if (registerStackListener != mTaskStackListenerRegistered) {
- if (registerStackListener) {
- ActivityManagerWrapper.getInstance()
- .registerTaskStackListener(mTaskStackListener);
- reloadIfNeeded();
- } else {
- ActivityManagerWrapper.getInstance()
- .unregisterTaskStackListener(mTaskStackListener);
- }
- mTaskStackListenerRegistered = registerStackListener;
- }
- }
-
- private static Rect getPadding(DeviceProfile profile, Context context) {
- WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
- Rect padding = new Rect(profile.workspacePadding);
-
- float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
- float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
-
- float overviewHeight, overviewWidth;
- if (profile.isVerticalBarLayout()) {
- // Use the same padding on both sides for symmetry.
- float availableWidth = taskWidth - 2 * Math.max(padding.left, padding.right);
- float availableHeight = profile.availableHeightPx - padding.top
- - sTempStableInsets.top - Math.max(padding.bottom,
- profile.heightPx * (1 - OverviewState.getVerticalProgress(profile, context)));
- float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
- overviewHeight = taskHeight * scaledRatio;
- overviewWidth = taskWidth * scaledRatio;
-
- } else {
- overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- - sTempStableInsets.top;
- overviewWidth = taskWidth * overviewHeight / taskHeight;
- }
-
- padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
- - Math.round(overviewHeight);
- padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
- return padding;
- }
-
- /**
- * Sets the {@param outRect} to match the position of the first tile such that it is scaled
- * down to match the 2nd taskView.
- * @return returns the factor which determines the scaling factor for the second task.
- */
- public static float getScaledDownPageRect(DeviceProfile dp, Context context, Rect outRect) {
- getPageRect(dp, context, outRect);
-
- int pageSpacing = context.getResources()
- .getDimensionPixelSize(R.dimen.recents_page_spacing);
- float halfScreenWidth = dp.widthPx * 0.5f;
- float halfPageWidth = outRect.width() * 0.5f;
- float pageCenter = outRect.right + pageSpacing + halfPageWidth;
- float distanceFromCenter = Math.abs(halfScreenWidth - pageCenter);
- float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
- float linearInterpolation = Math.min(1, distanceFromCenter / distanceToReachEdge);
-
- float scale = 1 - CURVE_INTERPOLATOR.getInterpolation(linearInterpolation) * CURVE_FACTOR;
-
- int topMargin = context.getResources()
- .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- outRect.top -= topMargin;
- Utilities.scaleRectAboutCenter(outRect, scale);
- outRect.top += (int) (scale * topMargin);
- return linearInterpolation;
- }
-
- public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
- Rect targetPadding = getPadding(grid, context);
- Rect insets = grid.getInsets();
- outRect.set(
- targetPadding.left + insets.left,
- targetPadding.top + insets.top,
- grid.widthPx - targetPadding.right - insets.right,
- grid.heightPx - targetPadding.bottom - insets.bottom);
- outRect.top += context.getResources()
- .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- }
-
- @Override
- public void computeScroll() {
- super.computeScroll();
- updateCurveProperties();
- }
-
- /**
- * Scales and adjusts translation of adjacent pages as if on a curved carousel.
- */
- public void updateCurveProperties() {
- if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
- return;
- }
- final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
- mScrollState.lastScrollType = SCROLL_TYPE_NONE;
- final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
- final int halfScreenWidth = getMeasuredWidth() / 2;
- final int pageSpacing = mPageSpacing;
-
- final int pageCount = getPageCount();
- for (int i = 0; i < pageCount; i++) {
- View page = getPageAt(i);
- int pageCenter = page.getLeft() + halfPageWidth;
- mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
- float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
- mScrollState.linearInterpolation = Math.min(1,
- Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
- mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
- }
- }
-
- public void onTaskDismissed(TaskView taskView) {
- ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
- removeView(taskView);
- if (getTaskCount() == 0) {
- mLauncher.getStateManager().goToState(NORMAL);
- }
- }
-
- public void reset() {
- mRunningTaskId = -1;
- setCurrentPage(0);
- }
-
- public int getTaskCount() {
- return getChildCount() - mFirstTaskIndex;
- }
-
- public int getRunningTaskId() {
- return mRunningTaskId;
- }
-
- /**
- * Reloads the view if anything in recents changed.
- */
- public void reloadIfNeeded() {
- if (!mModel.isLoadPlanValid(mLoadPlanId)) {
- mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
- }
- }
-
- /**
- * Ensures that the first task in the view represents {@param task} and reloads the view
- * if needed. This allows the swipe-up gesture to assume that the first tile always
- * corresponds to the correct task.
- * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
- * is called.
- * Also scrolls the view to this task
- */
- public void showTask(int runningTaskId) {
- boolean needsReload = false;
- if (getTaskCount() == 0) {
- needsReload = true;
- // Add an empty view for now
- setLayoutTransition(null);
- final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
- .inflate(R.layout.task, this, false);
- addView(taskView, mFirstTaskIndex);
- setLayoutTransition(mLayoutTransition);
- }
- if (!needsReload) {
- needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
- }
- if (needsReload) {
- mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
- }
- mRunningTaskId = runningTaskId;
- setCurrentPage(mFirstTaskIndex);
- if (mCurrentPage >= mFirstTaskIndex) {
- getPageAt(mCurrentPage).setAlpha(0);
- }
- }
-
- public QuickScrubController getQuickScrubController() {
- return mQuickScrubController;
- }
-
- public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
- if (mFirstTaskIconScaledDown == isScaledDown) {
- return;
- }
- mFirstTaskIconScaledDown = isScaledDown;
- applyIconScale(animate);
- }
-
- private void applyIconScale(boolean animate) {
- float scale = mFirstTaskIconScaledDown ? 0 : 1;
- TaskView firstTask = (TaskView) getChildAt(mFirstTaskIndex);
- if (firstTask != null) {
- if (animate) {
- firstTask.animateIconToScale(scale);
- } else {
- firstTask.setIconScale(scale);
- }
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mScrim == null) {
- super.draw(canvas);
- return;
- }
-
- final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
-
- int length = mScrim.getWidth();
- int height = getHeight();
- int saveCount = canvas.getSaveCount();
-
- int scrimLeft;
- if (mScrimOnLeft) {
- scrimLeft = getScrollX();
- } else {
- scrimLeft = getScrollX() + getWidth() - length;
- }
- canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
- super.draw(canvas);
-
- mFadeMatrix.setTranslate(scrimLeft, 0);
- mFadeShader.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFadeShader);
- canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
- canvas.restoreToCount(saveCount);
- }
-
- public interface PageCallbacks {
-
- /**
- * Updates the page UI based on scroll params and returns the type of scroll
- * effect performed.
- *
- * @see #SCROLL_TYPE_NONE
- * @see #SCROLL_TYPE_TASK
- * @see #SCROLL_TYPE_WORKSPACE
- */
- int onPageScroll(ScrollState scrollState);
- }
-
- public static class ScrollState {
-
- public boolean isRtl;
- public int lastScrollType;
-
- public int halfPageWidth;
- public float distanceFromScreenCenter;
- public float linearInterpolation;
-
- public float prevPageExtraWidth;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/SimpleTaskView.java b/quickstep/src/com/android/quickstep/SimpleTaskView.java
deleted file mode 100644
index 8425fa3..0000000
--- a/quickstep/src/com/android/quickstep/SimpleTaskView.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 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.quickstep;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-
-/**
- * A simple view which keeps its size proportional to the display size
- */
-public class SimpleTaskView extends View {
-
- private static final Point sTempPoint = new Point();
-
- public SimpleTaskView(Context context) {
- super(context);
- }
-
- public SimpleTaskView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int height = MeasureSpec.getSize(heightMeasureSpec);
- getContext().getSystemService(WindowManager.class)
- .getDefaultDisplay().getRealSize(sTempPoint);
-
- int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
- setMeasuredDimension(width, height);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 75d8619..eebfb91 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -16,27 +16,39 @@
package com.android.quickstep;
-import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.util.Collections;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -86,9 +98,10 @@
}
}
- public static class SplitScreen extends TaskSystemShortcut {
+ public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener {
private Handler mHandler;
+ private TaskView mTaskView;
public SplitScreen() {
super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -104,22 +117,55 @@
if (!task.isDockable) {
return null;
}
+ mTaskView = taskView;
return (v -> {
- final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(true);
- final Consumer<Boolean> resultCallback = success -> {
- if (success) {
- launcher.<RecentsView>getOverviewPanel().removeView(taskView);
- }
- };
- ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
- task.key, options, resultCallback, mHandler);
+ AbstractFloatingView.closeOpenViews(launcher, true,
+ AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
- // TODO improve transition; see:
- // DockedFirstAnimationFrameEvent
- // RecentsTransitionHelper#composeDockAnimationSpec
- // WindowManagerWrapper#overridePendingAppTransitionMultiThumbFuture
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key.id,
+ ActivityOptionsCompat.makeSplitScreenOptions(true))) {
+ ISystemUiProxy sysUiProxy = RecentsModel.getInstance(launcher).getSystemUiProxy();
+ try {
+ sysUiProxy.onSplitScreenInvoked();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
+ return;
+ }
+
+ final Runnable animStartedListener = () -> {
+ mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
+ launcher.<RecentsView>getOverviewPanel().removeView(taskView);
+ };
+
+ final int[] position = new int[2];
+ taskView.getLocationOnScreen(position);
+ final int width = (int) (taskView.getWidth() * taskView.getScaleX());
+ final int height = (int) (taskView.getHeight() * taskView.getScaleY());
+ final Rect taskBounds = new Rect(position[0], position[1],
+ position[0] + width, position[1] + height);
+
+ Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
+ taskBounds.width(), taskBounds.height(), taskView, 1f, Color.BLACK);
+ AppTransitionAnimationSpecsFuture future =
+ new AppTransitionAnimationSpecsFuture(mHandler) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ return Collections.singletonList(new AppTransitionAnimationSpecCompat(
+ task.key.id, thumbnail, taskBounds));
+ }
+ };
+ WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+ future, animStartedListener, mHandler, true /* scaleUp */);
+ }
});
}
+
+ @Override
+ public boolean onPreDraw() {
+ mTaskView.getViewTreeObserver().removeOnPreDrawListener(this);
+ WindowManagerWrapper.getInstance().endProlongedAnimations();
+ return true;
+ }
}
public static class Pin extends TaskSystemShortcut {
@@ -137,7 +183,11 @@
if (sysUiProxy == null) {
return null;
}
- if (!ActivityManagerWrapper.getInstance().isLockToAppEnabled()) {
+ if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
+ return null;
+ }
+ if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+ // We shouldn't be able to pin while an app is locked.
return null;
}
return view -> {
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index f35f6a6..768fbda 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -21,8 +21,6 @@
import android.view.Choreographer;
import android.view.MotionEvent;
-import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
@@ -64,4 +62,14 @@
default Choreographer getIntrimChoreographer(MotionEventQueue queue) {
return null;
}
+
+ default void deferInit() { }
+
+ default boolean deferNextEventToMainThread() {
+ return false;
+ }
+
+ default boolean forceToLauncherConsumer() {
+ return false;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c166292..a522ef1 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,15 +21,13 @@
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
+import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.NORMAL;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
-import android.content.ComponentName;
import android.content.Intent;
-import android.content.pm.ResolveInfo;
import android.graphics.PointF;
import android.os.Build;
import android.os.Handler;
@@ -40,15 +38,18 @@
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -60,6 +61,8 @@
@TargetApi(Build.VERSION_CODES.O)
public class TouchInteractionService extends Service {
+ public static final boolean DEBUG_SHOW_OVERVIEW_BUTTON = false;
+
private static final SparseArray<String> sMotionEventNames;
static {
@@ -117,7 +120,6 @@
@Override
public void onQuickScrubStart() {
mEventQueue.onQuickScrubStart();
- sQuickScrubEnabled = true;
TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
}
@@ -130,35 +132,37 @@
public void onQuickScrubEnd() {
mEventQueue.onQuickScrubEnd();
TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
- sQuickScrubEnabled = false;
}
+
+ @Override
+ public void onOverviewToggle() {
+ mOverviewCommandHelper.onOverviewToggle();
+ }
+
+ @Override
+ public void onOverviewShown(boolean triggeredFromAltTab) { }
+
+ @Override
+ public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
};
private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
private static boolean sConnected = false;
- private static boolean sQuickScrubEnabled = false;
public static boolean isConnected() {
return sConnected;
}
- public static boolean isQuickScrubEnabled() {
- return sQuickScrubEnabled;
- }
-
private ActivityManagerWrapper mAM;
- private RunningTaskInfo mRunningTask;
private RecentsModel mRecentsModel;
- private Intent mHomeIntent;
- private ComponentName mLauncher;
private MotionEventQueue mEventQueue;
private MainThreadExecutor mMainThreadExecutor;
private ISystemUiProxy mISystemUiProxy;
+ private OverviewCommandHelper mOverviewCommandHelper;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
- private MotionEventQueue mNoOpEventQueue;
@Override
public void onCreate() {
@@ -166,19 +170,9 @@
mAM = ActivityManagerWrapper.getInstance();
mRecentsModel = RecentsModel.getInstance(this);
mMainThreadExecutor = new MainThreadExecutor();
-
- mHomeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
- mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
- // Clear the packageName as system can fail to dedupe it b/64108432
- mHomeIntent.setComponent(mLauncher).setPackage(null);
-
+ mOverviewCommandHelper = new OverviewCommandHelper(this);
mMainThreadChoreographer = Choreographer.getInstance();
- mNoOpEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
- mEventQueue = mNoOpEventQueue;
+ mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
sConnected = true;
@@ -190,7 +184,6 @@
@Override
public void onDestroy() {
sConnected = false;
- sQuickScrubEnabled = false;
super.onDestroy();
}
@@ -201,31 +194,45 @@
}
private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
- mRunningTask = mAM.getRunningTask();
-
mEventQueue.reset();
-
- if (mRunningTask == null) {
- mEventQueue = mNoOpEventQueue;
- } else if (mRunningTask.topActivity.equals(mLauncher)) {
- mEventQueue = getLauncherEventQueue();
- } else {
+ TouchConsumer oldConsumer = mEventQueue.getConsumer();
+ if (oldConsumer.deferNextEventToMainThread()) {
mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
- new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
- mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
- mBackgroundThreadChoreographer, downHitTarget));
+ new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
+ oldConsumer.forceToLauncherConsumer(), v)));
+ mEventQueue.deferInit();
+ } else {
+ mEventQueue = new MotionEventQueue(
+ mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
}
}
- private MotionEventQueue getLauncherEventQueue() {
+ private TouchConsumer getCurrentTouchConsumer(
+ @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
+ RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
+
+ if (runningTaskInfo == null && !forceToLauncher) {
+ return mNoOpTouchConsumer;
+ } else if (forceToLauncher ||
+ runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
+ return getLauncherConsumer();
+ } else {
+ if (tracker == null) {
+ tracker = VelocityTracker.obtain();
+ }
+ return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
+ mOverviewCommandHelper.homeIntent, mISystemUiProxy, mMainThreadExecutor,
+ mBackgroundThreadChoreographer, downHitTarget, tracker);
+ }
+ }
+
+ private TouchConsumer getLauncherConsumer() {
Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
if (launcher == null) {
- return mNoOpEventQueue;
+ return mNoOpTouchConsumer;
}
-
View target = launcher.getDragLayer();
- return new MotionEventQueue(mMainThreadChoreographer,
- new LauncherTouchConsumer(launcher, target));
+ return new LauncherTouchConsumer(launcher, target);
}
private static class LauncherTouchConsumer implements TouchConsumer {
@@ -310,13 +317,12 @@
if (TouchConsumer.isInteractionQuick(interactionType)) {
Runnable action = () -> {
Runnable onComplete = null;
- if (interactionType == INTERACTION_QUICK_SCRUB) {
- mQuickScrubController.onQuickScrubStart(true);
- } else if (interactionType == INTERACTION_QUICK_SWITCH) {
+ if (interactionType == INTERACTION_QUICK_SWITCH) {
onComplete = mQuickScrubController::onQuickSwitch;
}
- mLauncher.getStateManager().goToState(OVERVIEW, true, 0,
- QUICK_SWITCH_START_DURATION, onComplete);
+ LauncherState fromState = mLauncher.getStateManager().getState();
+ mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
+ mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
};
if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 19942c3..af09842 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,9 +15,12 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
@@ -32,16 +35,17 @@
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.metrics.LogMaker;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemClock;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
@@ -67,9 +71,12 @@
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
@@ -77,6 +84,40 @@
import java.util.StringJoiner;
+class EventLogTags {
+ private EventLogTags() {
+ } // don't instantiate
+
+ /** 524292 sysui_multi_action (content|4) */
+ public static final int SYSUI_MULTI_ACTION = 524292;
+
+ public static void writeSysuiMultiAction(Object[] content) {
+ android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
+ }
+}
+
+class MetricsLogger {
+ private static MetricsLogger sMetricsLogger;
+
+ private static MetricsLogger getLogger() {
+ if (sMetricsLogger == null) {
+ sMetricsLogger = new MetricsLogger();
+ }
+ return sMetricsLogger;
+ }
+
+ protected void saveLog(Object[] rep) {
+ EventLogTags.writeSysuiMultiAction(rep);
+ }
+
+ public void write(LogMaker content) {
+ if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
+ content.setType(4/*MetricsEvent.TYPE_ACTION*/);
+ }
+ saveLog(content.serialize());
+ }
+}
+
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
@@ -189,11 +230,17 @@
private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
private Matrix mTmpMatrix = new Matrix();
+ private final long mTouchTimeMs;
+ private long mLauncherFrameDrawnTime;
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
- WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) {
+ WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs) {
mContext = context;
mRunningTaskId = runningTaskInfo.id;
- mInputConsumer.registerInputConsumer();
+ mTouchTimeMs = touchTimeMs;
+ // Register the input consumer on the UI thread, to ensure that it runs after any pending
+ // unregister calls
+ mMainExecutor.execute(mInputConsumer::registerInputConsumer);
initStateCallbacks();
}
@@ -323,7 +370,8 @@
// For the duration of the gesture, lock the screen orientation to ensure that we do not
// rotate mid-quickscrub
- mLauncher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+ mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
+
mRecentsView = mLauncher.getOverviewPanel();
mQuickScrubController = mRecentsView.getQuickScrubController();
mLauncherLayoutListener = new LauncherLayoutListener(mLauncher);
@@ -416,11 +464,26 @@
if (mLauncherDrawnCallback != null) {
mLauncherDrawnCallback.run();
}
+ mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
private void initializeLauncherAnimationController() {
mLauncherLayoutListener.setHandler(this);
onLauncherLayoutChanged();
+
+ final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
+ // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
+ // "Recents" activity for app transition tests for the app-to-recents case.
+ final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
+ builder.setPackageName("com.android.systemui");
+ builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
+ "com.android.systemui.recents.RecentsActivity");
+ builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
+ transitionDelay);
+ mMetricsLogger.write(builder);
+ if (LatencyTrackerCompat.isEnabled(mContext)) {
+ LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
+ }
}
public void updateInteractionType(@InteractionType int interactionType) {
@@ -442,6 +505,8 @@
}
private void onQuickInteractionStart() {
+ mLauncher.getStateManager().goToState(FAST_OVERVIEW,
+ mWasLauncherAlreadyVisible || mGestureStarted);
mQuickScrubController.onQuickScrubStart(false);
}
@@ -509,8 +574,13 @@
for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
if (app.mode == MODE_CLOSING) {
transaction.setMatrix(app.leash, mTmpMatrix)
- .setWindowCrop(app.leash, mClipRect)
- .show(app.leash);
+ .setWindowCrop(app.leash, mClipRect);
+
+ if (app.isNotInRecents) {
+ transaction.setAlpha(app.leash, 1 - shift);
+ }
+
+ transaction.show(app.leash);
}
}
transaction.apply();
@@ -525,9 +595,8 @@
mLauncherTransitionController.setPlayFraction(shift);
// Make sure the window follows the first task if it moves, e.g. during quick scrub.
- int firstTaskIndex = mRecentsView.getFirstTaskIndex();
- View firstTask = mRecentsView.getPageAt(firstTaskIndex);
- int scrollForFirstTask = mRecentsView.getScrollForPage(firstTaskIndex);
+ View firstTask = mRecentsView.getPageAt(0);
+ int scrollForFirstTask = mRecentsView.getScrollForPage(0);
int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
if (offsetFromFirstTask != 0) {
synchronized (mTargetRect) {
@@ -658,13 +727,14 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float progress, long duration) {
+ mIsGoingToHome = Float.compare(progress, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
- ? STATE_SCALED_CONTROLLER_APP : STATE_SCALED_CONTROLLER_RECENTS);
+ setStateOnUiThread(mIsGoingToHome ?
+ STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
}
});
anim.start();
@@ -685,7 +755,7 @@
}
private void invalidateHandler() {
- mCurrentShift.cancelAnimation();
+ mCurrentShift.finishAnimation();
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
@@ -701,7 +771,8 @@
mLauncherLayoutListener.close(false);
// Restore the requested orientation to the user preference after the gesture has ended
- mLauncher.updateRequestedOrientation();
+ mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
+
mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
new file mode 100644
index 0000000..7989e84
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 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.quickstep.views;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * {@link RecentsView} used in Launcher activity
+ */
+public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
+
+ private Bitmap mScrim;
+ private Paint mFadePaint;
+ private Shader mFadeShader;
+ private Matrix mFadeMatrix;
+ private boolean mScrimOnLeft;
+
+ public LauncherRecentsView(Context context) {
+ this(context, null);
+ }
+
+ public LauncherRecentsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ Rect padding = getPadding(dp, getContext());
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.bottomMargin = padding.bottom;
+ setLayoutParams(lp);
+
+ setPadding(padding.left, padding.top, padding.right, 0);
+
+ if (dp.isVerticalBarLayout()) {
+ boolean wasScrimOnLeft = mScrimOnLeft;
+ mScrimOnLeft = dp.isSeascape();
+
+ if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
+ Drawable scrim = getContext().getDrawable(mScrimOnLeft
+ ? R.drawable.recents_horizontal_scrim_left
+ : R.drawable.recents_horizontal_scrim_right);
+ if (scrim instanceof BitmapDrawable) {
+ mScrim = ((BitmapDrawable) scrim).getBitmap();
+ mFadePaint = new Paint();
+ mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
+ mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
+ mFadeMatrix = new Matrix();
+ } else {
+ mScrim = null;
+ }
+ }
+ } else {
+ mScrim = null;
+ mFadePaint = null;
+ mFadeShader = null;
+ mFadeMatrix = null;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mScrim == null) {
+ super.draw(canvas);
+ return;
+ }
+
+ final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+
+ int length = mScrim.getWidth();
+ int height = getHeight();
+ int saveCount = canvas.getSaveCount();
+
+ int scrimLeft;
+ if (mScrimOnLeft) {
+ scrimLeft = getScrollX();
+ } else {
+ scrimLeft = getScrollX() + getWidth() - length;
+ }
+ canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
+ super.draw(canvas);
+
+ mFadeMatrix.setTranslate(scrimLeft, 0);
+ mFadeShader.setLocalMatrix(mFadeMatrix);
+ mFadePaint.setShader(mFadeShader);
+ canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ protected void onAllTasksRemoved() {
+ mActivity.getStateManager().goToState(NORMAL);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
new file mode 100644
index 0000000..23e6e5b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2017 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.quickstep.views;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.PendingAnimation;
+import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.RecentsModel;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * A list of recent tasks.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public abstract class RecentsView<T extends BaseActivity>
+ extends PagedView implements OnSharedPreferenceChangeListener {
+
+ private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
+
+ private static final Rect sTempStableInsets = new Rect();
+
+ protected final T mActivity;
+ private final QuickScrubController mQuickScrubController;
+ private final float mFastFlingVelocity;
+ private final RecentsModel mModel;
+
+ private final ScrollState mScrollState = new ScrollState();
+ // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
+ private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
+
+ /**
+ * TODO: Call reloadIdNeeded in onTaskStackChanged.
+ */
+ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ @Override
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
+ return;
+ }
+ }
+ }
+ };
+
+ private int mLoadPlanId = -1;
+
+ // Only valid until the launcher state changes to NORMAL
+ private int mRunningTaskId = -1;
+
+ private boolean mFirstTaskIconScaledDown = false;
+
+ private boolean mOverviewStateEnabled;
+ private boolean mTaskStackListenerRegistered;
+ private Runnable mNextPageSwitchRunnable;
+
+ private PendingAnimation mPendingAnimation;
+
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
+ enableFreeScroll(true);
+ setClipToOutline(true);
+
+ mFastFlingVelocity = getResources()
+ .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
+ mActivity = (T) BaseActivity.fromContext(context);
+ mQuickScrubController = new QuickScrubController(mActivity, this);
+ mModel = RecentsModel.getInstance(context);
+
+ onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ if (s.equals(PREF_FLIP_RECENTS)) {
+ mIsRtl = Utilities.isRtl(getResources());
+ if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
+ mIsRtl = !mIsRtl;
+ }
+ setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+ }
+ }
+
+ public boolean isRtl() {
+ return mIsRtl;
+ }
+
+ public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
+ taskView.setAlpha(1);
+ return taskView;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ updateTaskStackListenerState();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateTaskStackListenerState();
+ Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ updateTaskStackListenerState();
+ Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+
+ // Clear the task data for the removed child if it was visible
+ Task task = ((TaskView) child).getTask();
+ if (mHasVisibleTaskData.get(task.key.id)) {
+ mHasVisibleTaskData.delete(task.key.id);
+ RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+ loader.unloadTaskData(task);
+ loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ }
+ }
+
+ public boolean isTaskViewVisible(TaskView tv) {
+ // For now, just check if it's the active task or an adjacent task
+ return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
+ }
+
+ public TaskView getTaskView(int taskId) {
+ for (int i = 0; i < getChildCount(); i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ if (tv.getTask().key.id == taskId) {
+ return tv;
+ }
+ }
+ return null;
+ }
+
+ public void setOverviewStateEnabled(boolean enabled) {
+ mOverviewStateEnabled = enabled;
+ updateTaskStackListenerState();
+ }
+
+ public void setNextPageSwitchRunnable(Runnable r) {
+ mNextPageSwitchRunnable = r;
+ }
+
+ @Override
+ protected void onPageEndTransition() {
+ super.onPageEndTransition();
+ if (mNextPageSwitchRunnable != null) {
+ mNextPageSwitchRunnable.run();
+ mNextPageSwitchRunnable = null;
+ }
+ }
+
+ private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+ if (mPendingAnimation != null) {
+ mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
+ return;
+ }
+ TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
+ if (stack == null) {
+ removeAllViews();
+ return;
+ }
+
+ int oldChildCount = getChildCount();
+
+ // Ensure there are as many views as there are tasks in the stack (adding and trimming as
+ // necessary)
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
+
+ final int requiredChildCount = tasks.size();
+ for (int i = getChildCount(); i < requiredChildCount; i++) {
+ final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
+ addView(taskView);
+ }
+ while (getChildCount() > requiredChildCount) {
+ final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+ removeView(taskView);
+ }
+
+ // Unload existing visible task data
+ unloadVisibleTaskData();
+
+ // Rebind and reset all task views
+ for (int i = requiredChildCount - 1; i >= 0; i--) {
+ final int pageIndex = requiredChildCount - i - 1;
+ final Task task = tasks.get(i);
+ final TaskView taskView = (TaskView) getChildAt(pageIndex);
+ taskView.bind(task);
+ }
+ resetTaskVisuals();
+ applyIconScale(false /* animate */);
+
+ if (oldChildCount != getChildCount()) {
+ mQuickScrubController.snapToNextTaskIfAvailable();
+ }
+ }
+
+ public void resetTaskVisuals() {
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ ((TaskView) getChildAt(i)).resetVisualProperties();
+ }
+
+ updateCurveProperties();
+ // Update the set of visible task's data
+ loadVisibleTaskData();
+ }
+
+ private void updateTaskStackListenerState() {
+ boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+ && getWindowVisibility() == VISIBLE;
+ if (registerStackListener != mTaskStackListenerRegistered) {
+ if (registerStackListener) {
+ ActivityManagerWrapper.getInstance()
+ .registerTaskStackListener(mTaskStackListener);
+ reloadIfNeeded();
+ } else {
+ ActivityManagerWrapper.getInstance()
+ .unregisterTaskStackListener(mTaskStackListener);
+ }
+ mTaskStackListenerRegistered = registerStackListener;
+ }
+ }
+
+ protected static Rect getPadding(DeviceProfile profile, Context context) {
+ WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
+ Rect padding = new Rect(profile.workspacePadding);
+
+ float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
+ float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
+
+ float overviewHeight, overviewWidth;
+ if (profile.isVerticalBarLayout()) {
+ float scrimLength = context.getResources()
+ .getDimension(R.dimen.recents_page_fade_length);
+ float maxPadding = Math.max(padding.left, padding.right);
+
+ // Use the same padding on both sides for symmetry.
+ float availableWidth = taskWidth - 2 * Math.max(maxPadding, scrimLength);
+ float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
+ - sTempStableInsets.top;
+ float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+ overviewHeight = taskHeight * scaledRatio;
+ overviewWidth = taskWidth * scaledRatio;
+
+ } else {
+ overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+ - sTempStableInsets.top;
+ overviewWidth = taskWidth * overviewHeight / taskHeight;
+ }
+
+ padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
+ - Math.round(overviewHeight);
+ padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ return padding;
+ }
+
+ public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+ Rect targetPadding = getPadding(grid, context);
+ Rect insets = grid.getInsets();
+ outRect.set(
+ targetPadding.left + insets.left,
+ targetPadding.top + insets.top,
+ grid.widthPx - targetPadding.right - insets.right,
+ grid.heightPx - targetPadding.bottom - insets.bottom);
+ outRect.top += context.getResources()
+ .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ }
+
+ @Override
+ protected boolean computeScrollHelper() {
+ boolean scrolling = super.computeScrollHelper();
+ boolean isFlingingFast = false;
+ updateCurveProperties();
+ if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
+ if (scrolling) {
+ // Check if we are flinging quickly to disable high res thumbnail loading
+ isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
+ }
+
+ // After scrolling, update the visible task's data
+ loadVisibleTaskData();
+ }
+
+ // Update the high res thumbnail loader
+ RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+ loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
+ return scrolling;
+ }
+
+ /**
+ * Scales and adjusts translation of adjacent pages as if on a curved carousel.
+ */
+ public void updateCurveProperties() {
+ if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
+ return;
+ }
+ final int halfPageWidth = getNormalChildWidth() / 2;
+ final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
+ final int halfScreenWidth = getMeasuredWidth() / 2;
+ final int pageSpacing = mPageSpacing;
+
+ final int pageCount = getPageCount();
+ for (int i = 0; i < pageCount; i++) {
+ View page = getPageAt(i);
+ float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
+ float distanceFromScreenCenter = screenCenter - pageCenter;
+ float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+ mScrollState.linearInterpolation = Math.min(1,
+ Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
+ ((PageCallbacks) page).onPageScroll(mScrollState);
+ }
+ }
+
+ /**
+ * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
+ * and unloads the associated task data for tasks that are no longer visible.
+ */
+ public void loadVisibleTaskData() {
+ RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+ int centerPageIndex = getPageNearestToCenterOfScreen();
+ int lower = Math.max(0, centerPageIndex - 2);
+ int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
+ int numChildren = getChildCount();
+
+ // Update the task data for the in/visible children
+ for (int i = 0; i < numChildren; i++) {
+ TaskView taskView = (TaskView) getChildAt(i);
+ Task task = taskView.getTask();
+ boolean visible = lower <= i && i <= upper;
+ if (visible) {
+ if (!mHasVisibleTaskData.get(task.key.id)) {
+ loader.loadTaskData(task);
+ loader.getHighResThumbnailLoader().onTaskVisible(task);
+ }
+ mHasVisibleTaskData.put(task.key.id, visible);
+ } else {
+ if (mHasVisibleTaskData.get(task.key.id)) {
+ loader.unloadTaskData(task);
+ loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ }
+ mHasVisibleTaskData.delete(task.key.id);
+ }
+ }
+ }
+
+ /**
+ * Unloads any associated data from the currently visible tasks
+ */
+ private void unloadVisibleTaskData() {
+ RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+ for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
+ if (mHasVisibleTaskData.valueAt(i)) {
+ TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
+ Task task = taskView.getTask();
+ loader.unloadTaskData(task);
+ loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ }
+ }
+ mHasVisibleTaskData.clear();
+ }
+
+
+ protected abstract void onAllTasksRemoved();
+
+ public void reset() {
+ unloadVisibleTaskData();
+ mRunningTaskId = -1;
+ setCurrentPage(0);
+ }
+
+ /**
+ * Reloads the view if anything in recents changed.
+ */
+ public void reloadIfNeeded() {
+ if (!mModel.isLoadPlanValid(mLoadPlanId)) {
+ mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
+ }
+ }
+
+ /**
+ * Ensures that the first task in the view represents {@param task} and reloads the view
+ * if needed. This allows the swipe-up gesture to assume that the first tile always
+ * corresponds to the correct task.
+ * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+ * is called.
+ * Also scrolls the view to this task
+ */
+ public void showTask(int runningTaskId) {
+ boolean needsReload = false;
+ if (getChildCount() == 0) {
+ needsReload = true;
+ // Add an empty view for now
+ final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
+ .inflate(R.layout.task, this, false);
+ addView(taskView, 0);
+ }
+ mRunningTaskId = runningTaskId;
+ setCurrentPage(0);
+ if (!needsReload) {
+ needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+ }
+ if (needsReload) {
+ mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+ } else {
+ loadVisibleTaskData();
+ }
+ getPageAt(mCurrentPage).setAlpha(0);
+ }
+
+ public QuickScrubController getQuickScrubController() {
+ return mQuickScrubController;
+ }
+
+ public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
+ if (mFirstTaskIconScaledDown == isScaledDown) {
+ return;
+ }
+ mFirstTaskIconScaledDown = isScaledDown;
+ applyIconScale(animate);
+ }
+
+ private void applyIconScale(boolean animate) {
+ float scale = mFirstTaskIconScaledDown ? 0 : 1;
+ TaskView firstTask = (TaskView) getChildAt(0);
+ if (firstTask != null) {
+ if (animate) {
+ firstTask.animateIconToScale(scale);
+ } else {
+ firstTask.setIconScale(scale);
+ }
+ }
+ }
+
+ public interface PageCallbacks {
+
+ /**
+ * Updates the page UI based on scroll params.
+ */
+ default void onPageScroll(ScrollState scrollState) {};
+ }
+
+ public static class ScrollState {
+
+ /**
+ * The progress from 0 to 1, where 0 is the center
+ * of the screen and 1 is the edge of the screen.
+ */
+ public float linearInterpolation;
+ }
+
+ public PendingAnimation createTaskDismissAnimation(TaskView taskView, long duration) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+ throw new IllegalStateException("Another pending animation is still running");
+ }
+ AnimatorSet anim = new AnimatorSet();
+ PendingAnimation pendingAnimation = new PendingAnimation(anim);
+
+ int count = getChildCount();
+ if (count == 0) {
+ return pendingAnimation;
+ }
+
+ int[] oldScroll = new int[count];
+ getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+
+ int[] newScroll = new int[count];
+ getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
+
+ int maxScrollDiff = 0;
+ int lastPage = mIsRtl ? 0 : count - 1;
+ if (getChildAt(lastPage) == taskView) {
+ if (count > 1) {
+ int secondLastPage = mIsRtl ? 1 : count - 2;
+ maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
+ }
+ }
+
+ boolean needsCurveUpdates = false;
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child == taskView) {
+ addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+ addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+ duration, LINEAR, anim);
+ } else {
+ int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
+ if (scrollDiff != 0) {
+ addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
+ duration, ACCEL, anim);
+ needsCurveUpdates = true;
+ }
+ }
+ }
+
+ if (needsCurveUpdates) {
+ ValueAnimator va = ValueAnimator.ofFloat(0, 1);
+ va.addUpdateListener((a) -> updateCurveProperties());
+ anim.play(va);
+ }
+
+ // Add a tiny bit of translation Z, so that it draws on top of other views
+ taskView.setTranslationZ(0.1f);
+
+ mPendingAnimation = pendingAnimation;
+ mPendingAnimation.addEndListener((isSuccess) -> {
+ if (isSuccess) {
+ ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+ removeView(taskView);
+ if (getChildCount() == 0) {
+ onAllTasksRemoved();
+ }
+ }
+ resetTaskVisuals();
+ mPendingAnimation = null;
+ });
+ return pendingAnimation;
+ }
+
+ private static void addAnim(ObjectAnimator anim, long duration,
+ TimeInterpolator interpolator, AnimatorSet set) {
+ anim.setDuration(duration).setInterpolator(interpolator);
+ set.play(anim);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
similarity index 98%
rename from quickstep/src/com/android/quickstep/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 6bbcb37..94f440d 100644
--- a/quickstep/src/com/android/quickstep/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.views;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -40,6 +40,8 @@
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.quickstep.TaskSystemShortcut;
+import com.android.quickstep.TaskUtils;
/**
* Contains options for a recent task when long-pressing its icon.
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
similarity index 96%
rename from quickstep/src/com/android/quickstep/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 9b9c6f2..87bb53b 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.views;
import android.content.Context;
import android.content.res.Configuration;
@@ -33,9 +33,10 @@
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -74,6 +75,7 @@
mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+ mPaint.setFilterBitmap(true);
}
public void bind() {
@@ -151,7 +153,8 @@
} else {
final Configuration configuration =
getContext().getApplicationContext().getResources().getConfiguration();
- final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
+ final DeviceProfile profile = BaseActivity.fromContext(getContext())
+ .getDeviceProfile();
if (configuration.orientation == mThumbnailData.orientation) {
// If we are in the same orientation as the screenshot, just scale it to the
// width of the task view
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
similarity index 76%
rename from quickstep/src/com/android/quickstep/TaskView.java
rename to quickstep/src/com/android/quickstep/views/TaskView.java
index b407f75..7a575ad 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -14,10 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep;
-
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+package com.android.quickstep.views;
import android.animation.TimeInterpolator;
import android.app.ActivityOptions;
@@ -33,8 +30,8 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
+import com.android.quickstep.views.RecentsView.PageCallbacks;
+import com.android.quickstep.views.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -47,17 +44,15 @@
*/
public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
- /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
- public static final float CURVE_FACTOR = 0.25f;
- /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
- public static final TimeInterpolator CURVE_INTERPOLATOR
- = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
+ /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
+ private static final TimeInterpolator CURVE_INTERPOLATOR
+ = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
/**
* The alpha of a black scrim on a page in the carousel as it leaves the screen.
* In the resting position of the carousel, the adjacent pages have about half this scrim.
*/
- private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
+ private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
private static final long SCALE_ICON_DURATION = 120;
@@ -159,38 +154,16 @@
setScaleY(1f);
setTranslationX(0f);
setTranslationY(0f);
+ setTranslationZ(0);
setAlpha(1f);
}
@Override
- public int onPageScroll(ScrollState scrollState) {
+ public void onPageScroll(ScrollState scrollState) {
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
- float scale = 1 - curveInterpolation * CURVE_FACTOR;
- setScaleX(scale);
- setScaleY(scale);
-
- // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
- setTranslationZ(scale);
mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
-
- float translation =
- scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
-
- if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
- // Make sure that the task cards do not overlap with the workspace card
- float min = scrollState.halfPageWidth * (1 - scale);
- if (scrollState.isRtl) {
- setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
- } else {
- setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
- }
- } else {
- setTranslationX(translation);
- }
- scrollState.prevPageExtraWidth = 0;
- return SCROLL_TYPE_TASK;
}
private static final class TaskOutlineProvider extends ViewOutlineProvider {