Snap for 10712072 from 5ed1b4001b to udc-qpr1-release

Change-Id: I681564ee16c56fc099243218a2c5028c11849fee
This commit is contained in:
Android Build Coastguard Worker
2023-08-24 23:19:43 +00:00
25 changed files with 762 additions and 40 deletions
+2
View File
@@ -27,6 +27,8 @@
<string name="nav_handle_long_press_handler_class" translatable="false"></string>
<string name="assist_utils_class" translatable="false"></string>
<string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
<string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
@@ -51,6 +51,7 @@ import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtilsBase;
import com.android.quickstep.views.DesktopTaskView;
import java.io.PrintWriter;
@@ -158,7 +159,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
switch (buttonType) {
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
startAssistant();
onLongPressHome();
return true;
case BUTTON_A11Y:
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS);
@@ -307,13 +308,17 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
}
}
private void startAssistant() {
private void onLongPressHome() {
if (mScreenPinned || !mAssistantLongPressEnabled) {
return;
}
Bundle args = new Bundle();
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
mSystemUiProxy.startAssistant(args);
// Attempt to start Assist with AssistUtils, otherwise fall back to SysUi's implementation.
if (!AssistUtilsBase.newInstance(mService.getApplicationContext()).tryStartAssistOverride(
INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
Bundle args = new Bundle();
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
mSystemUiProxy.startAssistant(args);
}
}
private void showQuickSettings() {
@@ -62,6 +62,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.AssistUtilsBase;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -250,6 +251,8 @@ public class SystemUiProxy implements ISystemUiProxy {
setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
setUnfoldAnimationListener(mUnfoldAnimationListener);
setDesktopTaskListener(mDesktopTaskListener);
setAssistantOverridesRequested(
AssistUtilsBase.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
}
/**
@@ -373,6 +376,17 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
@Override
public void setAssistantOverridesRequested(int[] invocationTypes) {
if (mSystemUiProxy != null) {
try {
mSystemUiProxy.setAssistantOverridesRequested(invocationTypes);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setAssistantOverridesRequested", e);
}
}
}
@Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (mSystemUiProxy != null) {
@@ -117,6 +117,7 @@ import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer;
import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
import com.android.quickstep.util.AssistUtilsBase;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -280,6 +281,20 @@ public class TouchInteractionService extends Service {
}));
}
/**
* Sent when the assistant has been invoked with the given type (defined in AssistManager)
* and should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested
* was previously called including this invocation type.
*/
@Override
public void onAssistantOverrideInvoked(int invocationType) {
executeForTouchInteractionService(tis -> {
if (!AssistUtilsBase.newInstance(tis).tryStartAssistOverride(invocationType)) {
Log.w(TAG, "Failed to invoke Assist override");
}
});
}
@Override
public void onNavigationBarSurface(SurfaceControl surface) {
// TODO: implement
@@ -0,0 +1,45 @@
/*
* 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.util;
import android.content.Context;
import com.android.launcher3.R;
import com.android.launcher3.util.ResourceBasedOverride;
/** Utilities to work with Assistant functionality. */
public class AssistUtilsBase implements ResourceBasedOverride {
public AssistUtilsBase() {}
/** Creates AssistUtils as specified by overrides */
public static AssistUtilsBase newInstance(Context context) {
return Overrides.getObject(AssistUtilsBase.class, context, R.string.assist_utils_class);
}
/** @return Array of AssistUtils.INVOCATION_TYPE_* that we want to handle instead of SysUI. */
public int[] getSysUiAssistOverrideInvocationTypes() {
return new int[0];
}
/**
* @return {@code true} if the override was handled, i.e. an assist surface was shown or the
* request should be ignored. {@code false} means the caller should start assist another way.
*/
public boolean tryStartAssistOverride(int invocationType) {
return false;
}
}
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.os.Handler;
import android.view.View;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.logging.StatsLogManager;
@@ -70,6 +71,9 @@ public class TaskbarNavButtonControllerTest {
MockitoAnnotations.initMocks(this);
when(mockService.getDisplayId()).thenReturn(DISPLAY_ID);
when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper);
when(mockService.getApplicationContext())
.thenReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
.getApplicationContext());
when(mockStatsLogManager.logger()).thenReturn(mockStatsLogger);
when(mockTaskbarControllers.getTaskbarActivityContext())
.thenReturn(mockTaskbarActivityContext);
@@ -25,6 +25,8 @@ import static org.junit.Assume.assumeTrue;
import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.config.FeatureFlags;
@@ -36,7 +38,10 @@ import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplTestsSplitscreen extends AbstractQuickStepTest {
private static final String CALCULATOR_APP_NAME = "Calculator";
private static final String CALCULATOR_APP_PACKAGE =
+20 -2
View File
@@ -16,6 +16,7 @@
package com.android.launcher3;
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -152,7 +153,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private final CheckLongPressHelper mLongPressHelper;
private final boolean mLayoutHorizontal;
private boolean mLayoutHorizontal;
private final boolean mIsRtl;
private final int mIconSize;
@@ -197,6 +198,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mActivity = ActivityContext.lookupContext(context);
FastBitmapDrawable.setFlagHoverEnabled(ENABLE_CURSOR_HOVER_STATES.get());
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
@@ -665,6 +667,18 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
}
/**
* Sets whether the layout is horizontal.
*/
public void setLayoutHorizontal(boolean layoutHorizontal) {
if (mLayoutHorizontal == layoutHorizontal) {
return;
}
mLayoutHorizontal = layoutHorizontal;
applyCompoundDrawables(getIconOrTransparentColor());
}
/**
* Sets whether to vertically center the content.
*/
@@ -991,10 +1005,14 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
if (!mIsIconVisible) {
resetIconScale();
}
Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
Drawable icon = getIconOrTransparentColor();
applyCompoundDrawables(icon);
}
private Drawable getIconOrTransparentColor() {
return mIsIconVisible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
}
/** Sets the icon visual state to disabled or not. */
public void setIconDisabled(boolean isDisabled) {
if (mIcon != null) {
@@ -245,6 +245,7 @@ public class CellLayout extends ViewGroup {
// the user where a dragged item will land when dropped.
setWillNotDraw(false);
setClipToPadding(false);
setClipChildren(false);
mActivity = ActivityContext.lookupContext(context);
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+11
View File
@@ -198,6 +198,7 @@ import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.CannedAnimationCoordinator;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -421,6 +422,9 @@ public class Launcher extends StatefulActivity<LauncherState>
private StartupLatencyLogger mStartupLatencyLogger;
private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
private final CannedAnimationCoordinator mAnimationCoordinator =
new CannedAnimationCoordinator(this);
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
@@ -3422,4 +3426,11 @@ public class Launcher extends StatefulActivity<LauncherState>
public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
// Overridden
}
/**
* Returns the animation coordinator for playing one-off animations
*/
public CannedAnimationCoordinator getAnimationCoordinator() {
return mAnimationCoordinator;
}
}
@@ -66,6 +66,7 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
mActivity = ActivityContext.lookupContext(context);
mWallpaperManager = WallpaperManager.getInstance(context);
mContainerType = containerType;
setClipChildren(false);
}
public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
@@ -0,0 +1,164 @@
/*
* 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.launcher3.util
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.util.Log
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.core.view.OneShotPreDrawListener
import com.android.app.animation.Interpolators.LINEAR
import com.android.launcher3.anim.AnimatorListeners
import com.android.launcher3.anim.AnimatorPlaybackController
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.statemanager.StatefulActivity
import java.util.function.Consumer
private const val TAG = "CannedAnimCoordinator"
/**
* Utility class to run a canned animation on Launcher.
*
* This class takes care to registering animations with stateManager and ensures that only one
* animation is playing at a time.
*/
class CannedAnimationCoordinator(private val activity: StatefulActivity<*>) {
private val launcherLayoutListener = OnGlobalLayoutListener { scheduleRecreateAnimOnPreDraw() }
private var recreatePending = false
private var animationProvider: Any? = null
private var animationDuration: Long = 0L
private var animationFactory: Consumer<PendingAnimation>? = null
private var animationController: AnimatorPlaybackController? = null
private var currentAnim: AnimatorPlaybackController? = null
/**
* Sets the current animation cancelling any previously set animation.
*
* Callers can control the animation using {@link #getPlaybackController}. The state is
* automatically cleared when the playback controller ends. The animation is automatically
* recreated when any layout change happens. Callers can also ask for recreation by calling
* {@link #recreateAnimation}
*/
fun setAnimation(provider: Any, factory: Consumer<PendingAnimation>, duration: Long) {
if (provider != animationProvider) {
Log.e(TAG, "Trying to play two animations together, $provider and $animationProvider")
}
// Cancel any previously running animation
endCurrentAnimation(false)
animationController?.dispatchOnCancel()?.dispatchOnEnd()
animationProvider = provider
animationFactory = factory
animationDuration = duration
// Setup a new controller and link it with launcher state animation
val anim = AnimatorSet()
anim.play(
ValueAnimator.ofFloat(0f, 1f).apply {
interpolator = LINEAR
this.duration = duration
addUpdateListener { anim -> currentAnim?.setPlayFraction(anim.animatedFraction) }
}
)
val controller = AnimatorPlaybackController.wrap(anim, duration)
anim.addListener(
AnimatorListeners.forEndCallback { success ->
if (animationController != controller) {
return@forEndCallback
}
endCurrentAnimation(success)
animationController = null
animationFactory = null
animationProvider = null
activity.rootView.viewTreeObserver.apply {
if (isAlive) {
removeOnGlobalLayoutListener(launcherLayoutListener)
}
}
}
)
// Recreate animation whenever layout happens in case transforms change during layout
activity.rootView.viewTreeObserver.apply {
if (isAlive) {
addOnGlobalLayoutListener(launcherLayoutListener)
}
}
// Link this to the state manager so that it auto-cancels when state changes
recreatePending = false
animationController =
controller.apply { activity.stateManager.setCurrentUserControlledAnimation(this) }
recreateAnimation(provider)
}
private fun endCurrentAnimation(success: Boolean) {
currentAnim?.apply {
// When cancelling an animation, apply final progress so that all transformations
// are restored
setPlayFraction(1f)
if (!success) dispatchOnCancel()
dispatchOnEnd()
}
currentAnim = null
}
/** Returns the current animation controller to control the animation */
fun getPlaybackController(provider: Any): AnimatorPlaybackController? {
return if (provider == animationProvider) animationController
else {
Log.d(TAG, "Wrong controller access from $provider, actual provider $animationProvider")
null
}
}
private fun scheduleRecreateAnimOnPreDraw() {
if (!recreatePending) {
recreatePending = true
OneShotPreDrawListener.add(activity.rootView) {
if (recreatePending) {
recreatePending = false
animationProvider?.apply { recreateAnimation(this) }
}
}
}
}
/** Notify the controller to recreate the animation. The animation progress is preserved */
fun recreateAnimation(provider: Any) {
if (provider != animationProvider) {
Log.e(TAG, "Ignore recreate request from $provider, actual provider $animationProvider")
return
}
endCurrentAnimation(false /* success */)
if (animationFactory == null || animationController == null) {
return
}
currentAnim =
PendingAnimation(animationDuration)
.apply { animationFactory?.accept(this) }
.createPlaybackController()
.apply { setPlayFraction(animationController!!.progressFraction) }
}
}
@@ -40,8 +40,7 @@ public class MultiScalePropertyFactory<T extends View> {
private static final boolean DEBUG = false;
private static final String TAG = "MultiScaleProperty";
private final String mName;
private final ArrayMap<Integer, MultiScaleProperty> mProperties =
new ArrayMap<Integer, MultiScaleProperty>();
private final ArrayMap<Integer, MultiScaleProperty> mProperties = new ArrayMap<>();
// This is an optimization for cases when set is called repeatedly with the same setterIndex.
private float mMinOfOthers = 0;
@@ -55,7 +54,7 @@ public class MultiScalePropertyFactory<T extends View> {
}
/** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
public MultiScaleProperty get(Integer index) {
public FloatProperty<T> get(Integer index) {
return mProperties.computeIfAbsent(index,
(k) -> new MultiScaleProperty(index, mName + "_" + index));
}
@@ -0,0 +1,329 @@
/*
* 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.launcher3.icons;
import static com.android.launcher3.icons.FastBitmapDrawable.CLICK_FEEDBACK_DURATION;
import static com.android.launcher3.icons.FastBitmapDrawable.HOVERED_SCALE;
import static com.android.launcher3.icons.FastBitmapDrawable.HOVER_FEEDBACK_DURATION;
import static com.android.launcher3.icons.FastBitmapDrawable.PRESSED_SCALE;
import static com.android.launcher3.icons.FastBitmapDrawable.SCALE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.PathInterpolator;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Spy;
/**
* Tests for FastBitmapDrawable.
*/
@SmallTest
@UiThreadTest
@RunWith(AndroidJUnit4.class)
public class FastBitmapDrawableTest {
private static final float EPSILON = 0.00001f;
@Spy
FastBitmapDrawable mFastBitmapDrawable =
spy(new FastBitmapDrawable(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)));
@Before
public void setUp() {
FastBitmapDrawable.setFlagHoverEnabled(true);
when(mFastBitmapDrawable.isVisible()).thenReturn(true);
mFastBitmapDrawable.mIsPressed = false;
mFastBitmapDrawable.mIsHovered = false;
mFastBitmapDrawable.resetScale();
}
@Test
public void testOnStateChange_noState() {
int[] state = new int[]{};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No scale changes without state change.
assertFalse("State change handled.", isHandled);
assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation);
}
@Test
public void testOnStateChange_statePressed() {
int[] state = new int[]{android.R.attr.state_pressed};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to state pressed.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
CLICK_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator()
instanceof AccelerateInterpolator);
}
@Test
public void testOnStateChange_stateHovered() {
int[] state = new int[]{android.R.attr.state_hovered};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to state hovered.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
HOVER_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator);
}
@Test
public void testOnStateChange_stateHoveredFlagDisabled() {
FastBitmapDrawable.setFlagHoverEnabled(false);
int[] state = new int[]{android.R.attr.state_hovered};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No state change with flag disabled.
assertFalse("Hover state change handled with flag disabled.", isHandled);
assertNull("Animation should not run with hover flag disabled.",
mFastBitmapDrawable.mScaleAnimation);
}
@Test
public void testOnStateChange_statePressedAndHovered() {
int[] state = new int[]{android.R.attr.state_pressed, android.R.attr.state_hovered};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to pressed state only.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
CLICK_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator()
instanceof AccelerateInterpolator);
}
@Test
public void testOnStateChange_stateHoveredAndPressed() {
int[] state = new int[]{android.R.attr.state_hovered, android.R.attr.state_pressed};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to pressed state only.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
CLICK_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator()
instanceof AccelerateInterpolator);
}
@Test
public void testOnStateChange_stateHoveredAndPressedToPressed() {
mFastBitmapDrawable.mIsPressed = true;
mFastBitmapDrawable.mIsHovered = true;
SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
int[] state = new int[]{android.R.attr.state_pressed};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No scale change from pressed state to pressed state.
assertTrue("State not changed.", isHandled);
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
}
@Test
public void testOnStateChange_stateHoveredAndPressedToHovered() {
mFastBitmapDrawable.mIsPressed = true;
mFastBitmapDrawable.mIsHovered = true;
SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
int[] state = new int[]{android.R.attr.state_hovered};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No scale change from pressed state to hovered state.
assertTrue("State not changed.", isHandled);
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
}
@Test
public void testOnStateChange_stateHoveredToPressed() {
mFastBitmapDrawable.mIsHovered = true;
SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE);
int[] state = new int[]{android.R.attr.state_pressed};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No scale change from pressed state to hovered state.
assertTrue("State not changed.", isHandled);
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
}
@Test
public void testOnStateChange_statePressedToHovered() {
mFastBitmapDrawable.mIsPressed = true;
SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
int[] state = new int[]{android.R.attr.state_hovered};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No scale change from pressed state to hovered state.
assertTrue("State not changed.", isHandled);
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
}
@Test
public void testOnStateChange_stateDefaultFromPressed() {
mFastBitmapDrawable.mIsPressed = true;
SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
int[] state = new int[]{};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to default state from pressed state.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
CLICK_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator()
instanceof DecelerateInterpolator);
}
@Test
public void testOnStateChange_stateDefaultFromHovered() {
mFastBitmapDrawable.mIsHovered = true;
SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE);
int[] state = new int[]{};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to default state from hovered state.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
HOVER_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator);
}
@Test
public void testOnStateChange_stateHoveredWhilePartiallyScaled() {
SCALE.setValue(mFastBitmapDrawable, 0.5f);
int[] state = new int[]{android.R.attr.state_hovered};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to hovered state from midway to pressed state.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.",
mFastBitmapDrawable.mScaleAnimation.getDuration(), HOVER_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator);
}
@Test
public void testOnStateChange_statePressedWhilePartiallyScaled() {
SCALE.setValue(mFastBitmapDrawable, 0.5f);
int[] state = new int[]{android.R.attr.state_pressed};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// Animate to pressed state from midway to hovered state.
assertTrue("State change not handled.", isHandled);
assertEquals("Duration not correct.",
mFastBitmapDrawable.mScaleAnimation.getDuration(), CLICK_FEEDBACK_DURATION);
mFastBitmapDrawable.mScaleAnimation.end();
assertEquals("End value not correct.",
(float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
assertTrue("Wrong interpolator used.",
mFastBitmapDrawable.mScaleAnimation.getInterpolator()
instanceof AccelerateInterpolator);
}
@Test
public void testOnStateChange_stateDefaultFromPressedNotVisible() {
when(mFastBitmapDrawable.isVisible()).thenReturn(false);
mFastBitmapDrawable.mIsPressed = true;
SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
clearInvocations(mFastBitmapDrawable);
int[] state = new int[]{};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No animations when state was pressed but drawable no longer visible. Set values directly.
assertTrue("State change not handled.", isHandled);
assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation);
assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
verify(mFastBitmapDrawable).invalidateSelf();
}
@Test
public void testOnStateChange_stateDefaultFromHoveredNotVisible() {
when(mFastBitmapDrawable.isVisible()).thenReturn(false);
mFastBitmapDrawable.mIsHovered = true;
SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE);
clearInvocations(mFastBitmapDrawable);
int[] state = new int[]{};
boolean isHandled = mFastBitmapDrawable.onStateChange(state);
// No animations when state was hovered but drawable no longer visible. Set values directly.
assertTrue("State change not handled.", isHandled);
assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation);
assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
verify(mFastBitmapDrawable).invalidateSelf();
}
}
@@ -395,6 +395,28 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
}
}
@Test
public void testLaunchHomeScreenMenuItem() {
// Drag the test app icon to home screen and open short cut menu from the icon
final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
try {
allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
APP_NAME).openDeepShortcutMenu();
executeOnLauncher(
launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
isOptionsPopupVisible(launcher)));
final AppIconMenuItem menuItem = menu.getMenuItem(1);
assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
menuItem.launch(getAppPackageName());
} finally {
allApps.unfreeze();
}
}
@PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
@@ -56,7 +56,10 @@ final class FlashDetector extends AnomalyDetector {
RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon",
DRAG_LAYER + "SearchContainerView:id/apps_view",
DRAG_LAYER + "LauncherDragView",
DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail"
DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
DRAG_LAYER
+ "WidgetsFullSheet|SpringRelativeLayout:id/container|WidgetsRecyclerView:id"
+ "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header"
));
// Per-AnalysisNode data that's specific to this detector.
@@ -336,4 +336,11 @@ public abstract class AllApps extends LauncherInstrumentation.VisibleContainer {
final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
return testInfo == null ? 0 : testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
/**
* Return the QSB UI object on the AllApps screen.
* @return the QSB UI object.
*/
@NonNull
public abstract Qsb getQsb();
}
@@ -62,4 +62,10 @@ public class AllAppsFromTaskbar extends AllApps {
return mLauncher.getTestInfo(TestProtocol.REQUEST_TASKBAR_APPS_LIST_SCROLL_Y)
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
@NonNull
@Override
public TaskbarAllAppsQsb getQsb() {
return new TaskbarAllAppsQsb(mLauncher, verifyActiveContainer());
}
}
@@ -22,16 +22,7 @@ import androidx.test.uiautomator.UiObject2;
*/
class AllAppsQsb extends Qsb {
private final UiObject2 mAllAppsContainer;
AllAppsQsb(LauncherInstrumentation launcher, UiObject2 allAppsContainer) {
super(launcher);
mAllAppsContainer = allAppsContainer;
waitForQsbObject();
}
@Override
protected UiObject2 waitForQsbObject() {
return mLauncher.waitForObjectInContainer(mAllAppsContainer, "search_container_all_apps");
super(launcher, allAppsContainer, "search_container_all_apps");
}
}
@@ -117,11 +117,8 @@ public class HomeAllApps extends AllApps {
}
}
/**
* Return the QSB UI object on the AllApps screen.
* @return the QSB UI object.
*/
@NonNull
@Override
public Qsb getQsb() {
return new AllAppsQsb(mLauncher, verifyActiveContainer());
}
@@ -22,16 +22,7 @@ import androidx.test.uiautomator.UiObject2;
*/
class HomeQsb extends Qsb {
private final UiObject2 mHotSeat;
HomeQsb(LauncherInstrumentation launcher, UiObject2 hotseat) {
super(launcher);
mHotSeat = hotseat;
waitForQsbObject();
}
@Override
protected UiObject2 waitForQsbObject() {
return mLauncher.waitForObjectInContainer(mHotSeat, "search_container_hotseat");
super(launcher, hotseat, "search_container_hotseat");
}
}
+15 -3
View File
@@ -30,13 +30,21 @@ public abstract class Qsb {
private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
protected final LauncherInstrumentation mLauncher;
private final UiObject2 mContainer;
private final String mQsbResName;
protected Qsb(LauncherInstrumentation launcher) {
protected Qsb(LauncherInstrumentation launcher, UiObject2 container, String qsbResName) {
mLauncher = launcher;
mContainer = container;
mQsbResName = qsbResName;
waitForQsbObject();
}
// Waits for the quick search box.
protected abstract UiObject2 waitForQsbObject();
private UiObject2 waitForQsbObject() {
return mLauncher.waitForObjectInContainer(mContainer, mQsbResName);
}
/**
* Launch assistant app by tapping mic icon on qsb.
*/
@@ -79,8 +87,12 @@ public abstract class Qsb {
mLauncher.waitForIdle();
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
"clicked qsb to open search result page")) {
return new SearchResultFromQsb(mLauncher);
return createSearchResult();
}
}
}
protected SearchResultFromQsb createSearchResult() {
return new SearchResultFromQsb(mLauncher);
}
}
@@ -32,7 +32,7 @@ public class SearchResultFromQsb {
// This particular ID change should happen with caution
private static final String SEARCH_CONTAINER_RES_ID = "search_results_list_view";
private final LauncherInstrumentation mLauncher;
protected final LauncherInstrumentation mLauncher;
SearchResultFromQsb(LauncherInstrumentation launcher) {
mLauncher = launcher;
@@ -49,8 +49,12 @@ public class SearchResultFromQsb {
}
/** Find the app from search results with app name. */
public Launchable findAppIcon(String appName) {
public AppIcon findAppIcon(String appName) {
UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
return createAppIcon(icon);
}
protected AppIcon createAppIcon(UiObject2 icon) {
return new AllAppsAppIcon(mLauncher, icon);
}
@@ -0,0 +1,38 @@
/*
* 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.launcher3.tapl;
import androidx.test.uiautomator.UiObject2;
/**
* Operations on search result page opened from Taskbar qsb.
*/
public class SearchResultFromTaskbarQsb extends SearchResultFromQsb {
SearchResultFromTaskbarQsb(LauncherInstrumentation launcher) {
super(launcher);
}
@Override
public TaskbarAppIcon findAppIcon(String appName) {
return (TaskbarAppIcon) super.findAppIcon(appName);
}
@Override
protected TaskbarAppIcon createAppIcon(UiObject2 icon) {
return new TaskbarAppIcon(mLauncher, icon);
}
}
@@ -0,0 +1,38 @@
/*
* 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.launcher3.tapl;
import androidx.test.uiautomator.UiObject2;
/**
* Operations on Taskbar AllApp screen qsb.
*/
public class TaskbarAllAppsQsb extends Qsb {
TaskbarAllAppsQsb(LauncherInstrumentation launcher, UiObject2 allAppsContainer) {
super(launcher, allAppsContainer, "search_container_all_apps");
}
@Override
public SearchResultFromTaskbarQsb showSearchResult() {
return (SearchResultFromTaskbarQsb) super.showSearchResult();
}
@Override
protected SearchResultFromTaskbarQsb createSearchResult() {
return new SearchResultFromTaskbarQsb(mLauncher);
}
}