Merge "Fix two pane widget picker bugs" into main
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 05e1535..851f2b3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -157,8 +157,8 @@
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.DelegateLaunchAnimatorController;
+import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.DelegateTransitionAnimatorController;
import com.android.systemui.animation.LaunchableView;
import com.android.systemui.animation.RemoteAnimationDelegate;
import com.android.systemui.shared.system.BlurUtils;
@@ -1838,8 +1838,8 @@
// The CUJ is logged by the click handler, so we don't log it inside the animation
// library.
- ActivityLaunchAnimator.Controller controllerDelegate =
- ActivityLaunchAnimator.Controller.fromView(viewToUse, null /* cujType */);
+ ActivityTransitionAnimator.Controller controllerDelegate =
+ ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */);
if (controllerDelegate == null) {
return null;
@@ -1847,15 +1847,15 @@
// This wrapper allows us to override the default value, telling the controller that the
// current window is below the animating window.
- ActivityLaunchAnimator.Controller controller =
- new DelegateLaunchAnimatorController(controllerDelegate) {
+ ActivityTransitionAnimator.Controller controller =
+ new DelegateTransitionAnimatorController(controllerDelegate) {
@Override
public boolean isBelowAnimatingWindow() {
return true;
}
};
- ActivityLaunchAnimator.Callback callback = task -> {
+ ActivityTransitionAnimator.Callback callback = task -> {
final int backgroundColor =
startingWindowListener.mBackgroundColor == Color.TRANSPARENT
? launcher.getScrimView().getBackgroundColor()
@@ -1863,15 +1863,17 @@
return ColorUtils.setAlphaComponent(backgroundColor, 255);
};
- ActivityLaunchAnimator.Listener listener = new ActivityLaunchAnimator.Listener() {
- @Override
- public void onLaunchAnimationEnd() {
- onEndCallback.executeAllAndDestroy();
- }
- };
+ ActivityTransitionAnimator.Listener listener =
+ new ActivityTransitionAnimator.Listener() {
+ @Override
+ public void onTransitionAnimationEnd() {
+ onEndCallback.executeAllAndDestroy();
+ }
+ };
return new ContainerAnimationRunner(
- new ActivityLaunchAnimator.AnimationDelegate(controller, callback, listener));
+ new ActivityTransitionAnimator.AnimationDelegate(
+ controller, callback, listener));
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 8e4a78f..9f6994a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -304,6 +304,10 @@
callbacks.addListener(mTaskBarRecentsAnimationListener);
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
+
+ ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchCancelledRunnable(() -> {
+ updateStateForUserFinishedToApp(false /* finishedToApp */);
+ });
return animatorSet;
}
@@ -770,21 +774,29 @@
mTaskBarRecentsAnimationListener = null;
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
- // Update the visible state immediately to ensure a seamless handoff
- boolean launcherVisible = !finishedToApp;
- updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
- updateStateForFlag(FLAG_VISIBLE, launcherVisible);
- applyState();
-
- TaskbarStashController controller = mControllers.taskbarStashController;
- if (DEBUG) {
- Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
- }
- controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
- controller.applyState();
+ updateStateForUserFinishedToApp(finishedToApp);
}
}
+ /**
+ * Updates the visible state immediately to ensure a seamless handoff.
+ * @param finishedToApp True iff user is in an app.
+ */
+ private void updateStateForUserFinishedToApp(boolean finishedToApp) {
+ // Update the visible state immediately to ensure a seamless handoff
+ boolean launcherVisible = !finishedToApp;
+ updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
+ updateStateForFlag(FLAG_VISIBLE, launcherVisible);
+ applyState();
+
+ TaskbarStashController controller = mControllers.taskbarStashController;
+ if (DEBUG) {
+ Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
+ }
+ controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
+ controller.applyState();
+ }
+
private static String getStateString(int flags) {
StringJoiner result = new StringJoiner("|");
appendFlag(result, flags, FLAG_VISIBLE, "flag_visible");
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 8ff43f0..c2cd11c 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -646,6 +646,18 @@
});
});
}
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ recentsView.onTaskLaunchedInLiveTileModeCancelled();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ recentsView.setTaskLaunchCancelledRunnable(null);
+ }
};
} else {
AnimatorPlaybackController controller =
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index fecbf08..9884d8d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -759,6 +759,9 @@
private RunnableList mSideTaskLaunchCallback;
@Nullable
private TaskLaunchListener mTaskLaunchListener;
+ @Nullable
+ private Runnable mOnTaskLaunchCancelledRunnable;
+
// keeps track of the state of the filter for tasks in recents view
private final RecentsFilterState mFilterState = new RecentsFilterState();
@@ -1195,6 +1198,21 @@
}
}
+ /**
+ * This is a one-time callback when touching in live tile mode. It's reset to null right
+ * after it's called.
+ */
+ public void setTaskLaunchCancelledRunnable(Runnable onTaskLaunchCancelledRunnable) {
+ mOnTaskLaunchCancelledRunnable = onTaskLaunchCancelledRunnable;
+ }
+
+ public void onTaskLaunchedInLiveTileModeCancelled() {
+ if (mOnTaskLaunchCancelledRunnable != null) {
+ mOnTaskLaunchCancelledRunnable.run();
+ mOnTaskLaunchCancelledRunnable = null;
+ }
+ }
+
private void executeSideTaskLaunchCallback() {
if (mSideTaskLaunchCallback != null) {
mSideTaskLaunchCallback.executeAllAndDestroy();
diff --git a/res/layout/widgets_list_row_header_two_pane.xml b/res/layout/widgets_list_row_header_two_pane.xml
index c0a6ea8..bdb2aed 100644
--- a/res/layout/widgets_list_row_header_two_pane.xml
+++ b/res/layout/widgets_list_row_header_two_pane.xml
@@ -23,6 +23,7 @@
android:importantForAccessibility="yes"
android:focusable="true"
launcher:appIconSize="48dp"
+ launcher:collapsable="false"
android:descendantFocusability="afterDescendants"
android:background="@drawable/bg_widgets_header_two_pane" >
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 754f0cb..4a0b5e8 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -582,6 +582,7 @@
<declare-styleable name="WidgetsListRowHeader">
<attr name="appIconSize" format="dimension" />
+ <attr name="collapsable" format="boolean" />
</declare-styleable>
<attr name="materialColorOnSecondaryFixedVariant" format="color" />
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index b5e7401..3383299 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -52,7 +52,11 @@
private static final int[] EXPANDED_DRAWABLE_STATE = new int[] {android.R.attr.state_expanded};
private final int mIconSize;
-
+ /**
+ * Indicates if the header is collapsable. For example, when displayed in a two pane layout,
+ * widget apps aren't collapsable.
+ */
+ private final boolean mIsCollapsable;
@Nullable private HandlerRunnable mIconLoadRequest;
@Nullable private Drawable mIconDrawable;
@Nullable private WidgetsListDrawableState mListDrawableState;
@@ -79,6 +83,7 @@
R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
grid.iconSizePx);
+ mIsCollapsable = a.getBoolean(R.styleable.WidgetsListRowHeader_collapsable, true);
}
@Override
@@ -87,32 +92,36 @@
mAppIcon = findViewById(R.id.app_icon);
mTitle = findViewById(R.id.app_title);
mSubtitle = findViewById(R.id.app_subtitle);
- setAccessibilityDelegate(new AccessibilityDelegate() {
+ // Lists that cannot collapse, don't need EXPAND or COLLAPSE accessibility actions.
+ if (mIsCollapsable) {
+ setAccessibilityDelegate(new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- if (mIsExpanded) {
- info.removeAction(AccessibilityNodeInfo.ACTION_EXPAND);
- info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
- } else {
- info.removeAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
- info.addAction(AccessibilityNodeInfo.ACTION_EXPAND);
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfo info) {
+ if (mIsExpanded) {
+ info.removeAction(AccessibilityNodeInfo.ACTION_EXPAND);
+ info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+ } else {
+ info.removeAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+ info.addAction(AccessibilityNodeInfo.ACTION_EXPAND);
+ }
+ super.onInitializeAccessibilityNodeInfo(host, info);
}
- super.onInitializeAccessibilityNodeInfo(host, info);
- }
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_EXPAND:
- case AccessibilityNodeInfo.ACTION_COLLAPSE:
- callOnClick();
- return true;
- default:
- return super.performAccessibilityAction(host, action, args);
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_EXPAND:
+ case AccessibilityNodeInfo.ACTION_COLLAPSE:
+ callOnClick();
+ return true;
+ default:
+ return super.performAccessibilityAction(host, action, args);
+ }
}
- }
- });
+ });
+ }
}
/** Sets the expand toggle to expand / collapse. */
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/multivalentTests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 10b428a..7fba33e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -125,8 +125,6 @@
Log.e(TAG, "Failed to save accessibility hierarchy", ex);
}
- dumpCommand("logcat -d -s TestRunner", diagFile(description, "FilteredLogcat", "txt"));
-
// Dump bugreport
if (!sSavedBugreport) {
dumpCommand("bugreportz -s", diagFile(description, "Bugreport", "zip"));
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index dbafe79..2905d85 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -17,9 +17,12 @@
import android.content.Context
import android.content.res.Configuration
-import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.IgnoreLimit
+import android.platform.test.rule.LimitDevicesRule
import android.util.DisplayMetrics
import android.view.Surface
import androidx.test.core.app.ApplicationProvider
@@ -32,7 +35,6 @@
import com.android.launcher3.util.rule.TestStabilityRule
import com.android.launcher3.util.window.CachedDisplayInfo
import com.android.launcher3.util.window.WindowManagerProxy
-import com.android.wm.shell.Flags
import com.google.common.truth.Truth
import java.io.BufferedReader
import java.io.File
@@ -52,6 +54,8 @@
*
* For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
*/
+@AllowedDevices(allowed = [DeviceProduct.CF_PHONE])
+@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
abstract class AbstractDeviceProfileTest {
protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
protected lateinit var context: SandboxContext
@@ -59,15 +63,11 @@
private val displayController: DisplayController = mock()
private val windowManagerProxy: WindowManagerProxy = mock()
private val launcherPrefs: LauncherPrefs = mock()
- private val allowLeftRightSplitInPortrait: Boolean = initAllowLeftRightSplitInPortrait()
- fun initAllowLeftRightSplitInPortrait(): Boolean {
- val res = Resources.getSystem()
- val resId = res.getIdentifier("config_leftRightSplitInPortrait", "bool", "android")
- return Flags.enableLeftRightSplitInPortrait() && resId > 0 && res.getBoolean(resId)
- }
@Rule @JvmField val testStabilityRule = TestStabilityRule()
+ @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+
class DeviceSpec(
val naturalSize: Pair<Int, Int>,
var densityDpi: Int,
@@ -311,22 +311,6 @@
protected fun assertDump(dp: DeviceProfile, folderName: String, filename: String) {
val dump = dump(context!!, dp, "${folderName}_$filename.txt")
var expected = readDumpFromAssets(testContext, "$folderName/$filename.txt")
-
- // TODO(b/315230497): We don't currently have device-specific device profile dumps, so just
- // update the result before we do the comparison
- if (allowLeftRightSplitInPortrait) {
- val isLeftRightSplitInPortrait =
- when {
- allowLeftRightSplitInPortrait && dp.isTablet -> !dp.isLandscape
- else -> dp.isLandscape
- }
- expected =
- expected.replace(
- Regex("isLeftRightSplit:\\w+"),
- "isLeftRightSplit:$isLeftRightSplitInPortrait"
- )
- }
-
Truth.assertThat(dump).isEqualTo(expected)
}
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 30b5663..251a401 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -18,6 +18,10 @@
import android.content.Context
import android.graphics.PointF
import android.graphics.Rect
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.IgnoreLimit
+import android.platform.test.rule.LimitDevicesRule
import android.util.SparseArray
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.DeviceProfile.DEFAULT_DIMENSION_PROVIDER
@@ -27,6 +31,7 @@
import java.io.PrintWriter
import java.io.StringWriter
import org.junit.Before
+import org.junit.Rule
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -37,6 +42,8 @@
*
* For an implementation that creates InvariantDeviceProfile, use [AbstractDeviceProfileTest]
*/
+@AllowedDevices(allowed = [DeviceProduct.CF_PHONE])
+@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
abstract class FakeInvariantDeviceProfileTest {
protected var context: Context? = null
@@ -49,6 +56,8 @@
protected var isGestureMode: Boolean = true
protected var isTransientTaskbar: Boolean = true
+ @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+
@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java
new file mode 100644
index 0000000..b347f07
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.picker;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ActivityContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WidgetsListHeaderAccessibilityTest {
+ private Context mContext;
+ private LayoutInflater mLayoutInflater;
+ @Mock
+ private View.OnClickListener mOnClickListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = new ActivityContextWrapper(getApplicationContext());
+ mLayoutInflater = LayoutInflater.from(
+ new ContextThemeWrapper(mContext, R.style.WidgetContainerTheme));
+ }
+
+ @Test
+ public void singlePaneCollapsable_hasCustomAccessibilityActions() {
+ WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
+ R.layout.widgets_list_row_header,
+ new FrameLayout(mContext), false);
+
+ assertThat(header.getAccessibilityDelegate()).isNotNull();
+
+ header.setOnClickListener(mOnClickListener);
+ header.getAccessibilityDelegate().performAccessibilityAction(header,
+ AccessibilityNodeInfo.ACTION_EXPAND, null);
+ header.getAccessibilityDelegate().performAccessibilityAction(header,
+ AccessibilityNodeInfo.ACTION_COLLAPSE, null);
+
+ verify(mOnClickListener, times(2)).onClick(header);
+ }
+
+ @Test
+ public void twoPaneNonCollapsable_noCustomAccessibilityDelegate() {
+ WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
+ R.layout.widgets_list_row_header_two_pane,
+ new FrameLayout(mContext), false);
+
+ assertThat(header.getAccessibilityDelegate()).isNull();
+ }
+}