From 08f9a5878d03deb4e4d9bbb294e53a38f390775c Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Tue, 14 Apr 2020 10:24:24 -0500 Subject: [PATCH 01/35] Don't scale workspace when going to all apps if ENABLE_OVERVIEW_ACTIONS Before the two-zone model introduced with ENABLE_OVERVIEW_ACTIONS, we scaled down the workspace when swiping up, since you could pause at any time to go to overview (which has workspace scaled down behind it). But in the two-zone model, that scaling is potentially confusing since it looks similar to the HintState (when swiping from the nav bar). Bug: 143361609 Change-Id: I3ee9f6eb45abb0123563d283a3145f27c3a63dd5 --- .../android/launcher3/uioverrides/states/AllAppsState.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index 93e02a111f..8e002d0ea3 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -16,6 +16,8 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; +import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import android.content.Context; @@ -76,7 +78,7 @@ public class AllAppsState extends LauncherState { public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) { ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW .getWorkspaceScaleAndTranslation(launcher); - if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) { + if (SysUINavigationMode.getMode(launcher) == NO_BUTTON && !ENABLE_OVERVIEW_ACTIONS.get()) { float normalScale = 1; // Scale down halfway to where we'd be in overview, to prepare for a potential pause. scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2; From 34ff8cb3c1d920434b47b43bbc2db75cd44d3d9e Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 20 Apr 2020 14:41:23 -0700 Subject: [PATCH 02/35] Adding common code for instrumentation and robolectric tests Change-Id: Ia338f0565868bb174ef5f6721924944792866be6 --- .../quickstep/ViewInflationDuringSwipeUp.java | 5 +- robolectric_tests/Android.mk | 5 +- tests/Android.mk | 6 +- .../launcher3/ui/AbstractLauncherUiTest.java | 25 +---- .../launcher3/ui/widget/BindWidgetTest.java | 57 +--------- tests/src_common/README.md | 1 + .../android/launcher3/common/WidgetUtils.java | 104 ++++++++++++++++++ 7 files changed, 125 insertions(+), 78 deletions(-) create mode 100644 tests/src_common/README.md create mode 100644 tests/src_common/com/android/launcher3/common/WidgetUtils.java diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java index 8ecd88a45e..115294addf 100644 --- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java +++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java @@ -17,10 +17,11 @@ package com.android.quickstep; import static androidx.test.InstrumentationRegistry.getContext; import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.InstrumentationRegistry.getTargetContext; +import static com.android.launcher3.common.WidgetUtils.createWidgetInfo; import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE; import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER; -import static com.android.launcher3.ui.widget.BindWidgetTest.createWidgetInfo; import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON; import static org.junit.Assert.assertEquals; @@ -187,7 +188,7 @@ public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest { LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); - LauncherAppWidgetInfo item = createWidgetInfo(info, true); + LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true); addItemToScreen(item); assertTrue("Widget is not present", diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk index bbc62e90a8..3fa9b0ab63 100644 --- a/robolectric_tests/Android.mk +++ b/robolectric_tests/Android.mk @@ -22,7 +22,10 @@ LOCAL_MODULE := LauncherRoboTests LOCAL_MODULE_CLASS := JAVA_LIBRARIES LOCAL_SDK_VERSION := system_current -LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-java-files-under, ../tests/src_common) + LOCAL_STATIC_JAVA_LIBRARIES := \ androidx.test.runner \ androidx.test.rules \ diff --git a/tests/Android.mk b/tests/Android.mk index a9fff8ef97..4d1bfa6067 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -62,7 +62,11 @@ else LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl endif -LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-java-files-under, src_common) + + LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_PACKAGE_NAME := Launcher3Tests diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 86faddb41d..0ba5a36112 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -17,7 +17,6 @@ package com.android.launcher3.ui; import static androidx.test.InstrumentationRegistry.getInstrumentation; -import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -26,7 +25,6 @@ import static org.junit.Assert.assertTrue; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -53,6 +51,7 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.Utilities; +import com.android.launcher3.common.WidgetUtils; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.tapl.LauncherInstrumentation; @@ -60,7 +59,6 @@ import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Wait; @@ -306,26 +304,7 @@ public abstract class AbstractLauncherUiTest { * Adds {@param item} on the homescreen on the 0th screen */ protected void addItemToScreen(ItemInfo item) { - ContentResolver resolver = mTargetContext.getContentResolver(); - int screenId = FIRST_SCREEN_ID; - // Update the screen id counter for the provider. - LauncherSettings.Settings.call(resolver, - LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); - - if (screenId > FIRST_SCREEN_ID) { - screenId = FIRST_SCREEN_ID; - } - - // Insert the item - ContentWriter writer = new ContentWriter(mTargetContext); - item.id = LauncherSettings.Settings.call( - resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) - .getInt(LauncherSettings.Settings.EXTRA_VALUE); - item.screenId = screenId; - item.onAddToDatabase(writer); - writer.put(LauncherSettings.Favorites._ID, item.id); - resolver.insert(LauncherSettings.Favorites.CONTENT_URI, - writer.getValues(mTargetContext)); + WidgetUtils.addItemToScreen(item, mTargetContext); resetLoaderState(); // Launch the home activity diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 62ce085e8d..df11557fe2 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -17,18 +17,16 @@ package com.android.launcher3.ui.widget; import static androidx.test.InstrumentationRegistry.getTargetContext; -import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget; +import static com.android.launcher3.common.WidgetUtils.createWidgetInfo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.ContentResolver; -import android.content.Context; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; @@ -39,7 +37,6 @@ import android.widget.RemoteViews; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; -import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -50,7 +47,6 @@ import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetManagerHelper; import org.junit.After; @@ -108,7 +104,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { @Test public void testBindNormalWidget_withConfig() { LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true); - LauncherAppWidgetInfo item = createWidgetInfo(info, true); + LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true); addItemToScreen(item); verifyWidgetPresent(info); @@ -117,7 +113,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { @Test public void testBindNormalWidget_withoutConfig() { LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); - LauncherAppWidgetInfo item = createWidgetInfo(info, true); + LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true); addItemToScreen(item); verifyWidgetPresent(info); @@ -126,7 +122,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { @Test public void testUnboundWidget_removed() { LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); - LauncherAppWidgetInfo item = createWidgetInfo(info, false); + LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false); item.appWidgetId = -33; addItemToScreen(item); @@ -147,7 +143,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); // Do not bind the widget - LauncherAppWidgetInfo item = createWidgetInfo(info, false); + LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false); item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; addItemToScreen(item); @@ -160,7 +156,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true); // Do not bind the widget - LauncherAppWidgetInfo item = createWidgetInfo(info, false); + LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false); item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; addItemToScreen(item); @@ -281,47 +277,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest { widget != null); } - /** - * Creates a LauncherAppWidgetInfo corresponding to {@param info} - * - * @param bindWidget if true the info is bound and a valid widgetId is assigned to - * the LauncherAppWidgetInfo - */ - public static LauncherAppWidgetInfo createWidgetInfo( - LauncherAppWidgetProviderInfo info, boolean bindWidget) { - Context targetContext = getTargetContext(); - - LauncherAppWidgetInfo item = new LauncherAppWidgetInfo( - LauncherAppWidgetInfo.NO_ID, info.provider); - item.spanX = info.minSpanX; - item.spanY = info.minSpanY; - item.minSpanX = info.minSpanX; - item.minSpanY = info.minSpanY; - item.user = info.getProfile(); - item.cellX = 0; - item.cellY = 1; - item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; - - if (bindWidget) { - PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info); - pendingInfo.spanX = item.spanX; - pendingInfo.spanY = item.spanY; - pendingInfo.minSpanX = item.minSpanX; - pendingInfo.minSpanY = item.minSpanY; - Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo); - - AppWidgetHost host = new LauncherAppWidgetHost(targetContext); - int widgetId = host.allocateAppWidgetId(); - if (!new WidgetManagerHelper(targetContext) - .bindAppWidgetIdIfAllowed(widgetId, info, options)) { - host.deleteAppWidgetId(widgetId); - throw new IllegalArgumentException("Unable to bind widget id"); - } - item.appWidgetId = widgetId; - } - return item; - } - /** * Returns a LauncherAppWidgetInfo with package name which is not present on the device */ diff --git a/tests/src_common/README.md b/tests/src_common/README.md new file mode 100644 index 0000000000..2bc9e73d86 --- /dev/null +++ b/tests/src_common/README.md @@ -0,0 +1 @@ +Common source code used by both android tests and robolectric tests \ No newline at end of file diff --git a/tests/src_common/com/android/launcher3/common/WidgetUtils.java b/tests/src_common/com/android/launcher3/common/WidgetUtils.java new file mode 100644 index 0000000000..c0913bf96f --- /dev/null +++ b/tests/src_common/com/android/launcher3/common/WidgetUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 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.common; + +import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; +import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget; + +import android.appwidget.AppWidgetHost; +import android.content.ContentResolver; +import android.content.Context; +import android.os.Bundle; + +import com.android.launcher3.LauncherAppWidgetHost; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.WidgetManagerHelper; + +/** + * Common method for widget binding + */ +public class WidgetUtils { + + /** + * Creates a LauncherAppWidgetInfo corresponding to {@param info} + * + * @param bindWidget if true the info is bound and a valid widgetId is assigned to + * the LauncherAppWidgetInfo + */ + public static LauncherAppWidgetInfo createWidgetInfo( + LauncherAppWidgetProviderInfo info, Context targetContext, boolean bindWidget) { + LauncherAppWidgetInfo item = new LauncherAppWidgetInfo( + LauncherAppWidgetInfo.NO_ID, info.provider); + item.spanX = info.minSpanX; + item.spanY = info.minSpanY; + item.minSpanX = info.minSpanX; + item.minSpanY = info.minSpanY; + item.user = info.getProfile(); + item.cellX = 0; + item.cellY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + + if (bindWidget) { + PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info); + pendingInfo.spanX = item.spanX; + pendingInfo.spanY = item.spanY; + pendingInfo.minSpanX = item.minSpanX; + pendingInfo.minSpanY = item.minSpanY; + Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo); + + AppWidgetHost host = new LauncherAppWidgetHost(targetContext); + int widgetId = host.allocateAppWidgetId(); + if (!new WidgetManagerHelper(targetContext) + .bindAppWidgetIdIfAllowed(widgetId, info, options)) { + host.deleteAppWidgetId(widgetId); + throw new IllegalArgumentException("Unable to bind widget id"); + } + item.appWidgetId = widgetId; + } + return item; + } + + /** + * Adds {@param item} on the homescreen on the 0th screen + */ + public static void addItemToScreen(ItemInfo item, Context targetContext) { + ContentResolver resolver = targetContext.getContentResolver(); + int screenId = FIRST_SCREEN_ID; + // Update the screen id counter for the provider. + LauncherSettings.Settings.call(resolver, + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); + + if (screenId > FIRST_SCREEN_ID) { + screenId = FIRST_SCREEN_ID; + } + + // Insert the item + ContentWriter writer = new ContentWriter(targetContext); + item.id = LauncherSettings.Settings.call( + resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) + .getInt(LauncherSettings.Settings.EXTRA_VALUE); + item.screenId = screenId; + item.onAddToDatabase(writer); + writer.put(LauncherSettings.Favorites._ID, item.id); + resolver.insert(LauncherSettings.Favorites.CONTENT_URI, + writer.getValues(targetContext)); + } +} From 9df3f0cebf40c2dfc8102de303364657ec3f68d1 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Tue, 21 Apr 2020 16:24:47 -0500 Subject: [PATCH 03/35] Stop computing scroll if handler is invalidated Bug: 153589287 Change-Id: I0282ec82fe4ae1f74a0f4342471ffd53f43095d7 --- .../src/com/android/quickstep/LauncherSwipeHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 5a64382478..6598971c4d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -1076,7 +1076,8 @@ public class LauncherSwipeHandler } private void continueComputingRecentsScrollIfNecessary() { - if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) { + if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED) + && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { computeRecentsScrollIfInvisible(); mRecentsView.post(this::continueComputingRecentsScrollIfNecessary); } From 0e7a338e8b8ca90c30bf7e4daf055febf4d24aaa Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 13 Apr 2020 17:15:05 -0700 Subject: [PATCH 04/35] Fixing SecondaryDisplay Launcher > All apps layout broken in landscpae UI > Crash when manager profile is added > Wrong icon size on low resolution Change-Id: If01dacf9f62a0384ebd8b31b62500178416d3ab4 --- res/layout/secondary_launcher.xml | 6 +- .../config/robolectric.properties | 1 + .../secondarydisplay/SDWorkModeTest.java | 133 +++++++++++++++++ .../LShadowApplicationPackageManager.java | 36 +++++ .../shadows/LShadowLauncherApps.java | 12 +- src/com/android/launcher3/DeviceProfile.java | 131 +++++++++++++---- .../launcher3/InvariantDeviceProfile.java | 139 ++++++++++++------ .../launcher3/allapps/WorkModeSwitch.java | 20 +-- .../SecondaryDisplayLauncher.java | 15 +- .../secondarydisplay/SecondaryDragLayer.java | 6 +- .../android/launcher3/views/ArrowTipView.java | 16 +- 11 files changed, 398 insertions(+), 117 deletions(-) create mode 100644 robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml index 98cfc349af..fdf4446c96 100644 --- a/res/layout/secondary_launcher.xml +++ b/res/layout/secondary_launcher.xml @@ -17,7 +17,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/drag_layer" > + android:id="@+id/drag_layer" + android:padding="@dimen/dynamic_grid_edge_margin"> + android:textSize="16sp" /> diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties index 0579400655..c162255bb4 100644 --- a/robolectric_tests/config/robolectric.properties +++ b/robolectric_tests/config/robolectric.properties @@ -1,5 +1,6 @@ sdk=29 shadows= \ + com.android.launcher3.shadows.LShadowApplicationPackageManager \ com.android.launcher3.shadows.LShadowAppPredictionManager \ com.android.launcher3.shadows.LShadowAppWidgetManager \ com.android.launcher3.shadows.LShadowBackupManager \ diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java new file mode 100644 index 0000000000..0ca5ce6f44 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 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.secondarydisplay; + +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; +import static com.android.launcher3.util.LauncherUIHelper.doLayout; +import static com.android.launcher3.util.Preconditions.assertNotNull; + +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.allapps.AllAppsPagedView; +import com.android.launcher3.allapps.AllAppsRecyclerView; +import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.shadows.ShadowOverrides; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowUserManager; + +/** + * Tests for {@link SecondaryDisplayLauncher} with work profile + */ +@RunWith(RobolectricTestRunner.class) +@LooperMode(Mode.PAUSED) +public class SDWorkModeTest { + + private static final int SYSTEM_USER = 0; + private static final int FLAG_SYSTEM = 0x00000800; + private static final int WORK_PROFILE_ID = 10; + private static final int FLAG_PROFILE = 0x00001000; + + private Context mTargetContext; + private InvariantDeviceProfile mIdp; + private LauncherModelHelper mModelHelper; + + private LauncherLayoutBuilder mLayoutBuilder; + + @Before + public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); + mTargetContext = RuntimeEnvironment.application; + mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + ShadowOverrides.setProvider(UserEventDispatcher.class, + c -> mock(UserEventDispatcher.class)); + Settings.Global.putFloat(mTargetContext.getContentResolver(), + Settings.Global.WINDOW_ANIMATION_SCALE, 0); + + mModelHelper.installApp(TEST_PACKAGE); + mLayoutBuilder = new LauncherLayoutBuilder(); + } + + @Test + public void testAllAppsList_noWorkProfile() throws Exception { + SecondaryDisplayLauncher launcher = loadLauncher(); + launcher.showAppDrawer(true); + doLayout(launcher); + + verifyRecyclerViewCount(launcher.getAppsView().getActiveRecyclerView()); + } + + @Test + public void testAllAppsList_workProfile() throws Exception { + ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class)); + sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM); + sum.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE); + + SecondaryDisplayLauncher launcher = loadLauncher(); + launcher.showAppDrawer(true); + doLayout(launcher); + + AllAppsRecyclerView rv1 = launcher.getAppsView().getActiveRecyclerView(); + verifyRecyclerViewCount(rv1); + + assertNotNull(launcher.getAppsView().getWorkModeSwitch()); + assertTrue(launcher.getAppsView().getRecyclerViewContainer() instanceof AllAppsPagedView); + + AllAppsPagedView pagedView = + (AllAppsPagedView) launcher.getAppsView().getRecyclerViewContainer(); + pagedView.snapToPageImmediately(1); + doLayout(launcher); + + AllAppsRecyclerView rv2 = launcher.getAppsView().getActiveRecyclerView(); + verifyRecyclerViewCount(rv2); + assertNotSame(rv1, rv2); + } + + private SecondaryDisplayLauncher loadLauncher() throws Exception { + // Install 100 apps + for (int i = 0; i < 100; i++) { + mModelHelper.installApp(TEST_PACKAGE + i); + } + mModelHelper.setupDefaultLayoutProvider(new LauncherLayoutBuilder()).loadModelSync(); + SecondaryDisplayLauncher launcher = + Robolectric.buildActivity(SecondaryDisplayLauncher.class).setup().get(); + doLayout(launcher); + return launcher; + } + + private void verifyRecyclerViewCount(AllAppsRecyclerView rv) { + int childCount = rv.getChildCount(); + assertTrue(childCount > 0); + assertTrue(childCount < 100); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java new file mode 100644 index 0000000000..da8b91986f --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 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.shadows; + +import android.os.Process; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowApplicationPackageManager; + +/** + * Shadow for {@link ShadowApplicationPackageManager} which create mock predictors + */ +@Implements(className = "android.app.ApplicationPackageManager") +public class LShadowApplicationPackageManager extends ShadowApplicationPackageManager { + + @Implementation + public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { + return Process.myUserHandle().equals(user) ? label : "Work " + label; + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java index 76cb74707f..6a6f0fbe70 100644 --- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java @@ -30,7 +30,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; -import android.os.Process; import android.os.UserHandle; import android.util.ArraySet; @@ -80,14 +79,15 @@ public class LShadowLauncherApps extends ShadowLauncherApps { protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { ResolveInfo ri = RuntimeEnvironment.application.getPackageManager() .resolveActivity(intent, 0); - return ri == null ? null : getLauncherActivityInfo(ri.activityInfo); + return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user); } - public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) { + public LauncherActivityInfo getLauncherActivityInfo( + ActivityInfo activityInfo, UserHandle user) { return callConstructor(LauncherActivityInfo.class, ClassParameter.from(Context.class, RuntimeEnvironment.application), ClassParameter.from(ActivityInfo.class, activityInfo), - ClassParameter.from(UserHandle.class, Process.myUserHandle())); + ClassParameter.from(UserHandle.class, user)); } @Implementation @@ -104,7 +104,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps { .setPackage(packageName); return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0) .stream() - .map(ri -> getLauncherActivityInfo(ri.activityInfo)) + .map(ri -> getLauncherActivityInfo(ri.activityInfo, user)) .collect(Collectors.toList()); } @@ -130,7 +130,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps { Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName); return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0) .stream() - .map(ri -> getLauncherActivityInfo(ri.activityInfo)) + .map(ri -> getLauncherActivityInfo(ri.activityInfo, user)) .collect(Collectors.toList()); } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index fc3c9fd1cc..a5f98c0cdc 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -22,7 +22,6 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.util.DisplayMetrics; import android.view.Surface; import com.android.launcher3.CellLayout.ContainerType; @@ -34,6 +33,7 @@ import com.android.launcher3.util.DefaultDisplay; public class DeviceProfile { public final InvariantDeviceProfile inv; + private final DefaultDisplay.Info mInfo; // Device properties public final boolean isTablet; @@ -133,9 +133,9 @@ public class DeviceProfile { public DotRenderer mDotRendererWorkSpace; public DotRenderer mDotRendererAllApps; - public DeviceProfile(Context context, InvariantDeviceProfile inv, - Point minSize, Point maxSize, - int width, int height, boolean isLandscape, boolean isMultiWindowMode) { + public DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info, + Point minSize, Point maxSize, int width, int height, boolean isLandscape, + boolean isMultiWindowMode, boolean transposeLayoutWithOrientation) { this.inv = inv; this.isLandscape = isLandscape; @@ -152,8 +152,8 @@ public class DeviceProfile { availableHeightPx = maxSize.y; } + mInfo = info; Resources res = context.getResources(); - DisplayMetrics dm = res.getDisplayMetrics(); // Constants from resources isTablet = res.getBoolean(R.bool.is_tablet); @@ -163,8 +163,7 @@ public class DeviceProfile { boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; // Some more constants - transposeLayoutWithOrientation = - res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation); + this.transposeLayoutWithOrientation = transposeLayoutWithOrientation; context = getContext(context, isVerticalBarLayout() ? Configuration.ORIENTATION_LANDSCAPE @@ -207,13 +206,14 @@ public class DeviceProfile { res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); // Add a bit of space between nav bar and hotseat in vertical bar layout. hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; - hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout() + hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) + + (isVerticalBarLayout() ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx) : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx)); // Calculate all of the remaining variables. - updateAvailableDimensions(dm, res); + updateAvailableDimensions(res); // Now that we have all of the variables calculated, we can tune certain sizes. if (!isVerticalBarLayout() && isPhone && isTallDevice) { @@ -227,7 +227,7 @@ public class DeviceProfile { hotseatBarBottomPaddingPx += extraSpace; // Recalculate the available dimensions using the new hotseat size. - updateAvailableDimensions(dm, res); + updateAvailableDimensions(res); } updateWorkspacePadding(); @@ -239,12 +239,21 @@ public class DeviceProfile { IconShape.DEFAULT_PATH_SIZE); } - public DeviceProfile copy(Context context) { + public Builder toBuilder(Context context) { Point size = new Point(availableWidthPx, availableHeightPx); - return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape, - isMultiWindowMode); + return new Builder(context, inv, mInfo) + .setSizeRange(size, size) + .setSize(widthPx, heightPx) + .setMultiWindowMode(isMultiWindowMode); } + public DeviceProfile copy(Context context) { + return toBuilder(context).build(); + } + + /** + * TODO: Move this to the builder as part of setMultiWindowMode + */ public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { // We take the minimum sizes of this profile and it's multi-window variant to ensure that // the system decor is always excluded. @@ -253,8 +262,11 @@ public class DeviceProfile { // In multi-window mode, we can have widthPx = availableWidthPx // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles' // widthPx and heightPx values where it's needed. - DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, - isLandscape, true); + DeviceProfile profile = toBuilder(context) + .setSizeRange(mwSize, mwSize) + .setSize(mwSize.x, mwSize.y) + .setMultiWindowMode(true) + .build(); // If there isn't enough vertical cell padding with the labels displayed, hide the labels. float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx @@ -289,27 +301,30 @@ public class DeviceProfile { iconTextSizePx = 0; iconDrawablePaddingPx = 0; cellHeightPx = iconSizePx; + autoResizeAllAppsCells(); + } - // In normal cases, All Apps cell height should equal the Workspace cell height. - // Since we are removing labels from the Workspace, we need to manually compute the - // All Apps cell height. + /** + * Re-computes the all-apps cell size to be independent of workspace + */ + public void autoResizeAllAppsCells() { int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1); allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx + Utilities.calculateTextHeight(allAppsIconTextSizePx) + topBottomPadding * 2; } - private void updateAvailableDimensions(DisplayMetrics dm, Resources res) { - updateIconSize(1f, res, dm); + private void updateAvailableDimensions(Resources res) { + updateIconSize(1f, res); // Check to see if the icons fit within the available height. If not, then scale down. float usedHeight = (cellHeightPx * inv.numRows); int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); if (usedHeight > maxHeight) { float scale = maxHeight / usedHeight; - updateIconSize(scale, res, dm); + updateIconSize(scale, res); } - updateAvailableFolderCellDimensions(dm, res); + updateAvailableFolderCellDimensions(res); } /** @@ -317,12 +332,13 @@ public class DeviceProfile { * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. */ - private void updateIconSize(float scale, Resources res, DisplayMetrics dm) { + private void updateIconSize(float scale, Resources res) { // Workspace final boolean isVerticalLayout = isVerticalBarLayout(); float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; - iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale)); - iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); + iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics) + * scale)); + iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale); iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); cellHeightPx = iconSizePx + iconDrawablePaddingPx @@ -340,8 +356,8 @@ public class DeviceProfile { // All apps if (allAppsHasDifferentNumColumns()) { - allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm); - allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm); + allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics); + allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics); allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y; allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; } else { @@ -381,12 +397,12 @@ public class DeviceProfile { folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; } - private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) { + private void updateAvailableFolderCellDimensions(Resources res) { int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top) + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); - updateFolderCellSize(1f, dm, res); + updateFolderCellSize(1f, res); // Don't let the folder get too close to the edges of the screen. int folderMargin = edgeMarginPx * 2; @@ -405,12 +421,12 @@ public class DeviceProfile { float scale = Math.min(scaleX, scaleY); if (scale < 1f) { - updateFolderCellSize(scale, dm, res); + updateFolderCellSize(scale, res); } } - private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) { - folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale); + private void updateFolderCellSize(float scale, Resources res) { + folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale); folderChildTextSizePx = (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale); @@ -627,4 +643,55 @@ public class DeviceProfile { */ void onDeviceProfileChanged(DeviceProfile dp); } + + public static class Builder { + private Context mContext; + private InvariantDeviceProfile mInv; + private DefaultDisplay.Info mInfo; + + private Point mMinSize, mMaxSize; + private int mWidth, mHeight; + + private boolean mIsLandscape; + private boolean mIsMultiWindowMode = false; + private boolean mTransposeLayoutWithOrientation; + + public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) { + mContext = context; + mInv = inv; + mInfo = info; + mTransposeLayoutWithOrientation = context.getResources() + .getBoolean(R.bool.hotseat_transpose_layout_with_orientation); + } + + public Builder setSizeRange(Point minSize, Point maxSize) { + mMinSize = minSize; + mMaxSize = maxSize; + return this; + } + + public Builder setSize(int width, int height) { + mWidth = width; + mHeight = height; + mIsLandscape = mWidth > mHeight; + return this; + } + + public Builder setMultiWindowMode(boolean isMultiWindowMode) { + mIsMultiWindowMode = isMultiWindowMode; + return this; + } + + public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) { + mTransposeLayoutWithOrientation = transposeLayoutWithOrientation; + return this; + } + + public DeviceProfile build() { + return new DeviceProfile(mContext, mInv, mInfo, mMinSize, mMaxSize, + mWidth, mHeight, mIsLandscape, mIsMultiWindowMode, + mTransposeLayoutWithOrientation); + } + } + } diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index d2d08639d4..0a1fad1d7d 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -159,10 +160,12 @@ public class InvariantDeviceProfile { @TargetApi(23) private InvariantDeviceProfile(Context context) { - String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) - ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) - : null; - initGrid(context, gridName); + String gridName = getCurrentGridName(context); + String newGridName = initGrid(context, gridName); + if (!newGridName.equals(gridName)) { + Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply(); + } + mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); mOverlayMonitor = new OverlayMonitor(context); @@ -178,17 +181,36 @@ public class InvariantDeviceProfile { } } -/** + /** * This constructor should NOT have any monitors by design. */ public InvariantDeviceProfile(Context context, Display display) { - initGrid(context, null, new Info(display)); + // Ensure that the main device profile is initialized + InvariantDeviceProfile originalProfile = INSTANCE.get(context); + String gridName = getCurrentGridName(context); + + // Get the display info based on default display and interpolate it to existing display + DisplayOption defaultDisplayOption = invDistWeightedInterpolate( + DefaultDisplay.INSTANCE.get(context).getInfo(), + getPredefinedDeviceProfiles(context, gridName)); + + Info myInfo = new Info(display); + DisplayOption myDisplayOption = invDistWeightedInterpolate( + myInfo, getPredefinedDeviceProfiles(context, gridName)); + + DisplayOption result = new DisplayOption(defaultDisplayOption.grid) + .add(myDisplayOption); + result.iconSize = defaultDisplayOption.iconSize; + result.landscapeIconSize = defaultDisplayOption.landscapeIconSize; + result.allAppsIconSize = Math.min( + defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize); + initGrid(context, myInfo, result); } public static String getCurrentGridName(Context context) { - return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) - ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) - : null; + SharedPreferences prefs = Utilities.getPrefs(context); + return prefs.getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? prefs.getString(KEY_IDP_GRID_NAME, null) : null; } /** @@ -203,27 +225,17 @@ public class InvariantDeviceProfile { } private String initGrid(Context context, String gridName) { - return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo()); + DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo(); + ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); + + DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions); + initGrid(context, displayInfo, displayOption); + return displayOption.grid.name; } - private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) { - Point smallestSize = new Point(displayInfo.smallestSize); - Point largestSize = new Point(displayInfo.largestSize); - - ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); - // This guarantees that width < height - float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), - displayInfo.metrics); - float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), - displayInfo.metrics); - // Sort the profiles based on the closeness to the device size - Collections.sort(allOptions, (a, b) -> - Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps), - dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps))); - DisplayOption interpolatedDisplayOption = - invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions); - - GridOption closestProfile = allOptions.get(0).grid; + private void initGrid( + Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) { + GridOption closestProfile = displayOption.grid; numRows = closestProfile.numRows; numColumns = closestProfile.numColumns; numHotseatIcons = closestProfile.numHotseatIcons; @@ -236,21 +248,16 @@ public class InvariantDeviceProfile { mExtraAttrs = closestProfile.extraAttrs; - if (!closestProfile.name.equals(gridName)) { - Utilities.getPrefs(context).edit() - .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply(); - } - - iconSize = interpolatedDisplayOption.iconSize; + iconSize = displayOption.iconSize; iconShapePath = getIconShapePath(context); - landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; + landscapeIconSize = displayOption.landscapeIconSize; iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics); - iconTextSize = interpolatedDisplayOption.iconTextSize; + iconTextSize = displayOption.iconTextSize; fillResIconDpi = getLauncherIconDensity(iconBitmapSize); if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) { - allAppsIconSize = interpolatedDisplayOption.allAppsIconSize; - allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize; + allAppsIconSize = displayOption.allAppsIconSize; + allAppsIconTextSize = displayOption.allAppsIconTextSize; } else { allAppsIconSize = iconSize; allAppsIconTextSize = iconTextSize; @@ -266,10 +273,12 @@ public class InvariantDeviceProfile { int smallSide = Math.min(realSize.x, realSize.y); int largeSide = Math.max(realSize.x, realSize.y); - landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize, - largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */); - portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize, - smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */); + DeviceProfile.Builder builder = new DeviceProfile.Builder(context, this, displayInfo) + .setSizeRange(new Point(displayInfo.smallestSize), + new Point(displayInfo.largestSize)); + + landscapeProfile = builder.setSize(largeSide, smallSide).build(); + portraitProfile = builder.setSize(smallSide, largeSide).build(); // We need to ensure that there is enough extra space in the wallpaper // for the intended parallax effects @@ -283,8 +292,6 @@ public class InvariantDeviceProfile { ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName()); defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null); - - return closestProfile.name; } @Nullable @@ -453,6 +460,41 @@ public class InvariantDeviceProfile { return (float) Math.hypot(x1 - x0, y1 - y0); } + @VisibleForTesting + static DisplayOption invDistWeightedInterpolate( + DefaultDisplay.Info displayInfo, ArrayList points) { + Point smallestSize = new Point(displayInfo.smallestSize); + Point largestSize = new Point(displayInfo.largestSize); + + // This guarantees that width < height + float width = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), + displayInfo.metrics); + float height = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), + displayInfo.metrics); + + // Sort the profiles based on the closeness to the device size + Collections.sort(points, (a, b) -> + Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps), + dist(width, height, b.minWidthDps, b.minHeightDps))); + + GridOption closestOption = points.get(0).grid; + float weights = 0; + + DisplayOption p = points.get(0); + if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) { + return p; + } + + DisplayOption out = new DisplayOption(closestOption); + for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) { + p = points.get(i); + float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER); + weights += w; + out.add(new DisplayOption().add(p).multiply(w)); + } + return out.multiply(1.0f / weights); + } + @VisibleForTesting static DisplayOption invDistWeightedInterpolate(float width, float height, ArrayList points) { @@ -573,7 +615,6 @@ public class InvariantDeviceProfile { private static final class DisplayOption { private final GridOption grid; - private final String name; private final float minWidthDps; private final float minHeightDps; private final boolean canBeDefault; @@ -590,7 +631,6 @@ public class InvariantDeviceProfile { TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ProfileDisplayOption); - name = a.getString(R.styleable.ProfileDisplayOption_name); minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0); minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0); canBeDefault = a.getBoolean( @@ -609,8 +649,11 @@ public class InvariantDeviceProfile { } DisplayOption() { - grid = null; - name = null; + this(null); + } + + DisplayOption(GridOption grid) { + this.grid = grid; minWidthDps = 0; minHeightDps = 0; canBeDefault = false; diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index 33262b6860..6692af5d9a 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -18,6 +18,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.AsyncTask; @@ -30,7 +31,6 @@ import android.view.ViewConfiguration; import android.widget.Switch; import com.android.launcher3.Insettable; -import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.pm.UserCache; @@ -79,9 +79,8 @@ public class WorkModeSwitch extends Switch implements Insettable { @Override public void toggle() { - Launcher launcher = Launcher.getLauncher(getContext()); // don't show tip if user uses toggle - launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply(); + Utilities.getPrefs(getContext()).edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply(); trySetQuietModeEnabledToAllProfilesAsync(isChecked()); } @@ -203,9 +202,8 @@ public class WorkModeSwitch extends Switch implements Insettable { } private boolean shouldShowWorkSwitch() { - Launcher launcher = Launcher.getLauncher(getContext()); - return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher) - || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE") + return Utilities.ATLEAST_P && (hasShortcutsPermission(getContext()) + || getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE") == PackageManager.PERMISSION_GRANTED); } @@ -213,12 +211,14 @@ public class WorkModeSwitch extends Switch implements Insettable { * Shows a work tip on the Nth work tab open */ public void showTipifNeeded() { - Launcher launcher = Launcher.getLauncher(getContext()); - int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD); + Context context = getContext(); + SharedPreferences prefs = Utilities.getPrefs(context); + int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD); if (tipCounter < 0) return; if (tipCounter == 0) { - new ArrowTipView(launcher).show(launcher.getString(R.string.work_switch_tip), getTop()); + new ArrowTipView(context) + .show(context.getString(R.string.work_switch_tip), getTop()); } - launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply(); + prefs.edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply(); } } diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index f1f271f9fd..dd6fc49426 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -89,17 +89,16 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity if (mDragLayer != null) { return; } - InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this); InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay()); - // Pick the device profile with the smaller icon size so that the cached icons are - // shown properly - if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) { - mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this); - } else { - mDeviceProfile = currentDisplayIdp.getDeviceProfile(this); - } + // Disable transpose layout and use multi-window mode so that the icons are scaled properly + mDeviceProfile = currentDisplayIdp.getDeviceProfile(this) + .toBuilder(this) + .setMultiWindowMode(true) + .setTransposeLayoutWithOrientation(false) + .build(); + mDeviceProfile.autoResizeAllAppsCells(); setContentView(R.layout.secondary_launcher); mDragLayer = findViewById(R.id.drag_layer); diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java index e35e884c77..40630d3c2e 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java @@ -117,10 +117,12 @@ public class SecondaryDragLayer extends BaseDragLayer if (child == mAppsView) { int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx + grid.cellLayoutPaddingLeftRightPx); - int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding; + int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding; int appsWidth = Math.min(width, maxWidth); - int appsHeight = Math.round(appsWidth * (float) height / (float) width); + + int maxHeight = grid.allAppsCellHeightPx * idp.numAllAppsColumns + padding; + int appsHeight = Math.min(height, maxHeight); mAppsView.measure( makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY)); diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java index 60470dc57f..a7575d12ba 100644 --- a/src/com/android/launcher3/views/ArrowTipView.java +++ b/src/com/android/launcher3/views/ArrowTipView.java @@ -32,7 +32,7 @@ import android.widget.TextView; import androidx.core.content.ContextCompat; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.Launcher; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.dragndrop.DragLayer; @@ -48,13 +48,13 @@ public class ArrowTipView extends AbstractFloatingView { private static final long SHOW_DURATION_MS = 300; private static final long HIDE_DURATION_MS = 100; - protected final Launcher mLauncher; + protected final BaseDraggingActivity mActivity; private final Handler mHandler = new Handler(); private Runnable mOnClosed; public ArrowTipView(Context context) { super(context, null, 0); - mLauncher = Launcher.getLauncher(context); + mActivity = BaseDraggingActivity.fromContext(context); init(context); } @@ -75,11 +75,11 @@ public class ArrowTipView extends AbstractFloatingView { .setStartDelay(0) .setDuration(HIDE_DURATION_MS) .setInterpolator(Interpolators.ACCEL) - .withEndAction(() -> mLauncher.getDragLayer().removeView(this)) + .withEndAction(() -> mActivity.getDragLayer().removeView(this)) .start(); } else { animate().cancel(); - mLauncher.getDragLayer().removeView(this); + mActivity.getDragLayer().removeView(this); } if (mOnClosed != null) mOnClosed.run(); mIsOpen = false; @@ -126,12 +126,12 @@ public class ArrowTipView extends AbstractFloatingView { */ public ArrowTipView show(String text, int top) { ((TextView) findViewById(R.id.text)).setText(text); - mLauncher.getDragLayer().addView(this); + mActivity.getDragLayer().addView(this); DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams(); params.gravity = Gravity.CENTER_HORIZONTAL; - params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left; - params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right; + params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left; + params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right; post(() -> setY(top - getHeight())); setAlpha(0); animate() From f065fdf311eeb072342b995b373388c6ebe748c8 Mon Sep 17 00:00:00 2001 From: thiruram Date: Tue, 14 Apr 2020 09:55:34 -0700 Subject: [PATCH 05/35] Updates folder creation logging. After ag/11020901, folder creation process will not open gboard and hence drag and drop events resulting in folder creation should send additional folder creation event. Fixes NPE with ItemInfo.java. Bug: 153768241 Change-Id: I52c996a62dee52bf07feef4252aadc2a28c79752 (cherry picked from commit 9a3671e80ebee3d6733e5a1e89b024228e1bfe71) --- src/com/android/launcher3/folder/Folder.java | 6 +++--- .../android/launcher3/folder/FolderIcon.java | 2 +- .../launcher3/model/data/ItemInfo.java | 21 ++++++++++++------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 12d88df7d9..c2871906e1 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -332,7 +332,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo .map(info -> info.suggestedFolderNames) .map(folderNames -> (FolderNameInfo[]) folderNames .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS)) - .ifPresent(nameInfos -> showLabelSuggestions(nameInfos)); + .ifPresent(this::showLabelSuggestions); } mFolderName.setHint(""); mIsEditingName = true; @@ -1450,7 +1450,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (hasFocus) { startEditingFolderName(); } else { - logEditFolderLabel(); + logCurrentFolderLabelState(); mFolderName.dispatchBackKey(); } } @@ -1649,7 +1649,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return mContent; } - private void logEditFolderLabel() { + protected void logCurrentFolderLabelState() { LauncherEvent launcherEvent = LauncherEvent.newBuilder() .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD)) .addSrcTarget(newEditTextTargetBuilder() diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 96bdc2ad2a..7fc6d5489c 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -416,6 +416,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mPreviewItemManager.hidePreviewItem(finalIndex, false); mFolder.showItem(item); setLabelSuggestion(nameInfos); + mFolder.logCurrentFolderLabelState(); invalidate(); }, DROP_IN_ANIMATION_DURATION); } @@ -438,7 +439,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); - // TODO: Add logging while folder creation. } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index bd843e77c9..14f9a3e6d1 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -37,6 +37,8 @@ import com.android.launcher3.Workspace; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.util.ContentWriter; +import java.util.Optional; + /** * Represents an item in the launcher. */ @@ -248,24 +250,29 @@ public class ItemInfo { LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder(); itemBuilder.setIsWork(user != Process.myUserHandle()); - ComponentName cn = getTargetComponent(); + Optional nullableComponent = Optional.ofNullable(getTargetComponent()); switch (itemType) { case ITEM_TYPE_APPLICATION: - itemBuilder.setApplication(LauncherAtom.Application.newBuilder() - .setComponentName(cn.flattenToShortString()) - .setPackageName(cn.getPackageName())); + itemBuilder + .setApplication(nullableComponent + .map(component -> LauncherAtom.Application.newBuilder() + .setComponentName(component.flattenToShortString()) + .setPackageName(component.getPackageName())) + .orElse(LauncherAtom.Application.newBuilder())); break; case ITEM_TYPE_DEEP_SHORTCUT: case ITEM_TYPE_SHORTCUT: - itemBuilder.setShortcut(LauncherAtom.Shortcut.newBuilder() - .setShortcutName(cn.flattenToShortString())); + itemBuilder + .setShortcut(nullableComponent + .map(component -> LauncherAtom.Shortcut.newBuilder() + .setShortcutName(component.flattenToShortString())) + .orElse(LauncherAtom.Shortcut.newBuilder())); break; case ITEM_TYPE_APPWIDGET: setItemBuilder(itemBuilder); break; default: break; - } if (fInfo != null) { LauncherAtom.FolderContainer.Builder folderBuilder = From b7833f8193c124fc107066c6b8d8e1d030abe23f Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 21 Apr 2020 13:50:08 -0700 Subject: [PATCH 06/35] Moving various orientation related config in RecentsOrientaedState > Fixing RecentsOrientationState values for fallback recents > Using proper rotation in task simulator Change-Id: I770e7c8850e469f5dc0ce6f89889672f2decdc0d --- .../android/quickstep/BaseSwipeUpHandler.java | 2 +- .../quickstep/LauncherSwipeHandler.java | 3 +- .../quickstep/TouchInteractionService.java | 3 +- .../fallback/FallbackRecentsView.java | 7 +- .../util/AppWindowAnimationHelper.java | 3 +- .../quickstep/util/TaskViewSimulator.java | 32 ++- .../quickstep/views/LauncherRecentsView.java | 9 +- .../quickstep/views/OverviewActionsView.java | 4 +- .../android/quickstep/views/RecentsView.java | 74 +----- .../RecentsAnimationDeviceState.java | 6 +- .../quickstep/util/RecentsOrientedState.java | 226 +++++++++++------- .../launcher3/config/FeatureFlags.java | 4 - .../launcher3/states/RotationHelper.java | 1 - 13 files changed, 188 insertions(+), 186 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index 32ab98bd4d..d22e5afcc5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -334,7 +334,7 @@ public abstract class BaseSwipeUpHandler mTaskAnimationManager = taskAnimationManager; mTouchTimeMs = touchTimeMs; mContinuingLastGesture = continuingLastGesture; - mTaskViewSimulator = new TaskViewSimulator(context, LayoutUtils::calculateLauncherTaskSize); + mTaskViewSimulator = new TaskViewSimulator( + context, LayoutUtils::calculateLauncherTaskSize, true); initAfterSubclassConstructor(); initStateCallbacks(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 9b5a935e6b..28c2b97309 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -23,6 +23,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.GestureState.DEFAULT_STATE; +import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; @@ -597,7 +598,7 @@ public class TouchInteractionService extends Service implements PluginListener { } public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + super(context, attrs, defStyleAttr, false); } @Override @@ -195,9 +195,4 @@ public class FallbackRecentsView extends RecentsView { } super.applyLoadPlan(tasks); } - - @Override - protected boolean supportsVerticalLandscape() { - return false; - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index 00329b87e0..9309110fd0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -267,7 +267,8 @@ public class AppWindowAnimationHelper { mTmpRectF.set(mTargetRect); Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale); mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF)); - if (mOrientedState == null || mOrientedState.areMultipleLayoutOrientationsDisabled()) { + if (mOrientedState == null + || !mOrientedState.isMultipleOrientationSupportedByDevice()) { mCurrentRect.offset(params.mOffset, 0); } else { int displayRotation = mOrientedState.getDisplayRotation(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java index 0131fdf5c4..0bc021b377 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java @@ -15,9 +15,13 @@ */ package com.android.quickstep.util; +import static android.view.Surface.ROTATION_0; + import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.launcher3.states.RotationHelper.deltaRotation; import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE; import static com.android.quickstep.util.AppWindowAnimationHelper.applySurfaceParams; +import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; @@ -33,7 +37,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.RecentsAnimationTargets; @@ -91,11 +94,17 @@ public class TaskViewSimulator { private boolean mLayoutValid = false; private boolean mScrollValid = false; - public TaskViewSimulator(Context context, TaskSizeProvider sizeProvider) { + public TaskViewSimulator(Context context, TaskSizeProvider sizeProvider, + boolean rotationSupportedByActivity) { mContext = context; mSizeProvider = sizeProvider; mPositionHelper = new PreviewPositionHelper(context); - mOrientationState = new RecentsOrientedState(context); + + mOrientationState = new RecentsOrientedState(context, rotationSupportedByActivity, + i -> { }); + // We do not need to attach listeners as the simulator is created just for the gesture + // duration, and any settings are unlikely to change during this + mOrientationState.initWithoutListeners(); mCurrentFullscreenParams = new FullscreenDrawParams(context); mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); @@ -114,11 +123,15 @@ public class TaskViewSimulator { * @see com.android.quickstep.views.RecentsView#setLayoutRotation(int, int) */ public void setLayoutRotation(int touchRotation, int displayRotation) { - if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) { - return; + int launcherRotation; + if (!mOrientationState.isMultipleOrientationSupportedByDevice() + || mOrientationState.isHomeRotationAllowed()) { + launcherRotation = displayRotation; + } else { + launcherRotation = ROTATION_0; } - mOrientationState.update(touchRotation, displayRotation, - mOrientationState.getLauncherRotation()); + + mOrientationState.update(touchRotation, displayRotation, launcherRotation); mLayoutValid = false; } @@ -180,7 +193,7 @@ public class TaskViewSimulator { mLayoutValid = true; getFullScreenScale(); - mThumbnailData.rotation = FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get() + mThumbnailData.rotation = isFixedRotationTransformEnabled(mContext) ? mOrientationState.getDisplayRotation() : mPositionHelper.getCurrentRotation(); mPositionHelper.updateThumbnailMatrix(mThumbnailPosition, mThumbnailData, @@ -226,7 +239,8 @@ public class TaskViewSimulator { // Apply recensView matrix mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y); - postDisplayRotation(mOrientationState.getDisplayRotation(), + postDisplayRotation(deltaRotation( + mOrientationState.getLauncherRotation(), mOrientationState.getDisplayRotation()), mDp.widthPx, mDp.heightPx, mMatrix); // Crop rect is the inverse of thumbnail matrix diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 454223e06f..0b6d340313 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -45,7 +45,6 @@ import com.android.launcher3.LauncherStateManager.StateListener; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; @@ -96,7 +95,7 @@ public class LauncherRecentsView extends RecentsView } public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + super(context, attrs, defStyleAttr, true); mActivity.getStateManager().addStateListener(this); } @@ -264,12 +263,6 @@ public class LauncherRecentsView extends RecentsView return mTransformParams; } - @Override - protected boolean supportsVerticalLandscape() { - return FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get() - && !mOrientationState.areMultipleLayoutOrientationsDisabled(); - } - @Override public void reset() { super.reset(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java index 93e68c0f5d..d160686f73 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -48,7 +48,8 @@ public class OverviewActionsView extends FrameLayo HIDDEN_NON_ZERO_ROTATION, HIDDEN_NO_TASKS, HIDDEN_GESTURE_RUNNING, - HIDDEN_NO_RECENTS}) + HIDDEN_NO_RECENTS, + HIDDEN_FULLESCREEN_PROGRESS}) @Retention(RetentionPolicy.SOURCE) public @interface ActionsHiddenFlags { } @@ -58,6 +59,7 @@ public class OverviewActionsView extends FrameLayo public static final int HIDDEN_NO_TASKS = 1 << 3; public static final int HIDDEN_GESTURE_RUNNING = 1 << 4; public static final int HIDDEN_NO_RECENTS = 1 << 5; + public static final int HIDDEN_FULLESCREEN_PROGRESS = 1 << 6; private static final int INDEX_CONTENT_ALPHA = 0; private static final int INDEX_VISIBILITY_ALPHA = 1; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index b687920f49..90dc5a46dd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -38,6 +38,7 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType. import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; +import static com.android.quickstep.views.OverviewActionsView.HIDDEN_FULLESCREEN_PROGRESS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; @@ -75,8 +76,6 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.OrientationEventListener; -import android.view.Surface; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -103,7 +102,6 @@ import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statehandlers.DepthController; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -191,8 +189,6 @@ public abstract class RecentsView extends PagedView impl }; protected final RecentsOrientedState mOrientationState; - private OrientationEventListener mOrientationListener; - private int mPreviousRotation; protected RecentsAnimationController mRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; protected AppWindowAnimationHelper mAppWindowAnimationHelper; @@ -301,9 +297,6 @@ public abstract class RecentsView extends PagedView impl } }; - private final RecentsOrientedState.SystemRotationChangeListener mSystemRotationChangeListener = - enabled -> toggleOrientationEventListener(); - private final PinnedStackAnimationListener mIPinnedStackAnimationListener = new PinnedStackAnimationListener(); @@ -360,11 +353,13 @@ public abstract class RecentsView extends PagedView impl } }; - public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { + public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, + boolean rotationSupportedByActivity) { super(context, attrs, defStyleAttr); setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); setEnableFreeScroll(true); - mOrientationState = new RecentsOrientedState(context); + mOrientationState = new RecentsOrientedState( + context, rotationSupportedByActivity, this::animateRecentsRotationInPlace); mFastFlingVelocity = getResources() .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); @@ -399,21 +394,10 @@ public abstract class RecentsView extends PagedView impl .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); setWillNotDraw(false); updateEmptyMessage(); - disableMultipleLayoutRotations(!supportsVerticalLandscape()); + mOrientationHandler = mOrientationState.getOrientationHandler(); // Initialize quickstep specific cache params here, as this is constructed only once mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); - - mOrientationListener = new OrientationEventListener(getContext()) { - @Override - public void onOrientationChanged(int i) { - int rotation = RecentsOrientedState.getRotationForUserDegreesRotated(i); - if (mPreviousRotation != rotation) { - animateRecentsRotationInPlace(rotation); - mPreviousRotation = rotation; - } - } - }; } public OverScroller getScroller() { @@ -502,7 +486,6 @@ public abstract class RecentsView extends PagedView impl SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( mIPinnedStackAnimationListener); mOrientationState.init(); - mOrientationState.addSystemRotationChangeListener(mSystemRotationChangeListener); } @Override @@ -517,7 +500,6 @@ public abstract class RecentsView extends PagedView impl mIdp.removeOnChangeListener(this); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); mIPinnedStackAnimationListener.setActivity(null); - mOrientationState.removeSystemRotationChangeListener(mSystemRotationChangeListener); mOrientationState.destroy(); } @@ -576,31 +558,12 @@ public abstract class RecentsView extends PagedView impl public void setOverviewStateEnabled(boolean enabled) { mOverviewStateEnabled = enabled; updateTaskStackListenerState(); + mOrientationState.setRotationWatcherEnabled(enabled); if (!enabled) { // Reset the running task when leaving overview since it can still have a reference to // its thumbnail mTmpRunningTask = null; } - toggleOrientationEventListener(); - } - - private void toggleOrientationEventListener() { - boolean canEnable = canEnableOverviewRotationAnimation() && mOverviewStateEnabled; - UI_HELPER_EXECUTOR.execute(() -> { - if (canEnable) { - mOrientationListener.enable(); - } else { - mOrientationListener.disable(); - } - }); - } - - private boolean canEnableOverviewRotationAnimation() { - return supportsVerticalLandscape() // not 3P launcher - && !TestProtocol.sDisableSensorRotation // Ignore hardware dependency for tests.. - && mOrientationListener.canDetectOrientation() // ..but does the hardware even work? - && (mOrientationState.isSystemRotationAllowed() && - !mOrientationState.canLauncherRotate()); // launcher is going to rotate itself } public void onDigitalWellbeingToastShown() { @@ -820,9 +783,7 @@ public abstract class RecentsView extends PagedView impl for (int i = 0; i < taskCount; i++) { getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress); } - if (mActionsView != null && mOrientationState.getLauncherRotation() == Surface.ROTATION_0) { - mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE); - } + mActionsView.updateHiddenFlags(HIDDEN_FULLESCREEN_PROGRESS, fullscreenProgress > 0); } private void updateTaskStackListenerState() { @@ -1039,10 +1000,6 @@ public abstract class RecentsView extends PagedView impl } private void animateRecentsRotationInPlace(int newRotation) { - if (!supportsVerticalLandscape()) { - return; - } - AnimatorSet pa = setRecentsChangedOrientation(true); pa.addListener(AnimationSuccessListener.forRunnable(() -> { setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); @@ -1067,7 +1024,6 @@ public abstract class RecentsView extends PagedView impl return as; } - abstract protected boolean supportsVerticalLandscape(); private void rotateAllChildTasks() { for (int i = 0; i < getTaskViewCount(); i++) { @@ -1611,17 +1567,12 @@ public abstract class RecentsView extends PagedView impl setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated()); mActivity.getDragLayer().recreateControllers(); - mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, touchRotation != 0); + mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, + touchRotation != 0 || launcherRotation != 0); requestLayout(); } } - public void disableMultipleLayoutRotations(boolean disable) { - mOrientationState.disableMultipleOrientations(disable); - mOrientationHandler = mOrientationState.getOrientationHandler(); - requestLayout(); - } - public RecentsOrientedState getPagedViewOrientedState() { return mOrientationState; } @@ -2083,8 +2034,7 @@ public abstract class RecentsView extends PagedView impl public Consumer getEventDispatcher(float navbarRotation) { float degreesRotated; if (navbarRotation == 0) { - degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 : - mOrientationHandler.getDegreesRotated(); + degreesRotated = mOrientationHandler.getDegreesRotated(); } else { degreesRotated = -navbarRotation; } @@ -2097,7 +2047,7 @@ public abstract class RecentsView extends PagedView impl // PagedOrientationHandler return e -> { if (navbarRotation != 0 - && !mOrientationState.areMultipleLayoutOrientationsDisabled() + && mOrientationState.isMultipleOrientationSupportedByDevice() && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) { mOrientationState.flipVertical(e); super.onTouchEvent(e); diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 0a00a61c12..a6ce2b5340 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -21,6 +21,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; +import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; @@ -35,7 +36,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -51,8 +51,6 @@ import androidx.annotation.BinderThread; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.util.NavBarPosition; @@ -176,7 +174,7 @@ public class RecentsAnimationDeviceState implements } private void setupOrientationSwipeHandler() { - if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) { + if (!isFixedRotationTransformEnabled(mContext)) { return; } diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index 5be0675cbb..74daeca33f 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -16,17 +16,13 @@ package com.android.quickstep.util; -import static android.Manifest.permission.WRITE_SECURE_SETTINGS; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; -import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -44,19 +40,18 @@ import android.os.Handler; import android.provider.Settings; import android.util.Log; import android.view.MotionEvent; +import android.view.OrientationEventListener; import android.view.Surface; import androidx.annotation.IntDef; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.touch.PortraitPagedViewHandler; import java.lang.annotation.Retention; -import java.util.ArrayList; -import java.util.List; +import java.util.function.IntConsumer; /** * Container to hold orientation/rotation related information for Launcher. @@ -71,6 +66,8 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private static final String TAG = "RecentsOrientedState"; private static final boolean DEBUG = false; + private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -87,46 +84,79 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private @SurfaceRotation int mDisplayRotation = ROTATION_0; private @SurfaceRotation int mLauncherRotation = Surface.ROTATION_0; - public interface SystemRotationChangeListener { - void onSystemRotationChanged(boolean enabled); - } + // Launcher activity supports multiple orientation, but fallback activity does not + private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0; + // Multiple orientation is only supported if density is < 600 + private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY = 1 << 1; + // Feature flag controlling the multi-orientation feature + private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG = 1 << 2; + // Shared prefs for rotation, only if activity supports it + private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 3; + // If the user has enabled system rotation + private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 4; + // Whether to rotation sensor is supported on the device + private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 5; + // Whether to enable rotation watcher when multi-rotation is supported + private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6; - /** - * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake - * launcher orientations. - */ - private boolean mDisableMultipleOrientations; - private boolean mIsHomeRotationAllowed; - private boolean mIsSystemRotationAllowed; + private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE = + FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY + | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY + | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG; + + private static final int MASK_ACTIVITY_ROTATING = + FLAG_HOME_ROTATION_ALLOWED_IN_PREFS | FLAG_SYSTEM_ROTATION_ALLOWED; + + // State for which rotation watcher will be enabled. + // We skip it when home rotation is enabled as in that case, activity itself rotates + private static final int VALUE_ROTATION_WATCHER_ENABLED = + MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED + | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED; private final ContentResolver mContentResolver; private final SharedPreferences mSharedPrefs; - private final boolean mAllowConfigurationDefaultValue; - - private List mSystemRotationChangeListeners = new ArrayList<>(); + private final OrientationEventListener mOrientationListener; private final Matrix mTmpMatrix = new Matrix(); private final Matrix mTmpInverseMatrix = new Matrix(); - public RecentsOrientedState(Context context) { + private int mFlags; + private int mPreviousRotation = ROTATION_0; + + /** + * @param rotationChangeListener Callback for receiving rotation events when rotation watcher + * is enabled + * @see #setRotationWatcherEnabled(boolean) + */ + public RecentsOrientedState(Context context, boolean rotationSupportedByActivity, + IntConsumer rotationChangeListener) { mContentResolver = context.getContentResolver(); mSharedPrefs = Utilities.getPrefs(context); + mOrientationListener = new OrientationEventListener(context) { + @Override + public void onOrientationChanged(int degrees) { + int newRotation = getRotationForUserDegreesRotated(degrees); + if (newRotation != mPreviousRotation) { + mPreviousRotation = newRotation; + rotationChangeListener.accept(newRotation); + } + } + }; + + mFlags = rotationSupportedByActivity ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0; Resources res = context.getResources(); int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE; - mAllowConfigurationDefaultValue = originalSmallestWidth >= 600; - - boolean isForcedRotation = Utilities.getFeatureFlagsPrefs(context) - .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true) - && !mAllowConfigurationDefaultValue; - UI_HELPER_EXECUTOR.execute(() -> { - if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) { - Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME, - isForcedRotation ? 1 : 0); - } - }); - disableMultipleOrientations(!isForcedRotation); + if (originalSmallestWidth < 600) { + mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY; + } + if (isFixedRotationTransformEnabled(context)) { + mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG; + } + if (mOrientationListener.canDetectOrientation()) { + mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED; + } } /** @@ -140,10 +170,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre public boolean update( @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation, @SurfaceRotation int launcherRotation) { - if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) { - return false; - } - if (mDisableMultipleOrientations) { + if (!isMultipleOrientationSupportedByDevice()) { return false; } if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation @@ -155,8 +182,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mDisplayRotation = displayRotation; mTouchRotation = touchRotation; - if ((mIsHomeRotationAllowed && mIsSystemRotationAllowed) || - mLauncherRotation == mTouchRotation) { + if (canLauncherRotate() || mLauncherRotation == mTouchRotation) { // TODO(b/153476489) Need to determine when launcher is rotated mOrientationHandler = PagedOrientationHandler.HOME_ROTATED; if (DEBUG) { @@ -178,14 +204,25 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre return true; } - /** - * Setting this preference renders future calls to {@link #update(int, int, int)} as a no-op. - */ - public void disableMultipleOrientations(boolean disable) { - mDisableMultipleOrientations = disable; - if (disable) { - mDisplayRotation = mTouchRotation = ROTATION_0; - mOrientationHandler = PagedOrientationHandler.PORTRAIT; + private void setFlag(int mask, boolean enabled) { + boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation + && mFlags == VALUE_ROTATION_WATCHER_ENABLED; + if (enabled) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + + boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation + && mFlags == VALUE_ROTATION_WATCHER_ENABLED; + if (wasRotationEnabled != isRotationEnabled) { + UI_HELPER_EXECUTOR.execute(() -> { + if (isRotationEnabled) { + mOrientationListener.enable(); + } else { + mOrientationListener.disable(); + } + }); } } @@ -195,47 +232,49 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre } private void updateAutoRotateSetting() { - try { - mIsSystemRotationAllowed = Settings.System.getInt(mContentResolver, - Settings.System.ACCELEROMETER_ROTATION) == 1; - } catch (Settings.SettingNotFoundException e) { - Log.e(TAG, "autorotate setting not found", e); - } - - for (SystemRotationChangeListener listener : mSystemRotationChangeListeners) { - listener.onSystemRotationChanged(mIsSystemRotationAllowed); - } + setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver, + Settings.System.ACCELEROMETER_ROTATION, 1) == 1); } private void updateHomeRotationSetting() { - mIsHomeRotationAllowed = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, - mAllowConfigurationDefaultValue); - } - - public void addSystemRotationChangeListener(SystemRotationChangeListener listener) { - mSystemRotationChangeListeners.add(listener); - listener.onSystemRotationChanged(mIsSystemRotationAllowed); - } - - public void removeSystemRotationChangeListener(SystemRotationChangeListener listener) { - mSystemRotationChangeListeners.remove(listener); + setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS, + mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false)); } + /** + * Initializes aany system values and registers corresponding change listeners. It must be + * paired with {@link #destroy()} call + */ public void init() { - mSharedPrefs.registerOnSharedPreferenceChangeListener(this); - mContentResolver.registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), - false, mSystemAutoRotateObserver); + if (isMultipleOrientationSupportedByDevice()) { + mSharedPrefs.registerOnSharedPreferenceChangeListener(this); + mContentResolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), + false, mSystemAutoRotateObserver); + } + initWithoutListeners(); + } + + /** + * Unregisters any previously registered listeners. + */ + public void destroy() { + if (isMultipleOrientationSupportedByDevice()) { + mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this); + mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver); + } + setRotationWatcherEnabled(false); + } + + /** + * Initializes the OrientationState without attaching any listeners. This can be used when + * the object is short lived. + */ + public void initWithoutListeners() { updateAutoRotateSetting(); updateHomeRotationSetting(); } - public void destroy() { - mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this); - mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver); - mSystemRotationChangeListeners.clear(); - } - @SurfaceRotation public int getDisplayRotation() { return mDisplayRotation; @@ -251,20 +290,24 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre return mLauncherRotation; } - public boolean areMultipleLayoutOrientationsDisabled() { - return mDisableMultipleOrientations; - } - - public boolean isSystemRotationAllowed() { - return mIsSystemRotationAllowed; + public boolean isMultipleOrientationSupportedByDevice() { + return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE) + == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE; } public boolean isHomeRotationAllowed() { - return mIsHomeRotationAllowed; + return (mFlags & FLAG_HOME_ROTATION_ALLOWED_IN_PREFS) != 0; } public boolean canLauncherRotate() { - return isSystemRotationAllowed() && isHomeRotationAllowed(); + return (mFlags & MASK_ACTIVITY_ROTATING) == MASK_ACTIVITY_ROTATING; + } + + /** + * Enables or disables the rotation watcher for listening to rotation callbacks + */ + public void setRotationWatcherEnabled(boolean isEnabled) { + setFlag(FLAG_ROTATION_WATCHER_ENABLED, isEnabled); } public int getTouchRotationDegrees() { @@ -388,4 +431,13 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre break; } } + + /** + * Returns true if system can keep Launcher fixed to portrait layout even if the + * foreground app is rotated + */ + public static boolean isFixedRotationTransformEnabled(Context context) { + return Settings.Global.getInt( + context.getContentResolver(), FIXED_ROTATION_TRANSFORM_SETTING_NAME, 1) == 1; + } } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 2b91cb1fa3..65d3cd2ded 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -159,10 +159,6 @@ public final class FeatureFlags { "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false, "Always use hardware optimization for folder animations."); - public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag( - FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true, - "Launch/close apps without rotation animation. Fix Launcher to portrait"); - public static void initialize(Context context) { synchronized (sDebugFlags) { for (DebugFlag flag : sDebugFlags) { diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 3640d8bbde..5a60f5328a 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -43,7 +43,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation"; - public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; private final ContentResolver mContentResolver; private boolean mSystemAutoRotateEnabled; From 7f9e8e2d2758d4bed14adc69d98f8fe2e1186a83 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 17 Apr 2020 12:05:21 -0700 Subject: [PATCH 07/35] Fixing animation player not overshooting spring animation; tuning springs => setCurrentPlayTime is bounded between [0, duration] by the animation framework Instead using interpolator so that we can go outside the bounds => Tune spring stiffness and dampening for overview card dismiss animations Bug: 154061408 Change-Id: Iaa31491fff499db916b36d9779ec159b8a89a2de --- .../LauncherAppTransitionManagerImpl.java | 5 +- .../util/StaggeredWorkspaceAnim.java | 5 +- res/values/config.xml | 8 +-- .../anim/AnimatorPlaybackController.java | 33 +++------ .../anim/SpringAnimationBuilder.java | 68 ++++++++++--------- 5 files changed, 54 insertions(+), 65 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 6c64bf7c75..8cf5da2144 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -199,13 +199,12 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti RecentsView.CONTENT_ALPHA, values); case INDEX_RECENTS_TRANSLATE_X_ANIM: // TODO: Do not assume motion across X axis for adjacent page - return new SpringAnimationBuilder<>( - mLauncher.getOverviewPanel(), ADJACENT_PAGE_OFFSET) + return new SpringAnimationBuilder(mLauncher) .setMinimumVisibleChange(1f / mLauncher.getOverviewPanel().getWidth()) .setDampingRatio(0.8f) .setStiffness(250) .setValues(values) - .build(mLauncher); + .build(mLauncher.getOverviewPanel(), ADJACENT_PAGE_OFFSET); case INDEX_PAUSE_TO_OVERVIEW_ANIM: { StateAnimationConfig config = new StateAnimationConfig(); config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index bde6f9af19..13c20f1800 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -28,6 +28,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.view.View; import android.view.ViewGroup; @@ -185,13 +186,13 @@ public class StaggeredWorkspaceAnim { ResourceProvider rp = DynamicResource.provider(v.getContext()); float stiffness = rp.getFloat(R.dimen.staggered_stiffness); float damping = rp.getFloat(R.dimen.staggered_damping_ratio); - ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y) + ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext()) .setStiffness(stiffness) .setDampingRatio(damping) .setMinimumVisibleChange(1f) .setEndValue(0) .setStartVelocity(mVelocity) - .build(v.getContext()); + .build(v, VIEW_TRANSLATE_Y); springTransY.setStartDelay(startDelay); mAnimators.play(springTransY); diff --git a/res/values/config.xml b/res/values/config.xml index 0657b86a6c..41bb909a1d 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -125,11 +125,11 @@ 0.75 600 - 0.5 - 1500 + 0.73 + 800 - 0.5 - 1500 + 0.73 + 800 0.8 400 diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index f12789a90f..e11917b273 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -27,7 +27,6 @@ import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; -import android.util.FloatProperty; import androidx.annotation.Nullable; @@ -64,19 +63,6 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL return new AnimatorPlaybackController(anim, duration, childAnims); } - private static final FloatProperty CURRENT_PLAY_TIME = - new FloatProperty("current-play-time") { - @Override - public void setValue(ValueAnimator animator, float v) { - animator.setCurrentPlayTime((long) v); - } - - @Override - public Float get(ValueAnimator animator) { - return (float) animator.getCurrentPlayTime(); - } - }; - // Progress factor after which an animation is considered almost completed. private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f; @@ -177,21 +163,22 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL long springDuration = animationDuration; for (Holder h : mChildAnimations) { if ((h.springProperty.flags & springFlag) != 0) { - SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME) - .setStartValue(clampDuration(mCurrentFraction)) - .setEndValue(goingToEnd ? h.anim.getDuration() : 0) - .setStartVelocity(scaledVelocity * h.anim.getDuration()) + SpringAnimationBuilder s = new SpringAnimationBuilder(context) + .setStartValue(mCurrentFraction) + .setEndValue(goingToEnd ? 1 : 0) + .setStartVelocity(scaledVelocity) .setMinimumVisibleChange(scaleInverse) .setDampingRatio(h.springProperty.mDampingRatio) - .setStiffness(h.springProperty.mStiffness); + .setStiffness(h.springProperty.mStiffness) + .computeParams(); - long expectedDurationL = s.build(context).getDuration(); + long expectedDurationL = s.getDuration(); springDuration = Math.max(expectedDurationL, springDuration); float expectedDuration = expectedDurationL; - h.setter = (a, l) -> - s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration); - h.anim.setInterpolator(LINEAR); + h.setter = (a, l) -> a.setCurrentFraction( + mAnimationPlayer.getCurrentPlayTime() / expectedDuration); + h.anim.setInterpolator(s::getInterpolatedValue); } } diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java index f22a9f0ac6..bc77aab858 100644 --- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java +++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java @@ -15,7 +15,9 @@ */ package com.android.launcher3.anim; -import android.animation.ObjectAnimator; +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.animation.ValueAnimator; import android.content.Context; import android.util.FloatProperty; @@ -28,10 +30,9 @@ import com.android.launcher3.util.DefaultDisplay; * Utility class to build an object animator which follows the same path as a spring animation for * an underdamped spring. */ -public class SpringAnimationBuilder extends FloatProperty { +public class SpringAnimationBuilder { - private final T mTarget; - private final FloatProperty mProperty; + private final Context mContext; private float mStartValue; private float mEndValue; @@ -64,27 +65,23 @@ public class SpringAnimationBuilder extends FloatProperty { private double mValueThreshold; private double mVelocityThreshold; - private float mCurrentTime = 0; + private float mDuration = 0; - public SpringAnimationBuilder(T target, FloatProperty property) { - super("dynamic-spring-property"); - mTarget = target; - mProperty = property; - - mStartValue = mProperty.get(target); + public SpringAnimationBuilder(Context context) { + mContext = context; } - public SpringAnimationBuilder setEndValue(float value) { + public SpringAnimationBuilder setEndValue(float value) { mEndValue = value; return this; } - public SpringAnimationBuilder setStartValue(float value) { + public SpringAnimationBuilder setStartValue(float value) { mStartValue = value; return this; } - public SpringAnimationBuilder setValues(float... values) { + public SpringAnimationBuilder setValues(float... values) { if (values.length > 1) { mStartValue = values[0]; mEndValue = values[values.length - 1]; @@ -94,7 +91,7 @@ public class SpringAnimationBuilder extends FloatProperty { return this; } - public SpringAnimationBuilder setStiffness( + public SpringAnimationBuilder setStiffness( @FloatRange(from = 0.0, fromInclusive = false) float stiffness) { if (stiffness <= 0) { throw new IllegalArgumentException("Spring stiffness constant must be positive."); @@ -103,7 +100,7 @@ public class SpringAnimationBuilder extends FloatProperty { return this; } - public SpringAnimationBuilder setDampingRatio( + public SpringAnimationBuilder setDampingRatio( @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) float dampingRatio) { if (dampingRatio <= 0 || dampingRatio >= 1) { @@ -113,7 +110,7 @@ public class SpringAnimationBuilder extends FloatProperty { return this; } - public SpringAnimationBuilder setMinimumVisibleChange( + public SpringAnimationBuilder setMinimumVisibleChange( @FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) { if (minimumVisibleChange <= 0) { throw new IllegalArgumentException("Minimum visible change must be positive."); @@ -122,25 +119,21 @@ public class SpringAnimationBuilder extends FloatProperty { return this; } - public SpringAnimationBuilder setStartVelocity(float startVelocity) { + public SpringAnimationBuilder setStartVelocity(float startVelocity) { mVelocity = startVelocity; return this; } - @Override - public void setValue(T object, float time) { - mCurrentTime = time; - mProperty.setValue( - object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue); + public float getInterpolatedValue(float fraction) { + return getValue(mDuration * fraction); } - @Override - public Float get(T t) { - return mCurrentTime; + private float getValue(float time) { + return (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue; } - public ObjectAnimator build(Context context) { - int singleFrameMs = DefaultDisplay.getSingleFrameMs(context); + public SpringAnimationBuilder computeParams() { + int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext); double naturalFreq = Math.sqrt(mStiffness); double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio); @@ -187,12 +180,21 @@ public class SpringAnimationBuilder extends FloatProperty { } } while (true); + mDuration = (float) duration; + return this; + } - long durationMs = (long) (1000.0 * duration); - ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration); - animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR); - animator.addListener(AnimationSuccessListener.forRunnable( - () -> mProperty.setValue(mTarget, mEndValue))); + public long getDuration() { + return (long) (1000.0 * mDuration); + } + + public ValueAnimator build(T target, FloatProperty property) { + computeParams(); + + ValueAnimator animator = ValueAnimator.ofFloat(0, mDuration); + animator.setDuration(getDuration()).setInterpolator(LINEAR); + animator.addUpdateListener(anim -> + property.set(target, getInterpolatedValue(anim.getAnimatedFraction()))); return animator; } From 99939d3b0dce9c26379b983df16ddfebb3cfce59 Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 22 Apr 2020 13:31:17 -0700 Subject: [PATCH 08/35] Removing tracing for a fixed bug Bug: 139891609 Change-Id: Ic7c988f06022b82685a3216154a47783ef2ec98b --- src/com/android/launcher3/model/PackageUpdatedTask.java | 5 ----- src/com/android/launcher3/testing/TestProtocol.java | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 0d975967c7..2fa605175d 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -42,7 +42,6 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.shortcuts.ShortcutRequest; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; @@ -86,10 +85,6 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " + - Arrays.toString(mPackages)); - } final Context context = app.getContext(); final IconCache iconCache = app.getIconCache(); diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 82f2eb49b3..a5a06b409c 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -96,6 +96,4 @@ public final class TestProtocol { public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation"; public static final String PERMANENT_DIAG_TAG = "TaplTarget"; - - public static final String APP_NOT_DISABLED = "b/139891609"; } From 71a091fe8a99dd930a6741af2ba287867af74865 Mon Sep 17 00:00:00 2001 From: Sreyas Date: Wed, 8 Apr 2020 16:45:15 -0700 Subject: [PATCH 09/35] Assistive chip: Adjusting chip UI Change-Id: I2970107f4324a5f01e0df4a6adf21875412582c6 --- .../res/values/dimens.xml | 1 + .../com/android/quickstep/views/TaskView.java | 76 ++++++++++++++++--- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml index 363840a217..9266b0652b 100644 --- a/quickstep/recents_ui_overrides/res/values/dimens.xml +++ b/quickstep/recents_ui_overrides/res/values/dimens.xml @@ -20,6 +20,7 @@ 10dp 12dp 20dp + 16dp 2dp 16dp 26dp diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index 470b720f5e..b92a7b1cfb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -175,11 +175,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { // Order in which the footers appear. Lower order appear below higher order. public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0; - public static final int INDEX_PROACTIVE_SUGGEST = 1; private final FooterWrapper[] mFooters = new FooterWrapper[2]; private float mFooterVerticalOffset = 0; private float mFooterAlpha = 1; private int mStackHeight; + private View mContextualChipWrapper; + private View mContextualChip; public TaskView(Context context) { this(context, null); @@ -262,8 +263,14 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { footer.animateHide(); } } + if (mContextualChip != null) { + mContextualChip.animate().scaleX(0f).scaleY(0f).setDuration(300); + } mIconView.animate().alpha(0.0f); } else { + if (mContextualChip != null) { + mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(300); + } mIconView.animate().alpha(1.0f); } @@ -667,6 +674,51 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { return oldFooter; } + /** + * Sets the contextual chip. + * + * @param view Wrapper view containing contextual chip. + */ + public void setContextualChip(View view) { + if (mContextualChipWrapper != null) { + removeView(mContextualChipWrapper); + } + if (view != null) { + mContextualChipWrapper = view; + LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL; + layoutParams.bottomMargin = (int) + (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin + - getExpectedViewHeight(view) + getResources().getDimension( + R.dimen.chip_hint_vertical_offset)); + mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0); + mContextualChip.setScaleX(0f); + mContextualChip.setScaleY(0f); + addView(view, getChildCount(), layoutParams); + view.setAlpha(mFooterAlpha); + if (mContextualChip != null) { + mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50); + } + } + + } + + /** + * Clears the contextual chip from TaskView. + * + * @return The contextual chip wrapper view to be recycled. + */ + public View clearContextualChip() { + if (mContextualChipWrapper != null) { + removeView(mContextualChipWrapper); + } + View oldContextualChipWrapper = mContextualChipWrapper; + mContextualChipWrapper = null; + mContextualChip = null; + return oldContextualChipWrapper; + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -767,14 +819,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mDelegate = mOldOutlineProvider == null ? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider; - int h = view.getLayoutParams().height; - if (h > 0) { - mExpectedHeight = h; - } else { - int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST); - view.measure(m, m); - mExpectedHeight = view.getMeasuredHeight(); - } + mExpectedHeight = getExpectedViewHeight(view); mOldPaddingBottom = view.getPaddingBottom(); if (mOldOutlineProvider != null) { @@ -836,6 +881,19 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } + private int getExpectedViewHeight(View view) { + int expectedHeight; + int h = view.getLayoutParams().height; + if (h > 0) { + expectedHeight = h; + } else { + int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST); + view.measure(m, m); + expectedHeight = view.getMeasuredHeight(); + } + return expectedHeight; + } + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); From e553e380d0dae577b690a355056cbaaacef2228b Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Wed, 15 Apr 2020 14:13:38 -0700 Subject: [PATCH 10/35] Rotate Overview Task Action Menu Fixes: 153371258 Test: Tap on icon in portrait, landscape and seascape. Open menu then rotate phone. Change-Id: I38b018371561b502fe211a3c2ddea4ff7fde4274 --- .../android/quickstep/views/TaskMenuView.java | 41 +++++++++++++++---- .../com/android/quickstep/views/TaskView.java | 23 ++++++++--- .../touch/LandscapePagedViewHandler.java | 29 +++++++++++++ .../touch/PagedOrientationHandler.java | 6 +++ .../touch/PortraitPagedViewHandler.java | 26 ++++++++++++ .../touch/SeascapePagedViewHandler.java | 11 +++++ 6 files changed, 124 insertions(+), 12 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java index 80022b4fe1..9b475205dd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java @@ -40,6 +40,7 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.TaskOverlayFactory; @@ -150,9 +151,26 @@ public class TaskMenuView extends AbstractFloatingView { return (type & TYPE_TASK_MENU) != 0; } - public void setPosition(float x, float y) { - setX(x); - setY(y + mThumbnailTopMargin); + public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) { + float adjustedY = y + mThumbnailTopMargin; + // Changing pivot to make computations easier + // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set, + // which would render the X and Y position set here incorrect + setPivotX(0); + setPivotY(0); + setRotation(pagedOrientationHandler.getDegreesRotated()); + setX(pagedOrientationHandler.getTaskMenuX(x, mTaskView.getThumbnail())); + setY(pagedOrientationHandler.getTaskMenuY(adjustedY, mTaskView.getThumbnail())); + } + + public void onRotationChanged() { + if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { + mOpenCloseAnimator.end(); + } + if (mIsOpen) { + mOptionLayout.removeAllViews(); + populateAndLayoutMenu(); + } } public static TaskMenuView showForTask(TaskView taskView) { @@ -168,12 +186,16 @@ public class TaskMenuView extends AbstractFloatingView { } mActivity.getDragLayer().addView(this); mTaskView = taskView; - addMenuOptions(mTaskView); - orientAroundTaskView(mTaskView); + populateAndLayoutMenu(); post(this::animateOpen); return true; } + private void populateAndLayoutMenu() { + addMenuOptions(mTaskView); + orientAroundTaskView(mTaskView); + } + private void addMenuOptions(TaskView taskView) { Drawable icon = taskView.getTask().icon.getConstantState().newDrawable(); mTaskIcon.setDrawable(icon); @@ -200,21 +222,26 @@ public class TaskMenuView extends AbstractFloatingView { R.layout.task_view_menu_option, this, false); menuOption.setIconAndLabelFor( menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); + LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams(); + mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp); menuOptionView.setOnClickListener(menuOption); mOptionLayout.addView(menuOptionView); } private void orientAroundTaskView(TaskView taskView) { + PagedOrientationHandler orientationHandler = taskView.getPagedOrientationHandler(); measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect); Rect insets = mActivity.getDragLayer().getInsets(); BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams(); - params.width = taskView.getMeasuredWidth(); + params.width = orientationHandler.getTaskMenuWidth(taskView.getThumbnail()); params.gravity = Gravity.START; setLayoutParams(params); setScaleX(taskView.getScaleX()); setScaleY(taskView.getScaleY()); - setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top); + mOptionLayout.setOrientation(orientationHandler.getTaskMenuLayoutOrientation()); + setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top, + taskView.getPagedOrientationHandler()); } private void animateOpen() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index b0758c9e48..ce8a193f00 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -466,6 +466,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { int iconRotation = orientationState.getTouchRotation(); PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler(); boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources()); + LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin); LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); int rotation = orientationState.getTouchRotationDegrees(); @@ -473,7 +474,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { case Surface.ROTATION_90: iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL; iconParams.rightMargin = -thumbnailPadding; - iconParams.leftMargin = iconParams.topMargin = iconParams.bottomMargin = 0; + iconParams.leftMargin = 0; + iconParams.topMargin = snapshotParams.topMargin / 2; break; case Surface.ROTATION_180: iconParams.gravity = BOTTOM | CENTER_HORIZONTAL; @@ -483,17 +485,21 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { case Surface.ROTATION_270: iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL; iconParams.leftMargin = -thumbnailPadding; - iconParams.rightMargin = iconParams.topMargin = iconParams.bottomMargin = 0; + iconParams.rightMargin = 0; + iconParams.topMargin = snapshotParams.topMargin / 2; break; case Surface.ROTATION_0: default: iconParams.gravity = TOP | CENTER_HORIZONTAL; - iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = - iconParams.bottomMargin = 0; + iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0; break; } mIconView.setLayoutParams(iconParams); mIconView.setRotation(rotation); + + if (mMenuView != null) { + mMenuView.onRotationChanged(); + } } private void setIconAndDimTransitionProgress(float progress, boolean invert) { @@ -600,7 +606,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } if (mMenuView != null) { - mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY()); + PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler(); + RecentsView recentsView = getRecentsView(); + mMenuView.setPosition(getX() - recentsView.getScrollX(), + getY() - recentsView.getScrollY(), pagedOrientationHandler); mMenuView.setScaleX(getScaleX()); mMenuView.setScaleY(getScaleY()); } @@ -874,6 +883,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { return (RecentsView) getParent(); } + PagedOrientationHandler getPagedOrientationHandler() { + return getRecentsView().mOrientationState.getOrientationHandler(); + } + public void notifyTaskLaunchFailed(String tag) { String msg = "Failed to launch task"; if (mTask != null) { diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index e290685d5e..dc500536d3 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -16,6 +16,7 @@ package com.android.launcher3.touch; +import static android.widget.ListPopupWindow.WRAP_CONTENT; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; @@ -30,6 +31,7 @@ import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.PagedView; @@ -223,6 +225,33 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { return 1; } + @Override + public float getTaskMenuX(float x, View thumbnailView) { + return thumbnailView.getMeasuredWidth() + x; + } + + @Override + public float getTaskMenuY(float y, View thumbnailView) { + return y; + } + + @Override + public int getTaskMenuWidth(View view) { + return view.getMeasuredHeight(); + } + + @Override + public int getTaskMenuLayoutOrientation() { + return LinearLayout.HORIZONTAL; + } + + @Override + public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp) { + lp.width = 0; + lp.height = WRAP_CONTENT; + lp.weight = 1; + } + @Override public ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild) { diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index b8396e1119..cc15f99127 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -27,6 +27,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.PagedView; @@ -90,6 +91,11 @@ public interface PagedOrientationHandler { void getCurveProperties(PagedView view, Rect insets, CurveProperties out); boolean isGoingUp(float displacement); boolean isLayoutNaturalToLauncher(); + float getTaskMenuX(float x, View thumbnailView); + float getTaskMenuY(float y, View thumbnailView); + int getTaskMenuWidth(View view); + int getTaskMenuLayoutOrientation(); + void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp); /** * Maps the velocity from the coordinate plane of the foreground app to that diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index dad00a4b5b..7c30e29aa4 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -30,6 +30,7 @@ import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.PagedView; @@ -221,6 +222,31 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { return -1; } + @Override + public float getTaskMenuX(float x, View thumbnailView) { + return x; + } + + @Override + public float getTaskMenuY(float y, View thumbnailView) { + return y; + } + + @Override + public int getTaskMenuWidth(View view) { + return view.getMeasuredWidth(); + } + + @Override + public int getTaskMenuLayoutOrientation() { + return LinearLayout.VERTICAL; + } + + @Override + public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp) { + // no-op, defaults are fine + } + @Override public ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild) { diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java index 5ce8a5763e..7beb7f7466 100644 --- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java @@ -20,6 +20,7 @@ import android.content.res.Resources; import android.graphics.PointF; import android.graphics.RectF; import android.view.Surface; +import android.view.View; import com.android.launcher3.Utilities; @@ -64,4 +65,14 @@ public class SeascapePagedViewHandler extends LandscapePagedViewHandler { float oldY = velocity.y; velocity.set(oldY, -oldX); } + + @Override + public float getTaskMenuX(float x, View thumbnailView) { + return x; + } + + @Override + public float getTaskMenuY(float y, View thumbnailView) { + return y + thumbnailView.getMeasuredHeight(); + } } From de4815bdad35ab5ffd3432f93990d60b5204204c Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 22 Apr 2020 12:49:22 -0700 Subject: [PATCH 11/35] Fixing recentsView getting translated in NavBarToHomeTouchController Bug: 154657409 Change-Id: I4d7f3cac52b563370d612dd29d5e7322645b5cbe --- .../launcher3/LauncherAppTransitionManagerImpl.java | 9 +++++---- .../touchcontrollers/NavBarToHomeTouchController.java | 10 +++------- .../src/com/android/quickstep/views/RecentsView.java | 7 +++++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 8cf5da2144..79b4002498 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -197,14 +197,15 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti case INDEX_RECENTS_FADE_ANIM: return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(), RecentsView.CONTENT_ALPHA, values); - case INDEX_RECENTS_TRANSLATE_X_ANIM: - // TODO: Do not assume motion across X axis for adjacent page + case INDEX_RECENTS_TRANSLATE_X_ANIM: { + RecentsView rv = mLauncher.getOverviewPanel(); return new SpringAnimationBuilder(mLauncher) - .setMinimumVisibleChange(1f / mLauncher.getOverviewPanel().getWidth()) + .setMinimumVisibleChange(1f / rv.getPageOffsetScale()) .setDampingRatio(0.8f) .setStiffness(250) .setValues(values) - .build(mLauncher.getOverviewPanel(), ADJACENT_PAGE_OFFSET); + .build(rv, ADJACENT_PAGE_OFFSET); + } case INDEX_PAUSE_TO_OVERVIEW_ANIM: { StateAnimationConfig config = new StateAnimationConfig(); config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 77118d58af..2b456ecec8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.uioverrides.touchcontrollers; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; @@ -23,6 +22,7 @@ import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; +import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.ValueAnimator; @@ -131,12 +131,8 @@ public class NavBarToHomeTouchController implements TouchController, final PendingAnimation builder = new PendingAnimation(accuracy); if (mStartState == OVERVIEW) { RecentsView recentsView = mLauncher.getOverviewPanel(); - float pullbackDist = mPullbackDistance; - if (!recentsView.isRtl()) { - pullbackDist = -pullbackDist; - } - - builder.setFloat(recentsView, VIEW_TRANSLATE_X, pullbackDist, PULLBACK_INTERPOLATOR); + builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET, + -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { builder.addOnFrameCallback( () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */)); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 90dc5a46dd..3b6fd13804 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -1670,6 +1670,13 @@ public abstract class RecentsView extends PagedView impl updateCurveProperties(); } + /** + * TODO: Do not assume motion across X axis for adjacent page + */ + public float getPageOffsetScale() { + return Math.max(getWidth(), 1); + } + private void updateDeadZoneRects() { // Get the deadzone rect surrounding the clear all button to not dismiss overview to home mClearAllButtonDeadZoneRect.setEmpty(); From 72c53af896d9d3b572d93a5722d85c6913fd9455 Mon Sep 17 00:00:00 2001 From: Becky Qiu Date: Wed, 22 Apr 2020 15:53:38 -0700 Subject: [PATCH 12/35] [Overview Actions] Update the navigation mode immediately when switch. This would fix the issue that overview actions UI visibility was not set correctly. Test: local Bug: 152047880 Change-Id: Ia335c5c8947b39e580403090f461f87e78645af1 --- .../android/launcher3/BaseQuickstepLauncher.java | 2 ++ .../android/quickstep/SysUINavigationMode.java | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 65763d45ef..af63a25efd 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -161,6 +161,8 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override protected void setupViews() { super.setupViews(); + + SysUINavigationMode.INSTANCE.get(this).updateMode(); mActionsView = findViewById(R.id.overview_actions_view); ((RecentsView) getOverviewPanel()).init(mActionsView); diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java index 6a10b37e35..c715c934dd 100644 --- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java +++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java @@ -74,15 +74,20 @@ public class SysUINavigationMode { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Mode oldMode = mMode; - initializeMode(); - if (mMode != oldMode) { - dispatchModeChange(); - } + updateMode(); } }, getPackageFilter("android", ACTION_OVERLAY_CHANGED)); } + /** Updates navigation mode when needed. */ + public void updateMode() { + Mode oldMode = mMode; + initializeMode(); + if (mMode != oldMode) { + dispatchModeChange(); + } + } + private void initializeMode() { int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME); for(Mode m : Mode.values()) { From 22757e840c7f6e7bad4df782c5f0bcf5cc811e20 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 22 Apr 2020 17:49:09 -0500 Subject: [PATCH 13/35] Don't allow swiping from overview to all apps in 3 button mode Bug: 154761926 Change-Id: I155faec27f3b96e2f11aaec318d6846a893885e6 --- .../touchcontrollers/PortraitStatesTouchController.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index cc3fd97b49..e5c9fc96ad 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -22,10 +22,12 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; +import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.TimeInterpolator; @@ -132,7 +134,12 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr return TouchInteractionService.isConnected() ? mLauncher.getStateManager().getLastState() : NORMAL; } else if (fromState == OVERVIEW) { - return isDragTowardPositive ? ALL_APPS : NORMAL; + LauncherState positiveDragTarget = ALL_APPS; + if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) { + // Don't allow swiping up to all apps. + positiveDragTarget = OVERVIEW; + } + return isDragTowardPositive ? positiveDragTarget : NORMAL; } else if (fromState == NORMAL && isDragTowardPositive) { int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags(); return mAllowDragToOverview && TouchInteractionService.isConnected() From 6a6301c569b58d49d731f3bd4e6f30595084ad4e Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 22 Apr 2020 18:42:40 -0500 Subject: [PATCH 14/35] Always return RecentsView to translation 0 on drag end After reaching overview, we let the user translate RecentsView if they continue dragging around. But when they let go, we need to return RecentsView to translation 0 since that's no longer part of the state machine. Change-Id: I30b51485339a3b6c3dd52bda113b1a05b6e885fa --- ...ButtonNavbarToOverviewTouchController.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 71aa2e8d99..381ecf1c4b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -166,8 +166,8 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo float velocityDp = dpiFromPx(velocity); boolean isFling = Math.abs(velocityDp) > 1; LauncherStateManager stateManager = mLauncher.getStateManager(); - if (isFling) { - // When flinging, go back to home instead of overview. + boolean goToHomeInsteadOfOverview = isFling; + if (goToHomeInsteadOfOverview) { if (velocity > 0) { stateManager.goToState(NORMAL, true, () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING)); @@ -187,20 +187,21 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE))); anim.start(); } - } else { - if (mReachedOverview) { - float distanceDp = dpiFromPx(Math.max( - Math.abs(mRecentsView.getTranslationX()), - Math.abs(mRecentsView.getTranslationY()))); - long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS, - distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS); - mRecentsView.animate() - .translationX(0) - .translationY(0) - .setInterpolator(ACCEL_DEACCEL) - .setDuration(duration) - .withEndAction(this::maybeSwipeInteractionToOverviewComplete); - } + } + if (mReachedOverview) { + float distanceDp = dpiFromPx(Math.max( + Math.abs(mRecentsView.getTranslationX()), + Math.abs(mRecentsView.getTranslationY()))); + long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS, + distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS); + mRecentsView.animate() + .translationX(0) + .translationY(0) + .setInterpolator(ACCEL_DEACCEL) + .setDuration(duration) + .withEndAction(goToHomeInsteadOfOverview + ? null + : this::maybeSwipeInteractionToOverviewComplete); } } From ed5f3082b0c9378fed39232d29a50c19e9d0103b Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Mon, 20 Apr 2020 01:13:26 -0700 Subject: [PATCH 15/35] Special handling when a db for one grid option is not setup yet - Init KEY_MIGRATION_SRC_WORKSPACE_SIZE and KEY_MIGRATION_SRC_HOTSEAT_COUNT - Load default workspace only when default db is created, not when peeking into dbs of other grid options during grid preview / migration Fixes: 154184711 Test: run grid preview and migration right after a cleared cache Pixel Launcher Change-Id: I86c7072b8c4a9da76e289c55ab440071f192fc38 --- .../model/DbDowngradeHelperTest.java | 4 +-- .../launcher3/provider/RestoreDbTaskTest.java | 2 +- .../launcher3/InvariantDeviceProfile.java | 8 ++++++ .../android/launcher3/LauncherProvider.java | 26 ++++++++++++------- .../model/GridSizeMigrationTask.java | 8 +++--- .../model/GridSizeMigrationTaskV2.java | 16 +++--------- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java index b7340cf7d0..bbbe21e3f2 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java @@ -130,7 +130,7 @@ public class DbDowngradeHelperTest { } helper.close(); - helper = new DatabaseHelper(mContext, DB_FILE) { + helper = new DatabaseHelper(mContext, DB_FILE, false) { @Override public void onOpen(SQLiteDatabase db) { } }; @@ -161,7 +161,7 @@ public class DbDowngradeHelperTest { DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext); - DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE) { + DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) { @Override public void onOpen(SQLiteDatabase db) { } }; diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index 58174c793c..ee73b82bb8 100644 --- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -95,7 +95,7 @@ public class RestoreDbTaskTest { private final long mProfileId; MyDatabaseHelper(long profileId) { - super(RuntimeEnvironment.application, null); + super(RuntimeEnvironment.application, null, false); mProfileId = profileId; } diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 0a1fad1d7d..63b90ae4f8 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.Utilities.getDevicePrefs; +import static com.android.launcher3.Utilities.getPointString; import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME; import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -69,6 +70,9 @@ public class InvariantDeviceProfile { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(InvariantDeviceProfile::new); + public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size"; + public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count"; + private static final String KEY_IDP_GRID_NAME = "idp_grid_name"; private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48; @@ -165,6 +169,10 @@ public class InvariantDeviceProfile { if (!newGridName.equals(gridName)) { Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply(); } + Utilities.getPrefs(context).edit() + .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, numHotseatIcons) + .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(numColumns, numRows)) + .apply(); mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 8d20bd64df..308d84f77c 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -146,7 +146,8 @@ public class LauncherProvider extends ContentProvider { */ protected synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext()); + mOpenHelper = DatabaseHelper.createDatabaseHelper( + getContext(), false /* forMigration */); if (RestoreDbTask.isPending(getContext())) { if (!RestoreDbTask.performRestore(getContext(), mOpenHelper, @@ -430,7 +431,8 @@ public class LauncherProvider extends ContentProvider { InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile, Favorites.TMP_TABLE, () -> mOpenHelper, - () -> DatabaseHelper.createDatabaseHelper(getContext()))); + () -> DatabaseHelper.createDatabaseHelper( + getContext(), true /* forMigration */))); return result; } } @@ -441,7 +443,8 @@ public class LauncherProvider extends ContentProvider { prepForMigration( arg /* dbFile */, Favorites.PREVIEW_TABLE_NAME, - () -> DatabaseHelper.createDatabaseHelper(getContext(), arg), + () -> DatabaseHelper.createDatabaseHelper( + getContext(), arg, true /* forMigration */), () -> mOpenHelper)); return result; } @@ -609,20 +612,22 @@ public class LauncherProvider extends ContentProvider { public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback { private final Context mContext; + private final boolean mForMigration; private int mMaxItemId = -1; private int mMaxScreenId = -1; private boolean mBackupTableExists; - static DatabaseHelper createDatabaseHelper(Context context) { - return createDatabaseHelper(context, null); + static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) { + return createDatabaseHelper(context, null, forMigration); } - static DatabaseHelper createDatabaseHelper(Context context, String dbName) { + static DatabaseHelper createDatabaseHelper(Context context, String dbName, + boolean forMigration) { if (dbName == null) { dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get( context).dbFile : LauncherFiles.LAUNCHER_DB; } - DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName); + DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration); // Table creation sometimes fails silently, which leads to a crash loop. // This way, we will try to create a table every time after crash, so the device // would eventually be able to recover. @@ -643,9 +648,10 @@ public class LauncherProvider extends ContentProvider { /** * Constructor used in tests and for restore. */ - public DatabaseHelper(Context context, String dbName) { + public DatabaseHelper(Context context, String dbName, boolean forMigration) { super(context, dbName, SCHEMA_VERSION); mContext = context; + mForMigration = forMigration; } protected void initIds() { @@ -670,7 +676,9 @@ public class LauncherProvider extends ContentProvider { // Fresh and clean launcher DB. mMaxItemId = initializeMaxItemId(db); - onEmptyDbCreated(); + if (!mForMigration) { + onEmptyDbCreated(); + } } protected void onAddOrDeleteOp(SQLiteDatabase db) { diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index b27e4ea72e..e8a52bdd54 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -1,5 +1,7 @@ package com.android.launcher3.model; +import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT; +import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE; import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE; import static com.android.launcher3.Utilities.getPointString; import static com.android.launcher3.Utilities.parsePoint; @@ -53,9 +55,6 @@ public class GridSizeMigrationTask { private static final String TAG = "GridSizeMigrationTask"; private static final boolean DEBUG = true; - private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size"; - private static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count"; - // These are carefully selected weights for various item types (Math.random?), to allow for // the least absurd migration experience. private static final float WT_SHORTCUT = 1; @@ -894,8 +893,7 @@ public class GridSizeMigrationTask { String gridSizeString = getPointString(idp.numColumns, idp.numRows); return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) - || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, - idp.numHotseatIcons); + || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1); } /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */ diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java index 1c44fc381e..4a28218246 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; +import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT; +import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE; import static com.android.launcher3.Utilities.getPointString; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; @@ -63,9 +65,6 @@ import java.util.stream.Collectors; */ public class GridSizeMigrationTaskV2 { - public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size"; - public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count"; - private static final String TAG = "GridSizeMigrationTaskV2"; private static final boolean DEBUG = true; @@ -110,8 +109,7 @@ public class GridSizeMigrationTaskV2 { String gridSizeString = getPointString(idp.numColumns, idp.numRows); return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) - || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, - idp.numHotseatIcons); + || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1); } /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */ @@ -148,14 +146,6 @@ public class GridSizeMigrationTaskV2 { SharedPreferences prefs = Utilities.getPrefs(context); String gridSizeString = getPointString(idp.numColumns, idp.numRows); - - if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) - && idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, - idp.numHotseatIcons)) { - // Skip if workspace and hotseat sizes have not changed. - return true; - } - HashSet validPackages = getValidPackages(context); int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons); From b3e8ae823098886206c37f4677877235e150b48a Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 17 Apr 2020 15:50:52 -0700 Subject: [PATCH 16/35] Makeshift analog of Strictmode leak detector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strictmode leak detector is still a goal, but we might not be able to achieve it in R. Strictmode has several framework-side bugs that perhaps hide Launcher-side strictmode violations, while the time to fix everything is limited, and new leaks get introduced all the time. For now, implementing a check that is slightly more relaxed than Strictmode, but still ensures the absence of leaks. I’ll keep eliminating Strictmode violations as well as keep strengthening the makeshift checker conditions until we’ll be able to enable Strictmode in continuous testing. I’m disabling Strictmode checks for now so that they don’t generate unnecessary hprof dumps, but leaving the code dealing with strictmode. Bug: 139137636 Change-Id: Ib10136b0d4e9892f70a19cd052ae5a54cf0a4efb --- .../quickstep/NavigationModeSwitchRule.java | 2 +- .../launcher3/ui/AbstractLauncherUiTest.java | 56 +++++++++---- .../launcher3/ui/ActivityLeakTracker.java | 83 +++++++++++++++++++ .../launcher3/ui/PortraitLandscapeRunner.java | 4 +- .../launcher3/ui/TaplTestsLauncher3.java | 2 +- 5 files changed, 126 insertions(+), 21 deletions(-) create mode 100644 tests/src/com/android/launcher3/ui/ActivityLeakTracker.java diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 0afe4a8c88..40265c47e2 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -203,7 +203,7 @@ public class NavigationModeSwitchRule implements TestRule { + launcher.getNavigationModeMismatchError(), () -> launcher.getNavigationModeMismatchError() == null, 60000 /* b/148422894 */, launcher); - AbstractLauncherUiTest.checkDetectedLeaks(); + AbstractLauncherUiTest.checkDetectedLeaks(launcher); return true; } diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 86faddb41d..b8c650c671 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -100,9 +100,10 @@ public abstract class AbstractLauncherUiTest { public static final long DEFAULT_UI_TIMEOUT = 10000; private static final String TAG = "AbstractLauncherUiTest"; - private static String sDetectedActivityLeak; + private static String sStrictmodeDetectedActivityLeak; private static boolean sActivityLeakReported; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + private static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker(); protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); @@ -115,30 +116,51 @@ public abstract class AbstractLauncherUiTest { if (TestHelpers.isInLauncherProcess()) { StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder() - .detectActivityLeaks() +// b/154772063 +// .detectActivityLeaks() .penaltyLog() .penaltyListener(Runnable::run, violation -> { - // Runs in the main thread. We can't dumpheap in the main thread, - // so let's just mark the fact that the leak has happened. - if (sDetectedActivityLeak == null) { - sDetectedActivityLeak = violation.toString(); - try { - Debug.dumpHprofData( - getInstrumentation().getTargetContext() - .getFilesDir().getPath() - + "/ActivityLeakHeapDump.hprof"); - } catch (Throwable e) { - Log.e(TAG, "dumpHprofData failed", e); - } + if (sStrictmodeDetectedActivityLeak == null) { + sStrictmodeDetectedActivityLeak = violation.toString() + ", " + + dumpHprofData() + "."; } }); StrictMode.setVmPolicy(builder.build()); } } - public static void checkDetectedLeaks() { - if (sDetectedActivityLeak != null && !sActivityLeakReported) { + public static void checkDetectedLeaks(LauncherInstrumentation launcher) { + if (sActivityLeakReported) return; + + if (sStrictmodeDetectedActivityLeak != null) { + // Report from the test thread strictmode violations detected in the main thread. sActivityLeakReported = true; + Assert.fail(sStrictmodeDetectedActivityLeak); + } + + // Check whether activity leak detector has found leaked activities. + Wait.atMost(AbstractLauncherUiTest::getActivityLeakErrorMessage, + () -> { + launcher.getTotalPssKb(); // Triggers GC + return MAIN_EXECUTOR.submit( + () -> ACTIVITY_LEAK_TRACKER.noLeakedActivities()).get(); + }, DEFAULT_UI_TIMEOUT, launcher); + } + + private static String getActivityLeakErrorMessage() { + sActivityLeakReported = true; + return "Activity leak detector has found leaked activities, " + dumpHprofData() + "."; + } + + private static String dumpHprofData() { + try { + final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath() + + "/ActivityLeakHeapDump.hprof"; + Debug.dumpHprofData(fileName); + return "memory dump filename: " + fileName; + } catch (Throwable e) { + Log.e(TAG, "dumpHprofData failed", e); + return "failed to save memory dump"; } } @@ -265,7 +287,7 @@ public abstract class AbstractLauncherUiTest { if (mLauncherPid != 0) { assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid()); } - checkDetectedLeaks(); + checkDetectedLeaks(mLauncher); } protected void clearLauncherData() throws IOException, InterruptedException { diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java new file mode 100644 index 0000000000..0b5ce02c5c --- /dev/null +++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 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.ui; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; + +import androidx.test.InstrumentationRegistry; + +import com.android.launcher3.tapl.TestHelpers; + +import java.util.WeakHashMap; + +class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks { + private final WeakHashMap mActivities = new WeakHashMap<>(); + + ActivityLeakTracker() { + if (!TestHelpers.isInLauncherProcess()) return; + final Application app = + (Application) InstrumentationRegistry.getTargetContext().getApplicationContext(); + app.registerActivityLifecycleCallbacks(this); + } + + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { + mActivities.put(activity, true); + } + + @Override + public void onActivityStarted(Activity activity) { + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + } + + public boolean noLeakedActivities() { + int liveActivities = 0; + int destroyedActivities = 0; + + for (Activity activity : mActivities.keySet()) { + if (activity.isDestroyed()) { + ++destroyedActivities; + } else { + ++liveActivities; + } + } + + // It's OK to have 1 leaked activity if no active activities exist. + return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0; + } +} diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java index 38f50c1ef2..266f0aeb1d 100644 --- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java +++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java @@ -56,7 +56,7 @@ class PortraitLandscapeRunner implements TestRule { private void evaluateInPortrait() throws Throwable { mTest.mDevice.setOrientationNatural(); mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); - AbstractLauncherUiTest.checkDetectedLeaks(); + AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher); base.evaluate(); mTest.getDevice().pressHome(); } @@ -64,7 +64,7 @@ class PortraitLandscapeRunner implements TestRule { private void evaluateInLandscape() throws Throwable { mTest.mDevice.setOrientationLeft(); mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90); - AbstractLauncherUiTest.checkDetectedLeaks(); + AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher); base.evaluate(); mTest.getDevice().pressHome(); } diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 57000a06c3..34e425d5fc 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -64,7 +64,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { test.waitForResumed("Launcher internal state is still Background"); // Check that we switched to home. test.mLauncher.getWorkspace(); - AbstractLauncherUiTest.checkDetectedLeaks(); + AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher); } // Please don't add negative test cases for methods that fail only after a long wait. From 219426f870e2e38e3b124619c7f1b34cc4b0b614 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Wed, 15 Apr 2020 14:46:03 -0700 Subject: [PATCH 17/35] Turn on V2 migration algorithm feature flag This flag is behind grid options master flag, so it won't go to all dogfood users yet. I will turn on grid options flag after enough bug bash, hopefully at the end of this week. Bug: 154154093 Test: N/A Change-Id: I608af1cd3c5613f7cc33a940a145309ac90a6c00 --- src/com/android/launcher3/config/FeatureFlags.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 65d3cd2ded..df30f7bc77 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -127,7 +127,7 @@ public final class FeatureFlags { "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache"); public static final BooleanFlag MULTI_DB_GRID_MIRATION_ALGO = getDebugFlag( - "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm"); + "MULTI_DB_GRID_MIRATION_ALGO", true, "Use the multi-db grid migration algorithm"); public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag( "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker"); From 26c4e23e9a646243b4930820360f932fae5c9fa9 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Wed, 15 Apr 2020 22:25:06 -0700 Subject: [PATCH 18/35] fix GridBackupTableTest in comply with grid size migration Bug: 154166808 Test: GridBackupTableTest Change-Id: I7af35ceed7edb77ac0d9c443d7eccbcef19ae476 --- src/com/android/launcher3/LauncherProvider.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 8d20bd64df..ac3a5b0ca8 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -406,12 +406,8 @@ public class LauncherProvider extends ContentProvider { return result; } case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: { - // TODO(pinyaoting): Update the behavior here. - if (!MULTI_DB_GRID_MIRATION_ALGO.get()) { - mOpenHelper.mBackupTableExists = - tableExists(mOpenHelper.getReadableDatabase(), - Favorites.BACKUP_TABLE_NAME); - } + mOpenHelper.mBackupTableExists = tableExists(mOpenHelper.getReadableDatabase(), + Favorites.BACKUP_TABLE_NAME); return null; } case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: { @@ -451,11 +447,7 @@ public class LauncherProvider extends ContentProvider { } private void onAddOrDeleteOp(SQLiteDatabase db) { - if (MULTI_DB_GRID_MIRATION_ALGO.get()) { - // TODO(pingyaoting): Implement the behavior here. - } else { - mOpenHelper.onAddOrDeleteOp(db); - } + mOpenHelper.onAddOrDeleteOp(db); } /** @@ -674,7 +666,7 @@ public class LauncherProvider extends ContentProvider { } protected void onAddOrDeleteOp(SQLiteDatabase db) { - if (!MULTI_DB_GRID_MIRATION_ALGO.get() && mBackupTableExists) { + if (mBackupTableExists) { dropTable(db, Favorites.BACKUP_TABLE_NAME); mBackupTableExists = false; } From 07034645565528c0d621612c4cb764783bb695a0 Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 22 Apr 2020 18:44:05 -0700 Subject: [PATCH 19/35] Checking that there are no more than 2 live activities at any moment This is tightening the makeshift strictmode criteria. Starting with this moment, we will know that there is no memory growth during tests execution, which is a big deal. Big: 139137636 Change-Id: I5edc84524463bd1736d727496ad0fc031bb9624c --- tests/src/com/android/launcher3/ui/ActivityLeakTracker.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java index 0b5ce02c5c..e9258e94d2 100644 --- a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java +++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java @@ -77,6 +77,8 @@ class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks { } } + if (liveActivities > 2) return false; + // It's OK to have 1 leaked activity if no active activities exist. return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0; } From 1d13c0bf13e77084051d44928693016b87f6ab90 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Tue, 21 Apr 2020 16:29:12 -0700 Subject: [PATCH 20/35] Cleanup reorder animations to not require layout on every frame => Extending on previous CL to separate multiple translation properties and avoiding expensive layout pass Test: manual Change-Id: I058da7367fb320b9f432bef9482be2966d3a7d42 --- src/com/android/launcher3/BubbleTextView.java | 41 +++++++---- src/com/android/launcher3/CellLayout.java | 41 +++++++---- src/com/android/launcher3/Reorderable.java | 17 +++-- .../android/launcher3/folder/FolderIcon.java | 41 +++++++---- .../widget/NavigableAppWidgetHostView.java | 71 ++++++++++++------- 5 files changed, 142 insertions(+), 69 deletions(-) diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index e8e88c45e1..d7d4a27aea 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -79,8 +79,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed}; - private final PointF mTranslationForReorder = new PointF(0, 0); - private float mScaleForReorder = 1f; + private final PointF mTranslationForReorderBounce = new PointF(0, 0); + private final PointF mTranslationForReorderPreview = new PointF(0, 0); + + private float mScaleForReorderBounce = 1f; private static final Property DOT_SCALE_PROPERTY = new Property(Float.TYPE, "dotScale") { @@ -675,24 +677,39 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, return mIconSize; } - public void setReorderOffset(float x, float y) { - mTranslationForReorder.set(x, y); - super.setTranslationX(x); - super.setTranslationY(y); + private void updateTranslation() { + super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x); + super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y); } - public void getReorderOffset(PointF offset) { - offset.set(mTranslationForReorder); + public void setReorderBounceOffset(float x, float y) { + mTranslationForReorderBounce.set(x, y); + updateTranslation(); } - public void setReorderScale(float scale) { - mScaleForReorder = scale; + public void getReorderBounceOffset(PointF offset) { + offset.set(mTranslationForReorderBounce); + } + + @Override + public void setReorderPreviewOffset(float x, float y) { + mTranslationForReorderPreview.set(x, y); + updateTranslation(); + } + + @Override + public void getReorderPreviewOffset(PointF offset) { + offset.set(mTranslationForReorderPreview); + } + + public void setReorderBounceScale(float scale) { + mScaleForReorderBounce = scale; super.setScaleX(scale); super.setScaleY(scale); } - public float getReorderScale() { - return mScaleForReorder; + public float getReorderBounceScale() { + return mScaleForReorderBounce; } public View getView() { diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 71a787f083..ed71ddc3b7 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -852,9 +852,10 @@ public class CellLayout extends ViewGroup { int delay, boolean permanent, boolean adjustOccupied) { ShortcutAndWidgetContainer clc = getShortcutsAndWidgets(); - if (clc.indexOfChild(child) != -1) { + if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final ItemInfo info = (ItemInfo) child.getTag(); + final Reorderable item = (Reorderable) child; // We cancel any existing animations if (mReorderAnimators.containsKey(lp)) { @@ -862,13 +863,18 @@ public class CellLayout extends ViewGroup { mReorderAnimators.remove(lp); } - final int oldX = lp.x; - final int oldY = lp.y; + if (adjustOccupied) { GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied; occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false); occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true); } + + // Compute the new x and y position based on the new cellX and cellY + // We leverage the actual layout logic in the layout params and hence need to modify + // state and revert that state. + final int oldX = lp.x; + final int oldY = lp.y; lp.isLockedToGrid = true; if (permanent) { lp.cellX = info.cellX = cellX; @@ -878,15 +884,23 @@ public class CellLayout extends ViewGroup { lp.tmpCellY = cellY; } clc.setupLp(child); - lp.isLockedToGrid = false; final int newX = lp.x; final int newY = lp.y; - lp.x = oldX; lp.y = oldY; + lp.isLockedToGrid = false; + // End compute new x and y + + item.getReorderPreviewOffset(mTmpPointF); + final float initPreviewOffsetX = mTmpPointF.x; + final float initPreviewOffsetY = mTmpPointF.y; + final float finalPreviewOffsetX = newX - oldX; + final float finalPreviewOffsetY = newY - oldY; + // Exit early if we're not actually moving the view - if (oldX == newX && oldY == newY) { + if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0 + && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) { lp.isLockedToGrid = true; return true; } @@ -899,9 +913,9 @@ public class CellLayout extends ViewGroup { @Override public void onAnimationUpdate(ValueAnimator animation) { float r = (Float) animation.getAnimatedValue(); - lp.x = (int) ((1 - r) * oldX + r * newX); - lp.y = (int) ((1 - r) * oldY + r * newY); - child.requestLayout(); + float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX; + float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY; + item.setReorderPreviewOffset(x, y); } }); va.addListener(new AnimatorListenerAdapter() { @@ -912,6 +926,7 @@ public class CellLayout extends ViewGroup { // place just yet. if (!cancelled) { lp.isLockedToGrid = true; + item.setReorderPreviewOffset(0, 0); child.requestLayout(); } if (mReorderAnimators.containsKey(lp)) { @@ -1930,10 +1945,10 @@ public class CellLayout extends ViewGroup { finalDeltaX = 0; finalDeltaY = 0; - child.getReorderOffset(mTmpPointF); + child.getReorderBounceOffset(mTmpPointF); initDeltaX = mTmpPointF.x; initDeltaY = mTmpPointF.y; - initScale = child.getReorderScale(); + initScale = child.getReorderBounceScale(); finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale; int dir = mode == MODE_HINT ? -1 : 1; @@ -2010,9 +2025,9 @@ public class CellLayout extends ViewGroup { float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress; float x = r1 * finalDeltaX + (1 - r1) * initDeltaX; float y = r1 * finalDeltaY + (1 - r1) * initDeltaY; - child.setReorderOffset(x, y); + child.setReorderBounceOffset(x, y); float s = animationProgress * finalScale + (1 - animationProgress) * initScale; - child.setReorderScale(s); + child.setReorderBounceScale(s); } private void cancel() { diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java index 5112eafdac..047fb013ff 100644 --- a/src/com/android/launcher3/Reorderable.java +++ b/src/com/android/launcher3/Reorderable.java @@ -22,17 +22,24 @@ import android.view.View; public interface Reorderable { /** - * Set the offset related to reorder hint and "bounce" animations + * Set the offset related to reorder hint and bounce animations */ - void setReorderOffset(float x, float y); + void setReorderBounceOffset(float x, float y); - void getReorderOffset(PointF offset); + void getReorderBounceOffset(PointF offset); + + /** + * Set the offset related to previewing the new reordered position + */ + void setReorderPreviewOffset(float x, float y); + + void getReorderPreviewOffset(PointF offset); /** * Set the scale related to reorder hint and "bounce" animations */ - void setReorderScale(float scale); - float getReorderScale(); + void setReorderBounceScale(float scale); + float getReorderBounceScale(); /** * Get the com.android.view related to this object diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 7fc6d5489c..b875a0b4cc 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -121,8 +121,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private float mDotScale; private Animator mDotScaleAnim; - private final PointF mTranslationForReorder = new PointF(0, 0); - private float mScaleForReorder = 1f; + private final PointF mTranslationForReorderBounce = new PointF(0, 0); + private final PointF mTranslationForReorderPreview = new PointF(0, 0); + private float mScaleForReorderBounce = 1f; private static final Property DOT_SCALE_PROPERTY = new Property(Float.TYPE, "dotScale") { @@ -712,25 +713,39 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mPreviewItemManager.onFolderClose(currentPage); } - - public void setReorderOffset(float x, float y) { - mTranslationForReorder.set(x, y); - super.setTranslationX(x); - super.setTranslationY(y); + private void updateTranslation() { + super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x); + super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y); } - public void getReorderOffset(PointF offset) { - offset.set(mTranslationForReorder); + public void setReorderBounceOffset(float x, float y) { + mTranslationForReorderBounce.set(x, y); + updateTranslation(); } - public void setReorderScale(float scale) { - mScaleForReorder = scale; + public void getReorderBounceOffset(PointF offset) { + offset.set(mTranslationForReorderBounce); + } + + @Override + public void setReorderPreviewOffset(float x, float y) { + mTranslationForReorderPreview.set(x, y); + updateTranslation(); + } + + @Override + public void getReorderPreviewOffset(PointF offset) { + offset.set(mTranslationForReorderPreview); + } + + public void setReorderBounceScale(float scale) { + mScaleForReorderBounce = scale; super.setScaleX(scale); super.setScaleY(scale); } - public float getReorderScale() { - return mScaleForReorder; + public float getReorderBounceScale() { + return mScaleForReorderBounce; } public View getView() { diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java index 5ea01d9900..a4e7daa15b 100644 --- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java @@ -46,8 +46,9 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView */ private final PointF mTranslationForCentering = new PointF(0, 0); - private final PointF mTranslationForReorder = new PointF(0, 0); - private float mScaleForReorder = 1f; + private final PointF mTranslationForReorderBounce = new PointF(0, 0); + private final PointF mTranslationForReorderPreview = new PointF(0, 0); + private float mScaleForReorderBounce = 1f; @ViewDebug.ExportedProperty(category = "launcher") private boolean mChildrenFocused; @@ -152,45 +153,63 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView setSelected(childIsFocused); } - public void setScaleToFit(float scale) { - mScaleToFit = scale; - super.setScaleX(scale * mScaleForReorder); - super.setScaleY(scale * mScaleForReorder); - } - - public float getScaleToFit() { - return mScaleToFit; - } - public View getView() { return this; } + private void updateTranslation() { + super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x + + mTranslationForCentering.x); + super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y + + mTranslationForCentering.y); + } public void setTranslationForCentering(float x, float y) { mTranslationForCentering.set(x, y); - super.setTranslationX(x + mTranslationForReorder.x); - super.setTranslationY(y + mTranslationForReorder.y); + updateTranslation(); } - public void setReorderOffset(float x, float y) { - mTranslationForReorder.set(x, y); - super.setTranslationX(mTranslationForCentering.x + x); - super.setTranslationY(mTranslationForCentering.y + y); + public void setReorderBounceOffset(float x, float y) { + mTranslationForReorderBounce.set(x, y); + updateTranslation(); } - public void getReorderOffset(PointF offset) { - offset.set(mTranslationForReorder); + public void getReorderBounceOffset(PointF offset) { + offset.set(mTranslationForReorderBounce); } - public void setReorderScale(float scale) { - mScaleForReorder = scale; - super.setScaleX(mScaleToFit * scale); - super.setScaleY(mScaleToFit * scale); + @Override + public void setReorderPreviewOffset(float x, float y) { + mTranslationForReorderPreview.set(x, y); + updateTranslation(); } - public float getReorderScale() { - return mScaleForReorder; + @Override + public void getReorderPreviewOffset(PointF offset) { + offset.set(mTranslationForReorderPreview); + } + + private void updateScale() { + super.setScaleX(mScaleToFit * mScaleForReorderBounce); + super.setScaleY(mScaleToFit * mScaleForReorderBounce); + } + + public void setReorderBounceScale(float scale) { + mScaleForReorderBounce = scale; + updateScale(); + } + + public float getReorderBounceScale() { + return mScaleForReorderBounce; + } + + public void setScaleToFit(float scale) { + mScaleToFit = scale; + updateScale(); + } + + public float getScaleToFit() { + return mScaleToFit; } @Override From 4f075254170b4e4cd9d23692458c7af12d2b1c30 Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Wed, 22 Apr 2020 21:54:21 +0800 Subject: [PATCH 21/35] Fix AlwaysOnTop stack may covered by RemoteAnimation Now with hierarachical animation, the layer of RecentsAnimation / RemoteAnimation are controlled by system. Now Launcher side don't need to have other way to set layer for ZBoost case, so remove setLayer & withLayer related logics for cleaning up. Fix: 151024899 Test: manual, launch google map app -> enter PiP mode, swipe up to overview screen or launch activity from shortcut, make sure the PiP window is not covered by other apps. Change-Id: I8028cd50d66f7d77e5d1ec6514e953c8be9935d6 (cherry picked from commit 99f7da7dd243ac3969a41ad64cdfd0f92fefaa08) --- .../com/android/quickstep/util/AppWindowAnimationHelper.java | 3 --- .../android/launcher3/QuickstepAppTransitionManagerImpl.java | 3 --- .../com/android/quickstep/util/RemoteAnimationProvider.java | 5 +---- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index 9309110fd0..5abbd86a86 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -208,7 +208,6 @@ public class AppWindowAnimationHelper { float alpha; float cornerRadius = 0f; float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width(); - int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers); if (app.mode == params.mTargetSet.targetMode) { alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha); if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { @@ -245,13 +244,11 @@ public class AppWindowAnimationHelper { alpha = mBaseAlphaCallback.getAlpha(app, progress); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) { crop = null; - layer = Integer.MAX_VALUE; } } builder.withAlpha(alpha) .withMatrix(mTmpMatrix) .withWindowCrop(crop) - .withLayer(layer) // Since radius is in Surface space, but we draw the rounded corners in screen // space, we have to undo the scale .withCornerRadius(cornerRadius / scale); diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index 70cbd828a6..1cb0aa452e 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -611,7 +611,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans .withWindowCrop(target.screenSpaceBounds) .withAlpha(1f); } - builder.withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING)); params[i] = builder.build(); } surfaceApplier.scheduleApply(params); @@ -718,7 +717,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans params[i] = new SurfaceParams.Builder(target.leash) .withAlpha(1f) .withWindowCrop(target.screenSpaceBounds) - .withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING)) .withCornerRadius(cornerRadius) .build(); } @@ -775,7 +773,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans } params[i] = builder .withWindowCrop(target.screenSpaceBounds) - .withLayer(RemoteAnimationProvider.getLayer(target, MODE_CLOSING)) .build(); } surfaceApplier.scheduleApply(params); diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java index 21b97ecc7f..4cd0206f20 100644 --- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java +++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java @@ -62,15 +62,12 @@ public abstract class RemoteAnimationProvider { static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets, TransactionCompat t, int boostModeTargets) { for (RemoteAnimationTargetCompat target : targets) { - t.setLayer(target.leash, getLayer(target, boostModeTargets)); t.show(target.leash); } } public static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) { - return target.mode == boostModeTarget - ? Z_BOOST_BASE + target.prefixOrderIndex - : target.prefixOrderIndex; + return target.prefixOrderIndex; } /** From 6b6287f4144323770a74d3a70e1a1900796a1e75 Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Thu, 16 Apr 2020 01:41:48 +0000 Subject: [PATCH 22/35] Detects nav bar gestures to progress through Home tutorial. Home gesture also exits Back and Home tutorials on the completion screens (as does back gesture). Test: Manually verified Back and Home tutorial flows. Bug: 148542211 Change-Id: I8026745e875e9ccb8cfd60bc438213b2d896254d --- .../BackGestureTutorialController.java | 10 ++ .../HomeGestureTutorialController.java | 18 +++ .../interaction/NavBarGestureHandler.java | 121 ++++++++++++++++++ .../interaction/TutorialController.java | 9 +- .../interaction/TutorialFragment.java | 28 ++-- .../interaction/TutorialHandAnimation.java | 1 + .../quickstep/util/NavBarPosition.java | 5 + 7 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index d58ab5d5b6..ff98701171 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -22,6 +22,7 @@ import android.view.View; import com.android.launcher3.R; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; +import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; /** A {@link TutorialController} for the Back tutorial. */ final class BackGestureTutorialController extends TutorialController { @@ -114,4 +115,13 @@ final class BackGestureTutorialController extends TutorialController { break; } } + + @Override + public void onNavBarGestureAttempted(NavBarGestureResult result) { + if (mTutorialType == BACK_NAVIGATION_COMPLETE) { + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + mTutorialFragment.closeTutorial(); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 0bf996dc8e..95b3c7924b 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -21,6 +21,7 @@ import android.view.View; import com.android.launcher3.R; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; +import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; /** A {@link TutorialController} for the Home tutorial. */ final class HomeGestureTutorialController extends TutorialController { @@ -82,4 +83,21 @@ final class HomeGestureTutorialController extends TutorialController { break; } } + + @Override + public void onNavBarGestureAttempted(NavBarGestureResult result) { + switch (mTutorialType) { + case HOME_NAVIGATION: + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + hideHandCoachingAnimation(); + mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE); + } + break; + case HOME_NAVIGATION_COMPLETE: + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + mTutorialFragment.closeTutorial(); + } + break; + } + } } diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java new file mode 100644 index 0000000000..6d8caa2ec5 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 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.interaction; + +import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED; +import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE; +import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION; +import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED; +import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.view.Display; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.View.OnTouchListener; + +import com.android.launcher3.ResourceUtils; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.util.NavBarPosition; +import com.android.quickstep.util.TriggerSwipeUpTouchTracker; + +/** Utility class to handle home gestures. */ +public class NavBarGestureHandler implements OnTouchListener { + + private static final String LOG_TAG = "NavBarGestureHandler"; + + private final Point mDisplaySize = new Point(); + private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker; + private int mBottomGestureHeight; + private boolean mTouchCameFromNavBar; + private NavBarGestureAttemptCallback mGestureCallback; + + NavBarGestureHandler(Context context) { + final Display display = context.getDisplay(); + final int displayRotation; + if (display == null) { + displayRotation = Surface.ROTATION_0; + } else { + displayRotation = display.getRotation(); + display.getRealSize(mDisplaySize); + } + mSwipeUpTouchTracker = + new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/, + new NavBarPosition(Mode.NO_BUTTON, displayRotation), + null /*onInterceptTouch*/, this::onSwipeUp); + + final Resources resources = context.getResources(); + mBottomGestureHeight = + ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, resources); + } + + void registerNavBarGestureAttemptCallback(NavBarGestureAttemptCallback callback) { + mGestureCallback = callback; + } + + void unregisterNavBarGestureAttemptCallback() { + mGestureCallback = null; + } + + private void onSwipeUp(boolean wasFling) { + if (mGestureCallback == null) { + return; + } + if (mTouchCameFromNavBar) { + mGestureCallback.onNavBarGestureAttempted(wasFling + ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED); + } else { + mGestureCallback.onNavBarGestureAttempted(wasFling + ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE); + } + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + int action = motionEvent.getAction(); + boolean intercepted = mSwipeUpTouchTracker.interceptedTouch(); + if (action == MotionEvent.ACTION_DOWN) { + mTouchCameFromNavBar = motionEvent.getRawY() >= mDisplaySize.y - mBottomGestureHeight; + mSwipeUpTouchTracker.init(); + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) { + mGestureCallback.onNavBarGestureAttempted( + HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION); + intercepted = true; + } + } + mSwipeUpTouchTracker.onMotionEvent(motionEvent); + return intercepted; + } + + enum NavBarGestureResult { + UNKNOWN, + HOME_GESTURE_COMPLETED, + OVERVIEW_GESTURE_COMPLETED, + HOME_NOT_STARTED_TOO_FAR_FROM_EDGE, + OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE, + HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION // Side swipe on nav bar. + } + + /** Callback to let the UI react to attempted nav bar gestures. */ + interface NavBarGestureAttemptCallback { + /** Called whenever any touch is completed. */ + void onNavBarGestureAttempted(NavBarGestureResult result); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index f0cb567161..69c61ce769 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -26,9 +26,11 @@ import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import com.android.launcher3.R; -import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; +import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback; +import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback; -abstract class TutorialController { +abstract class TutorialController implements BackGestureAttemptCallback, + NavBarGestureAttemptCallback { final TutorialFragment mTutorialFragment; final TutorialType mTutorialType; @@ -58,8 +60,6 @@ abstract class TutorialController { mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button); } - abstract void onBackGestureAttempted(BackGestureResult result); - @Nullable Integer getTitleStringId() { return null; @@ -86,6 +86,7 @@ abstract class TutorialController { void hideHandCoachingAnimation() { mHandCoachingAnimation.stop(); + mHandCoachingView.setVisibility(View.INVISIBLE); } @CallSuper diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index 6346a9bd9f..3d025257c2 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -21,7 +21,9 @@ import android.graphics.Insets; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; +import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.WindowInsets; @@ -31,13 +33,11 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.android.launcher3.R; -import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback; -import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import com.android.quickstep.interaction.TutorialController.TutorialType; import java.net.URISyntaxException; -abstract class TutorialFragment extends Fragment implements BackGestureAttemptCallback { +abstract class TutorialFragment extends Fragment implements OnTouchListener { private static final String LOG_TAG = "TutorialFragment"; private static final String SYSTEM_NAVIGATION_SETTING_INTENT = @@ -52,6 +52,7 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa View mRootView; TutorialHandAnimation mHandCoachingAnimation; EdgeBackGestureHandler mEdgeBackGestureHandler; + NavBarGestureHandler mNavBarGestureHandler; public static TutorialFragment newInstance(TutorialType tutorialType) { TutorialFragment fragment = getFragmentForTutorialType(tutorialType); @@ -91,13 +92,14 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE); mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext()); - mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this); + mNavBarGestureHandler = new NavBarGestureHandler(getContext()); } @Override public void onDestroy() { super.onDestroy(); mEdgeBackGestureHandler.unregisterBackGestureAttemptCallback(); + mNavBarGestureHandler.unregisterNavBarGestureAttemptCallback(); } @Override @@ -111,7 +113,7 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right); return insets; }); - mRootView.setOnTouchListener(mEdgeBackGestureHandler); + mRootView.setOnTouchListener(this); mHandCoachingAnimation = new TutorialHandAnimation(getContext(), mRootView, getHandAnimationResId()); return mRootView; @@ -129,6 +131,13 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa mHandCoachingAnimation.stop(); } + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + // Note: Using logical or to ensure both functions get called. + return mEdgeBackGestureHandler.onTouch(view, motionEvent) + | mNavBarGestureHandler.onTouch(view, motionEvent); + } + void onAttachedToWindow() { mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView()); } @@ -140,6 +149,8 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa void changeController(TutorialType tutorialType) { mTutorialController = createController(tutorialType); mTutorialController.transitToController(); + mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController); + mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController); mTutorialType = tutorialType; } @@ -157,13 +168,6 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa return mHandCoachingAnimation; } - @Override - public void onBackGestureAttempted(BackGestureResult result) { - if (mTutorialController != null) { - mTutorialController.onBackGestureAttempted(result); - } - } - void closeTutorial() { FragmentActivity activity = getActivity(); if (activity != null) { diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java b/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java index 5362aaf1b6..c810e43426 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java @@ -45,6 +45,7 @@ final class TutorialHandAnimation { /** [Re]starts animation for the given tutorial. */ void startLoopedAnimation(TutorialType tutorialType) { + mHandCoachingView.setVisibility(View.VISIBLE); if (mGestureAnimation.isRunning()) { stop(); } diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java index 74e6b29295..0a98e1bfea 100644 --- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java +++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java @@ -35,6 +35,11 @@ public class NavBarPosition { mDisplayRotation = info.rotation; } + public NavBarPosition(SysUINavigationMode.Mode mode, int displayRotation) { + mMode = mode; + mDisplayRotation = displayRotation; + } + public boolean isRightEdge() { return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90; } From 64a91132c5b85e4dd80853fca929d9304eaace21 Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Thu, 16 Apr 2020 21:13:01 +0000 Subject: [PATCH 23/35] Adds feedback to Home and Back tutorials. Back feedback: - Too far from edge of screen - Cancelled (reversed gesture, swiped upwards, etc.) - Inside nav bar region - Demo: https://drive.google.com/open?id=1pc_hr7i-iZmgF37CN8oijPjhOvvsJoEG Home feedback: - Too far from edge of screen - Paused too long (Overview detected) - Swiped sideways instead of up - Demo: https://drive.google.com/open?id=1NGYAlqV2wJtM2DOJ1pZM-r8N1SYVtMos Bug: 148542211 Test: Manual Change-Id: I627ed7c6e9b005d35794e4ae568529b5613cbf70 --- .../res/layout/gesture_tutorial_fragment.xml | 11 ++++ quickstep/res/values/dimens.xml | 1 + quickstep/res/values/strings.xml | 16 ++++++ quickstep/res/values/styles.xml | 8 +++ .../BackGestureTutorialController.java | 52 +++++++++++++++---- .../BackGestureTutorialFragment.java | 5 ++ .../interaction/EdgeBackGestureHandler.java | 17 +++--- .../HomeGestureTutorialController.java | 20 +++++-- .../HomeGestureTutorialFragment.java | 5 ++ .../interaction/TutorialController.java | 36 ++++++++++++- .../interaction/TutorialFragment.java | 8 ++- res/values/colors.xml | 3 +- 12 files changed, 160 insertions(+), 22 deletions(-) diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml index 69481ad49f..6e3672245f 100644 --- a/quickstep/res/layout/gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/gesture_tutorial_fragment.xml @@ -66,6 +66,17 @@ style="@style/TextAppearance.GestureTutorial.Subtitle"/> + +