From 470da4e3e497caa8bc0f41e01e3e5da07c870ba0 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Mon, 29 Oct 2018 17:31:29 -0400 Subject: [PATCH 1/6] Finish LauncherLayoutListener when launcher transition animation ends. In QuickScrub mode, the right task gets clipped out at the end of the animation. This is because LauncherLayoutListener continues to clear out where CurrentRect is, while the app surface is not displaying. We want to finish the listener after the transition animation. Bug: 111697218 Test: Manual test Change-Id: I4dd5f5cbcec0574082fd1463e07c116a9e9305d3 --- .../src/com/android/quickstep/WindowTransformSwipeHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 9ea88842d7..7d1e8ddd6a 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -993,6 +993,7 @@ public class WindowTransformSwipeHandler { mLauncherTransitionController.getAnimationPlayer().end(); mLauncherTransitionController = null; } + mLayoutListener.finish(); mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false, mTouchInteractionLog); From b3fe661860c6ec909d4726103e9c7d10434a0455 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Mon, 29 Oct 2018 10:45:25 -0700 Subject: [PATCH 2/6] Add kill-process call to TestCommandReceiver Bug: 117888000 Test: On Nexus side Change-Id: I4803a884ffe8ae6147fdbaf98e2f813934418b76 --- tests/AndroidManifest-common.xml | 2 ++ .../testcomponent/TestCommandReceiver.java | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml index 439058cbda..46b463b21d 100644 --- a/tests/AndroidManifest-common.xml +++ b/tests/AndroidManifest-common.xml @@ -18,6 +18,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher3.tests"> + + diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java index 04c04f5b11..0edb3d61bc 100644 --- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java +++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java @@ -19,6 +19,8 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.PackageManager.DONT_KILL_APP; +import android.app.Activity; +import android.app.ActivityManager; import android.app.Instrumentation; import android.content.ComponentName; import android.content.ContentProvider; @@ -36,6 +38,7 @@ public class TestCommandReceiver extends ContentProvider { public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher"; public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher"; + public static final String KILL_PROCESS = "kill-process"; @Override public boolean onCreate() { @@ -83,14 +86,22 @@ public class TestCommandReceiver extends ContentProvider { COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP); return null; } - + case KILL_PROCESS: { + ((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE)). + killBackgroundProcesses(arg); + return null; + } } return super.call(method, arg, extras); } public static Bundle callCommand(String command) { + return callCommand(command, null); + } + + public static Bundle callCommand(String command, String arg) { Instrumentation inst = InstrumentationRegistry.getInstrumentation(); Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands"); - return inst.getTargetContext().getContentResolver().call(uri, command, null, null); + return inst.getTargetContext().getContentResolver().call(uri, command, arg, null); } } From c00a25970173681efe12377d67d858f37f30a1b4 Mon Sep 17 00:00:00 2001 From: Rajeev Kumar Date: Tue, 30 Oct 2018 17:16:25 -0700 Subject: [PATCH 3/6] Rename go/src_flags directory as go/src This CL is preparation work to implement Modular shortcuts component in launcher using java/code swap. Bug: 112904271 Test: Manual test Change-Id: I35cf81e95fe487a384d4e9f17ea303093197318f --- Android.mk | 4 ++-- build.gradle | 2 +- .../com/android/launcher3/config/FeatureFlags.java | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename go/{src_flags => src}/com/android/launcher3/config/FeatureFlags.java (100%) diff --git a/Android.mk b/Android.mk index fbe19b0b63..ba820fb7d5 100644 --- a/Android.mk +++ b/Android.mk @@ -131,7 +131,7 @@ LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ $(call all-java-files-under, src_ui_overrides) \ - $(call all-java-files-under, go/src_flags) + $(call all-java-files-under, go/src) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res @@ -235,7 +235,7 @@ LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ $(call all-java-files-under, quickstep/src) \ - $(call all-java-files-under, go/src_flags) + $(call all-java-files-under, go/src) LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/quickstep/res \ diff --git a/build.gradle b/build.gradle index 1b9df53d6a..affd85379d 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ android { l3go { res.srcDirs = ['go/res'] - java.srcDirs = ['go/src_flags', "src_ui_overrides"] + java.srcDirs = ['go/src', "src_ui_overrides"] manifest.srcFile "go/AndroidManifest.xml" } diff --git a/go/src_flags/com/android/launcher3/config/FeatureFlags.java b/go/src/com/android/launcher3/config/FeatureFlags.java similarity index 100% rename from go/src_flags/com/android/launcher3/config/FeatureFlags.java rename to go/src/com/android/launcher3/config/FeatureFlags.java From 51ca603ac4a4b170e41ea3afc6e3cdd29290b396 Mon Sep 17 00:00:00 2001 From: Rajeev Kumar Date: Wed, 31 Oct 2018 12:54:15 -0700 Subject: [PATCH 4/6] Create a deep shortcuts override. This change disables deep shortcuts on Android Go devices. Bug: 112904271 Test: Manual test Change-Id: I6150a3500f67d8a8412b51796cc442e3384c2cbb --- Android.mk | 4 +- build.gradle | 2 +- .../shortcuts/DeepShortcutManager.java | 124 ++++++++++++++++++ .../shortcuts/DeepShortcutManager.java | 0 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java rename {src => src_shortcuts_overrides}/com/android/launcher3/shortcuts/DeepShortcutManager.java (100%) diff --git a/Android.mk b/Android.mk index ba820fb7d5..5614e25cac 100644 --- a/Android.mk +++ b/Android.mk @@ -101,6 +101,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ + $(call all-java-files-under, src_shortcuts_overrides) \ $(call all-java-files-under, src_ui_overrides) \ $(call all-java-files-under, src_flags) @@ -174,7 +175,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ $(call all-java-files-under, quickstep/src) \ - $(call all-java-files-under, src_flags) + $(call all-java-files-under, src_flags) \ + $(call all-java-files-under, src_shortcuts_overrides) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res LOCAL_PROGUARD_ENABLED := disabled diff --git a/build.gradle b/build.gradle index affd85379d..476e92bf67 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ android { sourceSets { main { res.srcDirs = ['res'] - java.srcDirs = ['src'] + java.srcDirs = ['src', 'src_shortcuts_overrides'] manifest.srcFile 'AndroidManifest-common.xml' proto { srcDir 'protos/' diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java new file mode 100644 index 0000000000..ff0c907f55 --- /dev/null +++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -0,0 +1,124 @@ +/* + * 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.shortcuts; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.launcher3.ItemInfo; + +import java.util.Collections; +import java.util.List; + +/** + * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc. + */ +public class DeepShortcutManager { + private static DeepShortcutManager sInstance; + private static final Object sInstanceLock = new Object(); + + public static DeepShortcutManager getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + sInstance = new DeepShortcutManager(context.getApplicationContext()); + } + return sInstance; + } + } + + private DeepShortcutManager(Context context) { + } + + public static boolean supportsShortcuts(ItemInfo info) { + return false; + } + + public boolean wasLastCallSuccess() { + return false; + } + + public void onShortcutsChanged(List shortcuts) { + } + + /** + * Queries for the shortcuts with the package name and provided ids. + * + * This method is intended to get the full details for shortcuts when they are added or updated, + * because we only get "key" fields in onShortcutsChanged(). + */ + public List queryForFullDetails(String packageName, + List shortcutIds, UserHandle user) { + return Collections.emptyList(); + } + + /** + * Gets all the manifest and dynamic shortcuts associated with the given package and user, + * to be displayed in the shortcuts container on long press. + */ + public List queryForShortcutsContainer(ComponentName activity, + UserHandle user) { + return Collections.emptyList(); + } + + /** + * Removes the given shortcut from the current list of pinned shortcuts. + * (Runs on background thread) + */ + public void unpinShortcut(final ShortcutKey key) { + } + + /** + * Adds the given shortcut to the current list of pinned shortcuts. + * (Runs on background thread) + */ + public void pinShortcut(final ShortcutKey key) { + } + + public void startShortcut(String packageName, String id, Rect sourceBounds, + Bundle startActivityOptions, UserHandle user) { + } + + public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) { + return null; + } + + /** + * Returns the id's of pinned shortcuts associated with the given package and user. + * + * If packageName is null, returns all pinned shortcuts regardless of package. + */ + public List queryForPinnedShortcuts(String packageName, UserHandle user) { + return Collections.emptyList(); + } + + public List queryForPinnedShortcuts(String packageName, + List shortcutIds, UserHandle user) { + return Collections.emptyList(); + } + + public List queryForAllShortcuts(UserHandle user) { + return Collections.emptyList(); + } + + public boolean hasHostPermission() { + return false; + } +} diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java similarity index 100% rename from src/com/android/launcher3/shortcuts/DeepShortcutManager.java rename to src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java From 6699eaf74d26b085deeaf300fc5bdb19272f3a14 Mon Sep 17 00:00:00 2001 From: Ryan Lothian Date: Tue, 30 Oct 2018 17:36:59 -0400 Subject: [PATCH 5/6] Add robolectric tests for launcher Bug: 117235618 Change-Id: Ia0441133a77069493b0a29a7a2d513a818dc7fe2 --- robolectric_tests/Android.mk | 53 ++++++++++++ .../android/launcher3/util/IntSetTest.java | 81 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 robolectric_tests/Android.mk create mode 100644 robolectric_tests/src/com/android/launcher3/util/IntSetTest.java diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk new file mode 100644 index 0000000000..501176475a --- /dev/null +++ b/robolectric_tests/Android.mk @@ -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. + +############################################# +# Launcher Robolectric test target. # +############################################# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := LauncherRoboTests +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := \ + androidx.test.runner \ + androidx.test.rules \ + mockito-robolectric-prebuilt \ + truth-prebuilt +LOCAL_JAVA_LIBRARIES := \ + platform-robolectric-3.6.1-prebuilt + +LOCAL_INSTRUMENTATION_FOR := Launcher3 +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_JAVA_LIBRARY) + +############################################ +# Target to run the previous target. # +############################################ +include $(CLEAR_VARS) + +LOCAL_MODULE := RunLauncherRoboTests +LOCAL_SDK_VERSION := current +LOCAL_JAVA_LIBRARIES := \ + LauncherRoboTests + +LOCAL_TEST_PACKAGE := Launcher3 + +LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \ + +LOCAL_ROBOTEST_TIMEOUT := 36000 + +include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java new file mode 100644 index 0000000000..faec380afd --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java @@ -0,0 +1,81 @@ +/* + * 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.util; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Robolectric unit tests for {@link IntSet} + */ +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 26) +public class IntSetTest { + + @Test + public void shouldBeEmptyInitially() { + IntSet set = new IntSet(); + assertThat(set.size()).isEqualTo(0); + } + + @Test + public void oneElementSet() { + IntSet set = new IntSet(); + set.add(2); + assertThat(set.size()).isEqualTo(1); + assertTrue(set.contains(2)); + assertFalse(set.contains(1)); + } + + + @Test + public void twoElementSet() { + IntSet set = new IntSet(); + set.add(2); + set.add(1); + assertThat(set.size()).isEqualTo(2); + assertTrue(set.contains(2)); + assertTrue(set.contains(1)); + } + + @Test + public void threeElementSet() { + IntSet set = new IntSet(); + set.add(2); + set.add(1); + set.add(10); + assertThat(set.size()).isEqualTo(3); + assertEquals("1, 2, 10", set.mArray.toConcatString()); + } + + + @Test + public void duplicateEntries() { + IntSet set = new IntSet(); + set.add(2); + set.add(2); + assertEquals(1, set.size()); + } +} From 50876bfac7eba0c78d5bcc1d279755a314967fff Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 19 Sep 2018 23:09:14 -0400 Subject: [PATCH 6/6] FeatureFlag: quick scrub is now quick switch - Scale down current task and translate it to the left - Translate previous task in from the left - This is a toggle; quick switch again returns to the first task Currently this is implemented by repurposing onQuickScrub(progress) to update the positions of the first two pages. This makes tracking velocity a bit difficult; if we want to go down this path in the long run we should probably track velocity properly on MotionEvents. Change-Id: I4445b5f08b6e88e71cbb5e30b1f1d45c5f1edc68 --- .gitignore | 3 +- .../LauncherAppTransitionManagerImpl.java | 10 +- .../uioverrides/FastOverviewState.java | 18 ++- .../quickstep/ActivityControlHelper.java | 5 +- .../quickstep/QuickScrubController.java | 142 +++++++++++++++++- .../WindowTransformSwipeHandler.java | 17 ++- .../quickstep/views/TaskThumbnailView.java | 19 ++- .../com/android/quickstep/views/TaskView.java | 17 ++- src/com/android/launcher3/PagedView.java | 8 + .../android/launcher3/config/BaseFlags.java | 9 +- 10 files changed, 221 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 7240e4877b..694b40c7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ bin/ local.properties gradle/ build/ -gradlew* \ No newline at end of file +gradlew* +.DS_Store diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 5680a67090..d7bbfe06e1 100644 --- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -178,6 +178,14 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag @Override public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) { if (hasControlRemoteAppTransitionPermission()) { + boolean fromRecents = mLauncher.getStateManager().getState().overviewUi + && findTaskViewToLaunch(launcher, v, null) != null; + RecentsView recentsView = mLauncher.getOverviewPanel(); + if (fromRecents && recentsView.getQuickScrubController().isQuickSwitch()) { + return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim, + R.anim.no_anim); + } + RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler, true /* startAtFrontOfQueue */) { @@ -218,8 +226,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag } }; - boolean fromRecents = mLauncher.getStateManager().getState().overviewUi - && findTaskViewToLaunch(launcher, v, null) != null; int duration = fromRecents ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION; diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java index 26453022f0..1d65a54e7a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java @@ -35,6 +35,7 @@ public class FastOverviewState extends OverviewState { * Vertical transition of the task previews relative to the full container. */ public static final float OVERVIEW_TRANSLATION_FACTOR = 0.4f; + public static final float OVERVIEW_CENTERED_TRANSLATION_FACTOR = 0.5f; private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON | FLAG_DISABLE_ACCESSIBILITY; @@ -60,12 +61,17 @@ public class FastOverviewState extends OverviewState { RecentsView recentsView = launcher.getOverviewPanel(); recentsView.getTaskSize(sTempRect); - return new float[] {getOverviewScale(launcher.getDeviceProfile(), sTempRect, launcher), - OVERVIEW_TRANSLATION_FACTOR}; + boolean isQuickSwitch = recentsView.getQuickScrubController().isQuickSwitch(); + float translationYFactor = isQuickSwitch + ? OVERVIEW_CENTERED_TRANSLATION_FACTOR + : OVERVIEW_TRANSLATION_FACTOR; + return new float[] {getOverviewScale(launcher.getDeviceProfile(), sTempRect, launcher, + isQuickSwitch), translationYFactor}; } - public static float getOverviewScale(DeviceProfile dp, Rect taskRect, Context context) { - if (dp.isVerticalBarLayout()) { + public static float getOverviewScale(DeviceProfile dp, Rect taskRect, Context context, + boolean isQuickSwitch) { + if (dp.isVerticalBarLayout() && !isQuickSwitch) { return 1f; } @@ -73,6 +79,10 @@ public class FastOverviewState extends OverviewState { float usedHeight = taskRect.height() + res.getDimension(R.dimen.task_thumbnail_top_margin); float usedWidth = taskRect.width() + 2 * (res.getDimension(R.dimen.recents_page_spacing) + res.getDimension(R.dimen.quickscrub_adjacent_visible_width)); + if (isQuickSwitch) { + usedWidth = taskRect.width(); + return Math.max(dp.availableHeightPx / usedHeight, dp.availableWidthPx / usedWidth); + } return Math.min(Math.min(dp.availableHeightPx / usedHeight, dp.availableWidthPx / usedWidth), MAX_PREVIEW_SCALE_UP); } diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index c809e28320..85eed1fbfe 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -16,7 +16,6 @@ package com.android.quickstep; import static android.view.View.TRANSLATION_Y; - import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.BACKGROUND_APP; @@ -58,6 +57,7 @@ import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.compat.AccessibilityManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.uioverrides.FastOverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -192,7 +192,8 @@ public interface ActivityControlHelper { @InteractionType int interactionType, TransformedRect outRect) { LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect); if (interactionType == INTERACTION_QUICK_SCRUB) { - outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context); + outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context, + FeatureFlags.QUICK_SWITCH.get()); } if (dp.isVerticalBarLayout()) { Rect targetInsets = dp.getInsets(); diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java index 34207670e8..c44ccd3ded 100644 --- a/quickstep/src/com/android/quickstep/QuickScrubController.java +++ b/quickstep/src/com/android/quickstep/QuickScrubController.java @@ -16,8 +16,18 @@ package com.android.quickstep; +import static com.android.launcher3.Utilities.SINGLE_FRAME_MS; +import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.TargetApi; +import android.os.Build; +import android.util.FloatProperty; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.animation.Interpolator; @@ -37,8 +47,10 @@ import com.android.quickstep.views.TaskView; * The behavior is to evenly divide the progress into sections, each of which scrolls one page. * The first and last section set an alarm to auto-advance backwards or forwards, respectively. */ +@TargetApi(Build.VERSION_CODES.P) public class QuickScrubController implements OnAlarmListener { + public static final int QUICK_SWITCH_FROM_APP_START_DURATION = 0; public static final int QUICK_SCRUB_FROM_APP_START_DURATION = 240; public static final int QUICK_SCRUB_FROM_HOME_START_DURATION = 200; // We want the translation y to finish faster than the rest of the animation. @@ -52,6 +64,19 @@ public class QuickScrubController implements OnAlarmListener { 0.05f, 0.20f, 0.35f, 0.50f, 0.65f, 0.80f, 0.95f }; + private static final FloatProperty PROGRESS + = new FloatProperty("progress") { + @Override + public void setValue(QuickScrubController quickScrubController, float progress) { + quickScrubController.onQuickScrubProgress(progress); + } + + @Override + public Float get(QuickScrubController quickScrubController) { + return quickScrubController.mEndProgress; + } + }; + private static final String TAG = "QuickScrubController"; private static final boolean ENABLE_AUTO_ADVANCE = true; private static final long AUTO_ADVANCE_DELAY = 500; @@ -72,6 +97,13 @@ public class QuickScrubController implements OnAlarmListener { private ActivityControlHelper mActivityControlHelper; private TouchInteractionLog mTouchInteractionLog; + private boolean mIsQuickSwitch; + private float mStartProgress; + private float mEndProgress; + private float mPrevProgressDelta; + private float mPrevPrevProgressDelta; + private boolean mShouldSwitchToNext; + public QuickScrubController(BaseActivity activity, RecentsView recentsView) { mActivity = activity; mRecentsView = recentsView; @@ -91,17 +123,26 @@ public class QuickScrubController implements OnAlarmListener { mActivityControlHelper = controlHelper; mTouchInteractionLog = touchInteractionLog; + if (mIsQuickSwitch) { + mShouldSwitchToNext = true; + mPrevProgressDelta = 0; + if (mRecentsView.getTaskViewCount() > 0) { + mRecentsView.getTaskViewAt(0).setFullscreen(true); + } + if (mRecentsView.getTaskViewCount() > 1) { + mRecentsView.getTaskViewAt(1).setFullscreen(true); + } + } + snapToNextTaskIfAvailable(); mActivity.getUserEventDispatcher().resetActionDurationMillis(); } public void onQuickScrubEnd() { mInQuickScrub = false; - if (ENABLE_AUTO_ADVANCE) { - mAutoAdvanceAlarm.cancelAlarm(); - } - int page = mRecentsView.getNextPage(); + Runnable launchTaskRunnable = () -> { + int page = mRecentsView.getPageNearestToCenterOfScreen(); TaskView taskView = mRecentsView.getTaskViewAt(page); if (taskView != null) { mWaitingForTaskLaunch = true; @@ -118,12 +159,49 @@ public class QuickScrubController implements OnAlarmListener { TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key)); } mWaitingForTaskLaunch = false; + if (mIsQuickSwitch) { + mIsQuickSwitch = false; + if (mRecentsView.getTaskViewCount() > 0) { + mRecentsView.getTaskViewAt(0).setFullscreen(false); + } + if (mRecentsView.getTaskViewCount() > 1) { + mRecentsView.getTaskViewAt(1).setFullscreen(false); + } + } + }, taskView.getHandler()); } else { breakOutOfQuickScrub(); } mActivityControlHelper = null; }; + + if (mIsQuickSwitch) { + float progressVelocity = mPrevPrevProgressDelta / SINGLE_FRAME_MS; + // Move to the next frame immediately, then start the animation from the + // following frame since it starts a frame later. + float singleFrameProgress = progressVelocity * SINGLE_FRAME_MS; + float fromProgress = mEndProgress + singleFrameProgress; + onQuickScrubProgress(fromProgress); + fromProgress += singleFrameProgress; + float toProgress = mShouldSwitchToNext ? 1 : 0; + int duration = (int) Math.abs((toProgress - fromProgress) / progressVelocity); + duration = Utilities.boundToRange(duration, 80, 300); + Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, fromProgress, toProgress); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + launchTaskRunnable.run(); + } + }); + anim.setDuration(duration).start(); + return; + } + + if (ENABLE_AUTO_ADVANCE) { + mAutoAdvanceAlarm.cancelAlarm(); + } + int page = mRecentsView.getNextPage(); int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen()) * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE; if (mRecentsView.getChildCount() > 0 && mRecentsView.snapToPage(page, snapDuration)) { @@ -151,19 +229,28 @@ public class QuickScrubController implements OnAlarmListener { mLaunchingTaskId = 0; } + public boolean prepareQuickScrub(String tag) { + return prepareQuickScrub(tag, mIsQuickSwitch); + } + /** * Initializes the UI for quick scrub, returns true if success. */ - public boolean prepareQuickScrub(String tag) { + public boolean prepareQuickScrub(String tag, boolean isQuickSwitch) { if (mWaitingForTaskLaunch || mInQuickScrub) { Log.d(tag, "Waiting for last scrub to finish, will skip this interaction"); return false; } mOnFinishedTransitionToQuickScrubRunnable = null; mRecentsView.setNextPageSwitchRunnable(null); + mIsQuickSwitch = isQuickSwitch; return true; } + public boolean isQuickSwitch() { + return mIsQuickSwitch; + } + public boolean isWaitingForTaskLaunch() { return mWaitingForTaskLaunch; } @@ -179,6 +266,40 @@ public class QuickScrubController implements OnAlarmListener { } public void onQuickScrubProgress(float progress) { + if (mIsQuickSwitch) { + TaskView currentPage = mRecentsView.getTaskViewAt(0); + TaskView nextPage = mRecentsView.getTaskViewAt(1); + if (currentPage == null || nextPage == null) { + return; + } + if (!mFinishedTransitionToQuickScrub) { + mStartProgress = mEndProgress = progress; + } else { + float progressDelta = progress - mEndProgress; + mEndProgress = progress; + progress = Utilities.boundToRange(progress, mStartProgress, 1); + progress = Utilities.mapToRange(progress, mStartProgress, 1, 0, 1, LINEAR); + if (mInQuickScrub) { + mShouldSwitchToNext = mPrevProgressDelta > 0.007f || progressDelta > 0.007f + || progress >= 0.5f; + } + mPrevPrevProgressDelta = mPrevProgressDelta; + mPrevProgressDelta = progressDelta; + float scrollDiff = nextPage.getWidth() + mRecentsView.getPageSpacing(); + int scrollDir = mRecentsView.isRtl() ? -1 : 1; + int linearScrollDiff = (int) (progress * scrollDiff * scrollDir); + float accelScrollDiff = ACCEL.getInterpolation(progress) * scrollDiff * scrollDir; + currentPage.setZoomScale(1 - DEACCEL_3.getInterpolation(progress) + * TaskView.EDGE_SCALE_DOWN_FACTOR); + currentPage.setTranslationX(linearScrollDiff + accelScrollDiff); + nextPage.setTranslationZ(1); + nextPage.setTranslationY(currentPage.getTranslationY()); + int startScroll = mRecentsView.isRtl() ? mRecentsView.getMaxScrollX() : 0; + mRecentsView.setScrollX(startScroll + linearScrollDiff); + } + return; + } + int quickScrubSection = 0; for (float threshold : QUICK_SCRUB_THRESHOLDS) { if (progress < threshold) { @@ -228,9 +349,14 @@ public class QuickScrubController implements OnAlarmListener { public void snapToNextTaskIfAvailable() { if (mInQuickScrub && mRecentsView.getChildCount() > 0) { - int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION - : QUICK_SCRUB_FROM_APP_START_DURATION; - int pageToGoTo = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1; + int duration = mIsQuickSwitch + ? QUICK_SWITCH_FROM_APP_START_DURATION + : mStartedFromHome + ? QUICK_SCRUB_FROM_HOME_START_DURATION + : QUICK_SCRUB_FROM_APP_START_DURATION; + int pageToGoTo = mStartedFromHome || mIsQuickSwitch + ? 0 + : mRecentsView.getNextPage() + 1; goToPageWithHaptic(pageToGoTo, duration, true /* forceHaptic */, QUICK_SCRUB_START_INTERPOLATOR); } diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 9ea88842d7..cc544178ca 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -23,6 +23,7 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION; +import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION; import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL; import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; @@ -59,6 +60,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; @@ -979,12 +981,14 @@ public class WindowTransformSwipeHandler { setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED); // Start the window animation without waiting for launcher. - animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR, - true /* goingToHome */); + long duration = FeatureFlags.QUICK_SWITCH.get() + ? QUICK_SWITCH_FROM_APP_START_DURATION + : QUICK_SCRUB_FROM_APP_START_DURATION; + animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToHome */); } private void onQuickScrubStartUi() { - if (!mQuickScrubController.prepareQuickScrub(TAG)) { + if (!mQuickScrubController.prepareQuickScrub(TAG, FeatureFlags.QUICK_SWITCH.get())) { mQuickScrubBlocked = true; setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED); return; @@ -1008,6 +1012,13 @@ public class WindowTransformSwipeHandler { mQuickScrubController.onFinishedTransitionToQuickScrub(); mRecentsView.animateUpRunningTaskIconScale(); + if (mQuickScrubController.isQuickSwitch()) { + TaskView runningTask = mRecentsView.getRunningTaskView(); + if (runningTask != null) { + runningTask.setTranslationY(-mActivity.getResources().getDimension( + R.dimen.task_thumbnail_half_top_margin) * 1f / mRecentsView.getScaleX()); + } + } RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); } diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index ce65de1e09..c92c8d66b8 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -17,7 +17,6 @@ package com.android.quickstep.views; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; - import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -33,7 +32,7 @@ import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; import android.view.View; - +import android.view.ViewGroup; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; @@ -78,6 +77,7 @@ public class TaskThumbnailView extends View { private final Matrix mMatrix = new Matrix(); private float mClipBottom = -1; + private Rect mScaledInsets = new Rect(); private Task mTask; private ThumbnailData mThumbnailData; @@ -179,7 +179,17 @@ public class TaskThumbnailView extends View { @Override protected void onDraw(Canvas canvas) { - drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius); + if (((TaskView) getParent()).isFullscreen()) { + // Draw the insets if we're being drawn fullscreen (we do this for quick switch). + drawOnCanvas(canvas, + -mScaledInsets.left, + -mScaledInsets.top, + getMeasuredWidth() + mScaledInsets.right, + getMeasuredHeight() + mScaledInsets.bottom, + mCornerRadius); + } else { + drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius); + } } public float getCornerRadius() { @@ -253,6 +263,9 @@ public class TaskThumbnailView extends View { : getMeasuredWidth() / thumbnailWidth; } + mScaledInsets.set(thumbnailInsets); + Utilities.scaleRect(mScaledInsets, thumbnailScale); + if (rotate) { int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1; mMatrix.setRotate(90 * rotationDir); diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 2a4226faa2..07d8b891dd 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -87,7 +87,7 @@ public class TaskView extends FrameLayout implements PageCallbacks { /** * How much to scale down pages near the edge of the screen. */ - private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f; + public static final float EDGE_SCALE_DOWN_FACTOR = 0.03f; public static final long SCALE_ICON_DURATION = 120; private static final long DIM_ANIM_DURATION = 700; @@ -142,6 +142,7 @@ public class TaskView extends FrameLayout implements PageCallbacks { private IconView mIconView; private float mCurveScale; private float mZoomScale; + private boolean mIsFullscreen; private Animator mIconAndDimAnimator; private float mFocusTransitionProgress = 1; @@ -509,4 +510,18 @@ public class TaskView extends FrameLayout implements PageCallbacks { Log.w(tag, msg); Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show(); } + + /** + * Hides the icon and shows insets when this TaskView is about to be shown fullscreen. + */ + public void setFullscreen(boolean isFullscreen) { + mIsFullscreen = isFullscreen; + mIconView.setVisibility(mIsFullscreen ? INVISIBLE : VISIBLE); + setClipChildren(!mIsFullscreen); + setClipToPadding(!mIsFullscreen); + } + + public boolean isFullscreen() { + return mIsFullscreen; + } } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 36b9e97aa4..9470635cb1 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -625,6 +625,10 @@ public abstract class PagedView extends ViewGrou mMaxScrollX = computeMaxScrollX(); } + public int getMaxScrollX() { + return mMaxScrollX; + } + protected int computeMaxScrollX() { int childCount = getChildCount(); if (childCount > 0) { @@ -640,6 +644,10 @@ public abstract class PagedView extends ViewGrou requestLayout(); } + public int getPageSpacing() { + return mPageSpacing; + } + private void dispatchPageCountChanged() { if (mPageIndicator != null) { mPageIndicator.setMarkersCount(getChildCount()); diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 64b5652c5c..e5a8a01f5d 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -22,9 +22,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.provider.Settings; -import androidx.annotation.GuardedBy; -import androidx.annotation.Keep; - import com.android.launcher3.Utilities; import java.util.ArrayList; @@ -32,6 +29,9 @@ import java.util.List; import java.util.SortedMap; import java.util.TreeMap; +import androidx.annotation.GuardedBy; +import androidx.annotation.Keep; + /** * Defines a set of flags used to control various launcher behaviors. * @@ -87,6 +87,9 @@ abstract class BaseFlags { // trying to make them fit the orientation the device is in. public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true; + public static final TogglableFlag QUICK_SWITCH = new TogglableFlag("QUICK_SWITCH", false, + "Swiping right on the nav bar while in an app switches to the previous app"); + /** * Feature flag to handle define config changes dynamically instead of killing the process. */