Merge "[Test Week] Add ViewCacheTest" into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index ea091ca..872a4d0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
@@ -37,6 +38,7 @@
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
+import android.view.inputmethod.Flags;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -147,7 +149,7 @@
break;
case BUTTON_IME_SWITCH:
logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
- showIMESwitcher();
+ onImeSwitcherPress();
break;
case BUTTON_A11Y:
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_TAP);
@@ -190,6 +192,12 @@
backRecentsLongpress(buttonType);
return true;
case BUTTON_IME_SWITCH:
+ if (Flags.imeSwitcherRevamp()) {
+ logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ onImeSwitcherLongPress();
+ return true;
+ }
+ return false;
default:
return false;
}
@@ -305,10 +313,14 @@
mSystemUiProxy.onBackPressed();
}
- private void showIMESwitcher() {
+ private void onImeSwitcherPress() {
mSystemUiProxy.onImeSwitcherPressed();
}
+ private void onImeSwitcherLongPress() {
+ mSystemUiProxy.onImeSwitcherLongPress();
+ }
+
private void notifyA11yClick(boolean longClick) {
if (longClick) {
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
diff --git a/quickstep/src/com/android/quickstep/OrientationRectF.java b/quickstep/src/com/android/quickstep/OrientationRectF.java
index aa01b05..2b7ecb2 100644
--- a/quickstep/src/com/android/quickstep/OrientationRectF.java
+++ b/quickstep/src/com/android/quickstep/OrientationRectF.java
@@ -67,13 +67,15 @@
}
public boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
+ if (deltaRotation == 0) {
+ return contains(event.getX(), event.getY());
+ }
mTmpMatrix.reset();
postDisplayRotation(deltaRotation, mHeight, mWidth, mTmpMatrix);
if (forceTransform) {
if (DEBUG) {
Log.d(TAG, "Transforming rotation due to forceTransform, "
+ "deltaRotation: " + deltaRotation
- + "mRotation: " + mRotation
+ " this: " + this);
}
event.applyTransform(mTmpMatrix);
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index 29a57fc..5264643 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -34,12 +34,18 @@
private final Context mContext;
private OrientationRectF mOrientationRectF;
+ private OrientationRectF mTouchingOrientationRectF;
+ private int mViewRotation;
public SimpleOrientationTouchTransformer(Context context) {
+ this(context, DisplayController.INSTANCE.get(context));
+ }
+
+ @androidx.annotation.VisibleForTesting
+ public SimpleOrientationTouchTransformer(Context context, DisplayController displayController) {
mContext = context;
- DisplayController.INSTANCE.get(context).addChangeListener(this);
- onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
- CHANGE_ALL);
+ displayController.addChangeListener(this);
+ onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
}
@Override
@@ -56,7 +62,29 @@
info.rotation);
}
+ /**
+ * Called when the touch is started. This preserves the touching orientation until the touch is
+ * done (i.e. ACTION_CANCEL or ACTION_UP). So the transform won't produce inconsistent position
+ * if display is changed during the touch.
+ */
+ public void updateTouchingOrientation(int viewRotation) {
+ mViewRotation = viewRotation;
+ mTouchingOrientationRectF = new OrientationRectF(mOrientationRectF.left,
+ mOrientationRectF.top, mOrientationRectF.right, mOrientationRectF.bottom,
+ mOrientationRectF.getRotation());
+ }
+
+ /** Called when the touch is finished. */
+ public void clearTouchingOrientation() {
+ mTouchingOrientationRectF = null;
+ }
+
public void transform(MotionEvent ev, int rotation) {
+ if (mTouchingOrientationRectF != null) {
+ mTouchingOrientationRectF.applyTransformToRotation(ev, mViewRotation,
+ true /* forceTransform */);
+ return;
+ }
mOrientationRectF.applyTransformToRotation(ev, rotation, true /* forceTransform */);
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 433baa9..0d9f81f 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -229,6 +229,17 @@
}
@Override
+ public void onImeSwitcherLongPress() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onImeSwitcherLongPress();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onImeSwitcherLongPress");
+ }
+ }
+ }
+
+ @Override
public void setHomeRotationEnabled(boolean enabled) {
if (mSystemUiProxy != null) {
try {
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index cb44a1a..fcf9ab1 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -108,19 +108,28 @@
return false;
}
+ final SimpleOrientationTouchTransformer touchTransformer =
+ SimpleOrientationTouchTransformer.INSTANCE.get(mContext);
+ final int viewRotation = mRotationSupplier.get();
+ final boolean needTransform = viewRotation != ev.getSurfaceRotation();
if (action == ACTION_DOWN) {
mTouchInProgress = true;
+ if (needTransform) {
+ touchTransformer.updateTouchingOrientation(viewRotation);
+ }
initInputConsumerIfNeeded(/* isFromTouchDown= */ true);
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
+ touchTransformer.clearTouchingOrientation();
if (mDestroyPending) {
destroy();
}
}
if (mInputConsumer != null) {
- SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev,
- mRotationSupplier.get());
+ if (needTransform) {
+ touchTransformer.transform(ev, viewRotation);
+ }
mInputConsumer.onMotionEvent(ev);
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 0f06d98..399aea6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -4,6 +4,8 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
@@ -26,6 +28,7 @@
import android.os.Handler;
import android.view.View;
+import android.view.inputmethod.Flags;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -109,8 +112,27 @@
@Test
public void testPressImeSwitcher() {
+ mNavButtonController.init(mockTaskbarControllers);
mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
+ verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
+ verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed();
+ verify(mockSystemUiProxy, never()).onImeSwitcherLongPress();
+ }
+
+ @Test
+ public void testLongPressImeSwitcher() {
+ mNavButtonController.init(mockTaskbarControllers);
+ mNavButtonController.onButtonLongClick(BUTTON_IME_SWITCH, mockView);
+ verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
+ verify(mockSystemUiProxy, never()).onImeSwitcherPressed();
+ if (Flags.imeSwitcherRevamp()) {
+ verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mockSystemUiProxy, times(1)).onImeSwitcherLongPress();
+ } else {
+ verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mockSystemUiProxy, never()).onImeSwitcherLongPress();
+ }
}
@Test
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 298dd6c..f5d082d 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -288,6 +289,34 @@
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
+ @Test
+ public void testSimpleOrientationTouchTransformer() {
+ final DisplayController displayController = mock(DisplayController.class);
+ doReturn(mInfo).when(displayController).getInfo();
+ final SimpleOrientationTouchTransformer transformer =
+ new SimpleOrientationTouchTransformer(getApplicationContext(), displayController);
+ final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.transform(move1, Surface.ROTATION_90);
+ // The position is transformed to 90 degree.
+ assertEquals(10, move1.getX(), 0f /* delta */);
+ assertEquals(NORMAL_SCREEN_SIZE.getWidth() - 100, move1.getY(), 0f /* delta */);
+
+ // If the touching state is specified, the position is still transformed to 90 degree even
+ // if the given rotation is changed.
+ final MotionEvent move2 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.updateTouchingOrientation(Surface.ROTATION_90);
+ transformer.transform(move2, Surface.ROTATION_0);
+ assertEquals(move1.getX(), move2.getX(), 0f /* delta */);
+ assertEquals(move1.getY(), move2.getY(), 0f /* delta */);
+
+ // If the touching state is cleared, it restores to use the given rotation.
+ final MotionEvent move3 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.clearTouchingOrientation();
+ transformer.transform(move3, Surface.ROTATION_0);
+ assertEquals(100, move3.getX(), 0f /* delta */);
+ assertEquals(10, move3.getY(), 0f /* delta */);
+ }
+
private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
RotationUtils.rotateSize(displaySize, rotation);
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 861631d..fbd24d8 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -795,6 +795,9 @@
@UiEvent(doc = "User launches Overview from meta+tab keyboard shortcut")
LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT(1765),
+ @UiEvent(doc = "User long pressed on the taskbar IME switcher button")
+ LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS(1798),
+
// ADD MORE
;
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 1fb8c83..3016c9a 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -37,6 +37,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
@@ -484,7 +485,8 @@
* Sets or unsets a flag the can change whether the widget host should be in the listening
* state.
*/
- private void setShouldListenFlag(int flag, boolean on) {
+ @VisibleForTesting
+ void setShouldListenFlag(int flag, boolean on) {
if (on) {
mFlags.updateAndGet(old -> old | flag);
} else {
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt
new file mode 100644
index 0000000..1a659e2
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 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.widget
+
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.BuildConfig.WIDGETS_ENABLED
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_ACTIVITY_RESUMED
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_ACTIVITY_STARTED
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_STATE_IS_NORMAL
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit::class)
+class LauncherWidgetHolderTest {
+ private lateinit var widgetHolder: LauncherWidgetHolder
+
+ @Before
+ fun setUp() {
+ assertTrue(WIDGETS_ENABLED)
+ widgetHolder =
+ LauncherWidgetHolder(ActivityContextWrapper(getInstrumentation().targetContext)) {}
+ }
+
+ @After
+ fun tearDown() {
+ widgetHolder.destroy()
+ }
+
+ @Test
+ fun widget_holder_start_listening() {
+ val testView = mock(PendingAppWidgetHostView::class.java)
+ widgetHolder.mViews[0] = testView
+ widgetHolder.setListeningFlag(false)
+ assertFalse(widgetHolder.isListening)
+ widgetHolder.startListening()
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ getInstrumentation().waitForIdleSync()
+ assertTrue(widgetHolder.isListening)
+ verify(testView, times(1)).reInflate()
+ widgetHolder.clearWidgetViews()
+ }
+
+ @Test
+ fun holder_start_listening_after_activity_start() {
+ widgetHolder.setShouldListenFlag(FLAG_STATE_IS_NORMAL or FLAG_ACTIVITY_RESUMED, true)
+ widgetHolder.setActivityStarted(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setActivityStarted(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ fun holder_start_listening_after_activity_resume() {
+ widgetHolder.setShouldListenFlag(FLAG_STATE_IS_NORMAL or FLAG_ACTIVITY_STARTED, true)
+ widgetHolder.setActivityResumed(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setActivityResumed(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ fun holder_start_listening_after_state_normal() {
+ widgetHolder.setShouldListenFlag(FLAG_ACTIVITY_RESUMED or FLAG_ACTIVITY_STARTED, true)
+ widgetHolder.setStateIsNormal(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setStateIsNormal(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ @UiThreadTest
+ fun widget_holder_create_view() {
+ val mockProviderInfo = mock(LauncherAppWidgetProviderInfo::class.java)
+ doReturn(false).whenever(mockProviderInfo).isCustomWidget
+ assertEquals(0, widgetHolder.mViews.size())
+ widgetHolder.createView(APP_WIDGET_ID, mockProviderInfo)
+ assertEquals(1, widgetHolder.mViews.size())
+ assertEquals(APP_WIDGET_ID, widgetHolder.mViews.get(0).appWidgetId)
+ widgetHolder.deleteAppWidgetId(APP_WIDGET_ID)
+ assertEquals(0, widgetHolder.mViews.size())
+ }
+
+ @Test
+ fun holder_add_provider_change_listener() {
+ val listener = LauncherWidgetHolder.ProviderChangedListener {}
+ widgetHolder.addProviderChangeListener(listener)
+ getInstrumentation().waitForIdleSync()
+ assertEquals(1, widgetHolder.mProviderChangedListeners.size)
+ assertSame(widgetHolder.mProviderChangedListeners.first(), listener)
+ widgetHolder.removeProviderChangeListener(listener)
+ }
+
+ @Test
+ fun holder_remove_provider_change_listener() {
+ val listener = LauncherWidgetHolder.ProviderChangedListener {}
+ widgetHolder.addProviderChangeListener(listener)
+ widgetHolder.removeProviderChangeListener(listener)
+ getInstrumentation().waitForIdleSync()
+ assertEquals(0, widgetHolder.mProviderChangedListeners.size)
+ }
+
+ @Test
+ fun widget_holder_stop_listening() {
+ widgetHolder.setListeningFlag(true)
+ assertTrue(widgetHolder.isListening)
+ widgetHolder.stopListening()
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.isListening)
+ }
+
+ companion object {
+ private const val APP_WIDGET_ID = 0
+ }
+}