Merge "Enabling Private Space Container in Launcher." into main
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index e30fe66..8cbf239 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -96,7 +96,7 @@
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
? mLauncher.getStateManager().getLastState()
: NORMAL;
- } else if (fromState == NORMAL && isDragTowardPositive) {
+ } else if (fromState == NORMAL && shouldOpenAllApps(isDragTowardPositive)) {
return ALL_APPS;
}
return fromState;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 26ab3d6..cda7855 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -21,6 +21,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadScroll;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
import android.graphics.PointF;
@@ -57,6 +58,8 @@
/* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
private boolean mCanIntercept;
+ private boolean mIsTrackpadReverseScroll;
+
public StatusBarTouchController(Launcher l) {
mLauncher = l;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher);
@@ -92,6 +95,8 @@
}
mDownEvents.clear();
mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
+ mIsTrackpadReverseScroll = !mLauncher.isNaturalScrollingEnabled()
+ && isTrackpadScroll(ev);
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
// Check!! should only set it only when threshold is not entered.
mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
@@ -102,6 +107,9 @@
if (action == ACTION_MOVE) {
float dy = ev.getY(idx) - mDownEvents.get(pid).y;
float dx = ev.getX(idx) - mDownEvents.get(pid).x;
+ if (mIsTrackpadReverseScroll) {
+ dy = -dy;
+ }
// Currently input dispatcher will not do touch transfer if there are more than
// one touch pointer. Hence, even if slope passed, only set the slippery flag
// when there is single touch event. (context: InputDispatcher.cpp line 1445)
@@ -126,6 +134,7 @@
mLauncher.getStatsLogManager().logger()
.log(LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN);
setWindowSlippery(false);
+ mIsTrackpadReverseScroll = false;
return true;
}
return true;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index db5ad82..179612b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -57,12 +57,14 @@
import android.provider.Settings;
import android.util.Log;
import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.config.FeatureFlags;
@@ -98,20 +100,22 @@
private final Context mContext;
private final DisplayController mDisplayController;
- private final int mDisplayId;
- private final ISystemGestureExclusionListener mGestureExclusionListener =
+ private final IWindowManager mIWindowManager;
+
+ @VisibleForTesting
+ final ISystemGestureExclusionListener mGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
@BinderThread
@Override
public void onSystemGestureExclusionChanged(int displayId,
Region systemGestureExclusionRegion, Region unrestrictedOrNull) {
- if (displayId != mDisplayId) {
+ if (displayId != DEFAULT_DISPLAY) {
return;
}
// Assignments are atomic, it should be safe on binder thread. Also we don't
- // think systemGestureExclusionRegion can be null but just in case, don't let
- // mExclusionRegion be null.
+ // think systemGestureExclusionRegion can be null but just in case, don't
+ // let mExclusionRegion be null.
mExclusionRegion = systemGestureExclusionRegion != null
? systemGestureExclusionRegion : new Region();
}
@@ -142,17 +146,28 @@
private boolean mExclusionListenerRegistered;
public RecentsAnimationDeviceState(Context context) {
- this(context, false);
+ this(context, false, WindowManagerGlobal.getWindowManagerService());
+ }
+
+ public RecentsAnimationDeviceState(Context context, boolean isInstanceForTouches) {
+ this(context, isInstanceForTouches, WindowManagerGlobal.getWindowManagerService());
+ }
+
+ @VisibleForTesting
+ RecentsAnimationDeviceState(Context context, IWindowManager windowManager) {
+ this(context, false, windowManager);
}
/**
* @param isInstanceForTouches {@code true} if this is the persistent instance being used for
* gesture touch handling
*/
- public RecentsAnimationDeviceState(Context context, boolean isInstanceForTouches) {
+ RecentsAnimationDeviceState(
+ Context context, boolean isInstanceForTouches,
+ IWindowManager windowManager) {
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
- mDisplayId = DEFAULT_DISPLAY;
+ mIWindowManager = windowManager;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
if (isInstanceForTouches) {
@@ -251,7 +266,7 @@
@Override
public void onDisplayInfoChanged(Context context, Info info, int flags) {
if ((flags & (CHANGE_ROTATION | CHANGE_NAVIGATION_MODE)) != 0) {
- mMode = info.navigationMode;
+ mMode = info.getNavigationMode();
ActiveGestureLog.INSTANCE.setIsFullyGesturalNavMode(isFullyGesturalNavMode());
mNavBarPosition = new NavBarPosition(mMode, info);
@@ -273,9 +288,8 @@
return;
}
try {
- WindowManagerGlobal.getWindowManagerService()
- .registerSystemGestureExclusionListener(
- mGestureExclusionListener, mDisplayId);
+ mIWindowManager.registerSystemGestureExclusionListener(
+ mGestureExclusionListener, DEFAULT_DISPLAY);
mExclusionListenerRegistered = true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to register window manager callbacks", e);
@@ -294,9 +308,8 @@
return;
}
try {
- WindowManagerGlobal.getWindowManagerService()
- .unregisterSystemGestureExclusionListener(
- mGestureExclusionListener, mDisplayId);
+ mIWindowManager.unregisterSystemGestureExclusionListener(
+ mGestureExclusionListener, DEFAULT_DISPLAY);
mExclusionListenerRegistered = false;
} catch (RemoteException e) {
Log.e(TAG, "Failed to unregister window manager callbacks", e);
@@ -340,7 +353,7 @@
* @return the display id for the display that Launcher is running on.
*/
public int getDisplayId() {
- return mDisplayId;
+ return DEFAULT_DISPLAY;
}
/**
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 92aa9fa..0d7ca07 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -178,9 +178,12 @@
mContext = context;
mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
- | Intent.FILL_IN_COMPONENT);
+ | Intent.FILL_IN_COMPONENT, options.toBundle());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index c82cdb7..348e4dc 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -18,6 +18,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.content.Context;
+import android.graphics.Rect;
import android.util.ArrayMap;
import android.view.Surface;
import android.view.WindowManager;
@@ -41,6 +42,12 @@
}
@Override
+ public Rect getCurrentBounds(Context displayInfoContext) {
+ return displayInfoContext.getResources().getConfiguration().windowConfiguration
+ .getMaxBounds();
+ }
+
+ @Override
public int getRotation(Context displayInfoContext) {
return displayInfoContext.getResources().getConfiguration().windowConfiguration
.getRotation();
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 17fa253..eced5a9 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -104,7 +104,7 @@
Context context = instrumentation.getContext();
mDevice = UiDevice.getInstance(instrumentation);
mDevice.setOrientationNatural();
- mLauncher = new LauncherInstrumentation();
+ mLauncher = AbstractLauncherUiTest.createLauncherInstrumentation();
mLauncher.enableDebugTracing();
// b/143488140
//mLauncher.enableCheckEventsForSuccessfulGestures();
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
new file mode 100644
index 0000000..53bc2a2
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -0,0 +1,144 @@
+package com.android.quickstep
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.IWindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
+import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
+import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.window.WindowManagerProxy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+/** Unit test for [RecentsAnimationDeviceState]. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RecentsAnimationDeviceStateTest {
+
+ @Mock private lateinit var windowManager: IWindowManager
+ @Mock private lateinit var windowManagerProxy: WindowManagerProxy
+ @Mock private lateinit var info: Info
+
+ private val context = ApplicationProvider.getApplicationContext() as Context
+ private lateinit var underTest: RecentsAnimationDeviceState
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = RecentsAnimationDeviceState(context, windowManager)
+ }
+
+ @Test
+ fun registerExclusionListener_success() {
+ underTest.registerExclusionListener()
+
+ awaitTasksCompleted()
+ verify(windowManager)
+ .registerSystemGestureExclusionListener(
+ underTest.mGestureExclusionListener,
+ Display.DEFAULT_DISPLAY
+ )
+ }
+
+ @Test
+ fun registerExclusionListener_again_fail() {
+ underTest.registerExclusionListener()
+ awaitTasksCompleted()
+ reset(windowManager)
+
+ underTest.registerExclusionListener()
+
+ awaitTasksCompleted()
+ verifyZeroInteractions(windowManager)
+ }
+
+ @Test
+ fun unregisterExclusionListener_success() {
+ underTest.registerExclusionListener()
+ awaitTasksCompleted()
+ reset(windowManager)
+
+ underTest.unregisterExclusionListener()
+
+ awaitTasksCompleted()
+ verify(windowManager)
+ .unregisterSystemGestureExclusionListener(
+ underTest.mGestureExclusionListener,
+ Display.DEFAULT_DISPLAY
+ )
+ }
+
+ @Test
+ fun unregisterExclusionListener_again_fail() {
+ underTest.registerExclusionListener()
+ underTest.unregisterExclusionListener()
+ awaitTasksCompleted()
+ reset(windowManager)
+
+ underTest.unregisterExclusionListener()
+
+ awaitTasksCompleted()
+ verifyZeroInteractions(windowManager)
+ }
+
+ @Test
+ fun onDisplayInfoChanged_noButton_registerExclusionListener() {
+ whenever(windowManagerProxy.getNavigationMode(context)).thenReturn(NavigationMode.NO_BUTTON)
+
+ underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
+
+ awaitTasksCompleted()
+ verify(windowManager)
+ .registerSystemGestureExclusionListener(
+ underTest.mGestureExclusionListener,
+ Display.DEFAULT_DISPLAY
+ )
+ }
+
+ @Test
+ fun onDisplayInfoChanged_twoButton_unregisterExclusionListener() {
+ underTest.registerExclusionListener()
+ awaitTasksCompleted()
+ whenever(info.getNavigationMode()).thenReturn(NavigationMode.TWO_BUTTONS)
+ reset(windowManager)
+
+ underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
+
+ awaitTasksCompleted()
+ verify(windowManager)
+ .unregisterSystemGestureExclusionListener(
+ underTest.mGestureExclusionListener,
+ Display.DEFAULT_DISPLAY
+ )
+ }
+
+ @Test
+ fun onDisplayInfoChanged_changeDensity_noOp() {
+ underTest.registerExclusionListener()
+ awaitTasksCompleted()
+ whenever(info.getNavigationMode()).thenReturn(NavigationMode.NO_BUTTON)
+ reset(windowManager)
+
+ underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY)
+
+ awaitTasksCompleted()
+ verifyZeroInteractions(windowManager)
+ }
+
+ private fun awaitTasksCompleted() {
+ Executors.UI_HELPER_EXECUTOR.submit<Any> { null }.get()
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java
deleted file mode 100644
index 969da68..0000000
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.InvariantDeviceProfile;
-
-import org.junit.After;
-import org.junit.Before;
-
-/**
- * Tests the Icon App Chip Menu in overview.
- *
- * <p>Same tests as TaplOverviewIconTest with the Flag FLAG_ENABLE_OVERVIEW_ICON_MENU enabled.
- * This class can be removed once FLAG_ENABLE_OVERVIEW_ICON_MENU is enabled by default.
- */
-public class TaplOverviewIconAppChipMenuTest extends TaplOverviewIconTest {
-
- @Before
- public void setUp() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU); // Call before super.setUp
- super.setUp();
- executeOnLauncher(launcher -> InvariantDeviceProfile.INSTANCE.get(launcher).onConfigChanged(
- launcher));
- }
-
- @After
- public void tearDown() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU);
- executeOnLauncher(launcher -> InvariantDeviceProfile.INSTANCE.get(launcher).onConfigChanged(
- launcher));
- }
-}
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 8eab3e3..ec26f58 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -162,6 +162,15 @@
}
}
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ super.setText(text, type);
+ // With hardware keyboard, there is a possibility that the user types before edit
+ // text is visible during the transition.
+ // So move the cursor to the end of the text.
+ setSelection(getText().length());
+ }
+
/**
* This method should be preferred to {@link #setOnFocusChangeListener(OnFocusChangeListener)},
* as it allows for multiple listeners from different sources.
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 75f4bb2..e5a6b2b 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -27,12 +27,12 @@
import android.annotation.TargetApi;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Point;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -700,14 +700,11 @@
}
public DeviceProfile getDeviceProfile(Context context) {
- Resources res = context.getResources();
- Configuration config = context.getResources().getConfiguration();
+ WindowManagerProxy windowManagerProxy = WindowManagerProxy.INSTANCE.get(context);
+ Rect bounds = windowManagerProxy.getCurrentBounds(context);
+ int rotation = windowManagerProxy.getRotation(context);
- float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
- float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
- int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
-
- return getBestMatch(screenWidth, screenHeight, rotation);
+ return getBestMatch(bounds.width(), bounds.height(), rotation);
}
/**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5a0cf9d..37b3e05 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -100,6 +100,7 @@
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
+import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -232,6 +233,7 @@
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
@@ -415,6 +417,11 @@
private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
private boolean mIsColdStartupAfterReboot;
+ private boolean mIsNaturalScrollingEnabled;
+
+ private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener =
+ enabled -> mIsNaturalScrollingEnabled = enabled;
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -563,6 +570,10 @@
}
getRootView().dispatchInsets();
+ final SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
+ settingsCache.register(TOUCHPAD_NATURAL_SCROLLING, mNaturalScrollingChangedListener);
+ mIsNaturalScrollingEnabled = settingsCache.getValue(TOUCHPAD_NATURAL_SCROLLING);
+
// Listen for screen turning off
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
@@ -1680,6 +1691,8 @@
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
+ SettingsCache.INSTANCE.get(this).unregister(TOUCHPAD_NATURAL_SCROLLING,
+ mNaturalScrollingChangedListener);
ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
mWorkspace.removeFolderListeners();
PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
@@ -3181,6 +3194,10 @@
return !isWorkspaceLoading();
}
+ public boolean isNaturalScrollingEnabled() {
+ return mIsNaturalScrollingEnabled;
+ }
+
public void setWaitingForResult(PendingRequestArgs args) {
mPendingRequestArgs = args;
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index aa06089..fb58cbe 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -24,6 +24,10 @@
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
+import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT
+import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS
+import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT
+import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT
import com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE
import com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS
import com.android.launcher3.model.DeviceGridState
@@ -327,28 +331,28 @@
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
nonRestorableItem(
"pref_long_press_nav_handle_haptic_hint_start_scale_percent",
- 0,
+ LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get(),
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
nonRestorableItem(
"pref_long_press_nav_handle_haptic_hint_end_scale_percent",
- 100,
+ LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get(),
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
nonRestorableItem(
"pref_long_press_nav_handle_haptic_hint_scale_exponent",
- 1,
+ LPNH_HAPTIC_HINT_SCALE_EXPONENT.get(),
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
nonRestorableItem(
"pref_long_press_nav_handle_haptic_hint_iterations",
- 50,
+ LPNH_HAPTIC_HINT_ITERATIONS.get(),
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 51eb363..7d9f709 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -286,6 +286,22 @@
getReleaseFlag(303023676, "ENABLE_SEARCH_HAPTIC_HINT", ENABLED,
"Enables haptic hint when long pressing on the bottom bar nav handle.");
+ public static final IntFlag LPNH_HAPTIC_HINT_START_SCALE_PERCENT =
+ getIntFlag(309972570, "FLAG_LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
+ "Haptic hint start scale.");
+
+ public static final IntFlag LPNH_HAPTIC_HINT_END_SCALE_PERCENT =
+ getIntFlag(309972570, "FLAG_LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
+ "Haptic hint end scale.");
+
+ public static final IntFlag LPNH_HAPTIC_HINT_SCALE_EXPONENT =
+ getIntFlag(309972570, "FLAG_LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
+ "Haptic hint scale exponent.");
+
+ public static final IntFlag LPNH_HAPTIC_HINT_ITERATIONS =
+ getIntFlag(309972570, "FLAG_LPNH_HAPTIC_HINT_ITERATIONS", 50,
+ "Haptic hint number of iterations.");
+
// TODO(Block 17): Clean up flags
// Aconfig migration complete for ENABLE_TASKBAR_PINNING.
private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index cec4574..9aed4eb 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadScroll;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -68,6 +69,7 @@
protected boolean mGoingBetweenStates = true;
// Ratio of transition process [0, 1] to drag displacement (px)
protected float mProgressMultiplier;
+ protected boolean mIsTrackpadReverseScroll;
private boolean mNoIntercept;
private boolean mIsLogContainerSet;
@@ -92,6 +94,9 @@
return false;
}
+ mIsTrackpadReverseScroll = !mLauncher.isNaturalScrollingEnabled()
+ && isTrackpadScroll(ev);
+
// Now figure out which direction scroll events the controller will start
// calling the callbacks.
final int directionsToDetectScroll;
@@ -248,6 +253,11 @@
}
mIsLogContainerSet = true;
}
+ // Only reverse the gesture to open all apps (not close) when trackpad reverse scrolling is
+ // on.
+ if (mIsTrackpadReverseScroll && mStartState == NORMAL) {
+ displacement = -displacement;
+ }
return onDrag(displacement);
}
@@ -274,6 +284,11 @@
return;
}
+ // Only reverse the gesture to open all apps (not close) when trackpad reverse scrolling is
+ // on.
+ if (mIsTrackpadReverseScroll && mStartState == NORMAL) {
+ velocity = -velocity;
+ }
boolean fling = mDetector.isFling(velocity);
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -412,9 +427,15 @@
mGoingBetweenStates = true;
mDetector.finishedScrolling();
mDetector.setDetectableScrollConditions(0, false);
+ mIsTrackpadReverseScroll = false;
}
private void cancelAnimationControllers() {
mCurrentAnimation = null;
}
+
+ protected boolean shouldOpenAllApps(boolean isDragTowardPositive) {
+ return (isDragTowardPositive && !mIsTrackpadReverseScroll)
+ || (!isDragTowardPositive && mIsTrackpadReverseScroll);
+ }
}
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 447d22b..8b9bc19 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -166,7 +166,7 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- if (fromState == NORMAL && isDragTowardPositive) {
+ if (fromState == NORMAL && shouldOpenAllApps(isDragTowardPositive)) {
return ALL_APPS;
} else if (fromState == ALL_APPS && !isDragTowardPositive) {
return NORMAL;
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 0470971..4c83668 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -431,6 +431,11 @@
return smallestSizeDp(bounds) >= MIN_TABLET_WIDTH;
}
+ /** Getter for {@link #navigationMode} to allow mocking. */
+ public NavigationMode getNavigationMode() {
+ return navigationMode;
+ }
+
/**
* Returns smallest size in dp for given bounds.
*/
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 2ab6dc3..ccd154a 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -60,6 +60,9 @@
Settings.Secure.getUriFor("hide_privatespace_entry_point");
public static final Uri ROTATION_SETTING_URI =
Settings.System.getUriFor(ACCELEROMETER_ROTATION);
+ /** Hidden field {@link Settings.System#TOUCHPAD_NATURAL_SCROLLING}. */
+ public static final Uri TOUCHPAD_NATURAL_SCROLLING = Settings.System.getUriFor(
+ "touchpad_natural_scrolling");
private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java
index ec3b642..84a3e7a 100644
--- a/src/com/android/launcher3/util/WindowBounds.java
+++ b/src/com/android/launcher3/util/WindowBounds.java
@@ -85,7 +85,7 @@
* Returns true if the device is in landscape orientation
*/
public final boolean isLandscape() {
- return availableSize.x > availableSize.y;
+ return bounds.width() > bounds.height();
}
/**
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 278a37e..51a96c4 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -338,6 +338,20 @@
}
/**
+ * Returns bounds of the display associated with the context, or bounds of DEFAULT_DISPLAY
+ * if the context isn't associated with a display.
+ */
+ public Rect getCurrentBounds(Context displayInfoContext) {
+ Resources res = displayInfoContext.getResources();
+ Configuration config = res.getConfiguration();
+
+ float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
+ float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
+
+ return new Rect(0, 0, (int) screenWidth, (int) screenHeight);
+ }
+
+ /**
* Returns rotation of the display associated with the context, or rotation of DEFAULT_DISPLAY
* if the context isn't associated with a display.
*/
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 0b31469..e46726d 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -273,6 +273,7 @@
val realBounds = windowsBounds[rotation]
whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
whenever(windowManagerProxy.getRealBounds(any(), any())).thenReturn(realBounds)
+ whenever(windowManagerProxy.getCurrentBounds(any())).thenReturn(realBounds.bounds)
whenever(windowManagerProxy.getRotation(any())).thenReturn(rotation)
whenever(windowManagerProxy.getNavigationMode(any()))
.thenReturn(
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 688f418..cb1102e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -114,9 +114,9 @@
protected final LauncherInstrumentation mLauncher = createLauncherInstrumentation();
@NonNull
- private static LauncherInstrumentation createLauncherInstrumentation() {
+ public static LauncherInstrumentation createLauncherInstrumentation() {
waitForSetupWizardDismissal(); // precondition for creating LauncherInstrumentation
- return new LauncherInstrumentation();
+ return new LauncherInstrumentation(true);
}
protected Context mTargetContext;
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index a60dba7..406de35 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -215,15 +215,31 @@
* Constructs the root of TAPL hierarchy. You get all other objects from it.
*/
public LauncherInstrumentation() {
- this(InstrumentationRegistry.getInstrumentation());
+ this(InstrumentationRegistry.getInstrumentation(), false);
}
/**
* Constructs the root of TAPL hierarchy. You get all other objects from it.
- * Deprecated: use the constructor without parameters instead.
+ */
+ public LauncherInstrumentation(boolean isLauncherTest) {
+ this(InstrumentationRegistry.getInstrumentation(), isLauncherTest);
+ }
+
+ /**
+ * Constructs the root of TAPL hierarchy. You get all other objects from it.
+ * @deprecated use the constructor without Instrumentation parameter instead.
*/
@Deprecated
public LauncherInstrumentation(Instrumentation instrumentation) {
+ this(instrumentation, false);
+ }
+
+ /**
+ * Constructs the root of TAPL hierarchy. You get all other objects from it.
+ * @deprecated use the constructor without Instrumentation parameter instead.
+ */
+ @Deprecated
+ public LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
@@ -272,12 +288,17 @@
.replaceAll("\\s", "");
mDevice.executeShellCommand(
"pm enable --user " + userId + " " + cn.flattenToString());
+
// Wait for Launcher restart after enabling test provider.
- for (int i = 0; i < 300; ++i) {
+ final int iterations = isLauncherTest ? 300 : 100;
+
+ for (int i = 0; i < iterations; ++i) {
final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
.replaceAll("\\s", "");
if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
- if (i == 299) fail("Launcher didn't restart after enabling test provider");
+ if (i == iterations - 1) {
+ fail("Launcher didn't restart after enabling test provider");
+ }
SystemClock.sleep(100);
}
} catch (IOException e) {