Snap for 8774677 from 6c7361fb46 to tm-qpr1-release

Change-Id: I8831c78255b256f56f17fa609c09255de41e5343
This commit is contained in:
Android Build Coastguard Worker
2022-06-28 01:24:59 +00:00
11 changed files with 177 additions and 35 deletions
@@ -17,6 +17,7 @@ package com.android.quickstep.util;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -31,6 +32,8 @@ import com.android.launcher3.testing.TestProtocol;
*/
public class MotionPauseDetector {
private static final String TAG = "MotionPauseDetector";
// The percentage of the previous speed that determines whether this is a rapid deceleration.
// The bigger this number, the easier it is to trigger the first pause.
private static final float RAPID_DECELERATION_FACTOR = 0.6f;
@@ -85,7 +88,8 @@ public class MotionPauseDetector {
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */,
"Force pause timeout after " + alarm.getLastSetTimeout() + "ms" /* reason */));
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
mVelocityProvider = new SystemVelocityProvider(axis);
}
@@ -102,7 +106,7 @@ public class MotionPauseDetector {
*/
public void setDisallowPause(boolean disallowPause) {
mDisallowPause = disallowPause;
updatePaused(mIsPaused);
updatePaused(mIsPaused, "Set disallowPause=" + disallowPause);
}
/**
@@ -134,21 +138,27 @@ public class MotionPauseDetector {
float speed = Math.abs(velocity);
float previousSpeed = Math.abs(prevVelocity);
boolean isPaused;
String isPausedReason = "";
if (mIsPaused) {
// Continue to be paused until moving at a fast speed.
isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast;
isPausedReason = "Was paused, but started moving at a fast speed";
} else {
if (velocity < 0 != prevVelocity < 0) {
// We're just changing directions, not necessarily stopping.
isPaused = false;
isPausedReason = "Velocity changed directions";
} else {
isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow;
isPausedReason = "Pause requires back to back slow speeds";
if (!isPaused && !mHasEverBeenPaused) {
// We want to be more aggressive about detecting the first pause to ensure it
// feels as responsive as possible; getting two very slow speeds back to back
// takes too long, so also check for a rapid deceleration.
boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
isPausedReason = "Didn't have back to back slow speeds, checking for rapid"
+ " deceleration on first pause only";
}
if (mMakePauseHarderToTrigger) {
if (speed < mSpeedSlow) {
@@ -156,22 +166,27 @@ public class MotionPauseDetector {
mSlowStartTime = time;
}
isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
isPausedReason = "Maintained slow speed for sufficient duration when making"
+ " pause harder to trigger";
} else {
mSlowStartTime = 0;
isPaused = false;
isPausedReason = "Intentionally making pause harder to trigger";
}
}
}
}
updatePaused(isPaused);
updatePaused(isPaused, isPausedReason);
}
private void updatePaused(boolean isPaused) {
private void updatePaused(boolean isPaused, String reason) {
if (mDisallowPause) {
reason = "Disallow pause; otherwise, would have been " + isPaused + " due to " + reason;
isPaused = false;
}
if (mIsPaused != isPaused) {
mIsPaused = isPaused;
Log.d(TAG, "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason);
boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
if (mIsPaused) {
AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
+7
View File
@@ -30,6 +30,7 @@ public class Alarm implements Runnable{
private Handler mHandler;
private OnAlarmListener mAlarmListener;
private boolean mAlarmPending = false;
private long mLastSetTimeout;
public Alarm() {
mHandler = new Handler();
@@ -46,6 +47,7 @@ public class Alarm implements Runnable{
mAlarmPending = true;
long oldTriggerTime = mAlarmTriggerTime;
mAlarmTriggerTime = currentTime + millisecondsInFuture;
mLastSetTimeout = millisecondsInFuture;
// If the previous alarm was set for a longer duration, cancel it.
if (mWaitingForCallback && oldTriggerTime > mAlarmTriggerTime) {
@@ -84,4 +86,9 @@ public class Alarm implements Runnable{
public boolean alarmPending() {
return mAlarmPending;
}
/** Returns the last value passed to {@link #setAlarm(long)} */
public long getLastSetTimeout() {
return mLastSetTimeout;
}
}
@@ -304,7 +304,8 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
return true;
}
if (isSearching()) {
if (isSearching()
&& mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) {
// if in search state, consume touch event.
return true;
}
@@ -277,6 +277,10 @@ public final class FeatureFlags {
"ENABLE_DISMISS_PREDICTION_UNDO", false,
"Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(
"ENABLE_CACHED_WIDGET", true,
"Show previously cached widgets as opposed to deferred widget where available");
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
@@ -28,6 +28,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.SparseArray;
import android.widget.RemoteViews;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -37,6 +38,7 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
@@ -70,13 +72,14 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
private final Context mContext;
private int mFlags = FLAG_STATE_IS_NORMAL;
private IntConsumer mAppWidgetRemovedCallback = null;
public LauncherAppWidgetHost(Context context) {
this(context, null);
}
@@ -95,6 +98,11 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
if (mPendingViews.get(appWidgetId) != null) {
view = mPendingViews.get(appWidgetId);
mPendingViews.remove(appWidgetId);
} else if (mDeferredViews.get(appWidgetId) != null) {
// In case the widget view is deferred, we will simply return the deferred view as
// opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
// already added the former to the workspace.
view = mDeferredViews.get(appWidgetId);
} else {
view = new LauncherAppWidgetHostView(context);
}
@@ -120,12 +128,25 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
// widgets upon bind anyway. See issue 14255011 for more context.
}
// We go in reverse order and inflate any deferred widget
// We go in reverse order and inflate any deferred or cached widget
for (int i = mViews.size() - 1; i >= 0; i--) {
LauncherAppWidgetHostView view = mViews.valueAt(i);
if (view instanceof DeferredAppWidgetHostView) {
view.reInflate();
}
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
final int appWidgetId = mViews.keyAt(i);
if (view == mDeferredViews.get(appWidgetId)) {
// If the widget view was deferred, we'll need to call super.createView here
// to make the binder call to system process to fetch cumulative updates to this
// widget, as well as setting up this view for future updates.
super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo());
// At this point #onCreateView should have been called, which in turn returned
// the deferred view. There's no reason to keep the reference anymore, so we
// removed it here.
mDeferredViews.remove(appWidgetId);
}
}
}
}
@@ -221,10 +242,28 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
return lahv;
} else if ((mFlags & FLAG_LISTENING) == 0) {
DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
view.setAppWidget(appWidgetId, appWidget);
mViews.put(appWidgetId, view);
return view;
// Since the launcher hasn't started listening to widget updates, we can't simply call
// super.createView here because the later will make a binder call to retrieve
// RemoteViews from system process.
// TODO: have launcher always listens to widget updates in background so that this
// check can be removed altogether.
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
&& mCachedRemoteViews.get(appWidgetId) != null) {
// We've found RemoteViews from cache for this widget, so we will instantiate a
// widget host view and populate it with the cached RemoteViews.
final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
view.setAppWidget(appWidgetId, appWidget);
view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
mDeferredViews.put(appWidgetId, view);
mViews.put(appWidgetId, view);
return view;
} else {
// When cache misses, a placeholder for the widget will be returned instead.
DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
view.setAppWidget(appWidgetId, appWidget);
mViews.put(appWidgetId, view);
return view;
}
} else {
try {
return super.createView(context, appWidgetId, appWidget);
@@ -281,6 +320,16 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
@Override
public void clearViews() {
super.clearViews();
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
// First, we clear any previously cached content from existing widgets
mCachedRemoteViews.clear();
// Then we proceed to cache the content from the widgets
for (int i = 0; i < mViews.size(); i++) {
final int appWidgetId = mViews.keyAt(i);
final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
}
}
mViews.clear();
}
@@ -43,6 +43,7 @@ import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -85,7 +86,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
private Runnable mAutoAdvanceRunnable;
private long mDeferUpdatesUntilMillis = 0;
private RemoteViews mDeferredRemoteViews;
RemoteViews mLastRemoteViews;
private boolean mHasDeferredColorChange = false;
private @Nullable SparseIntArray mDeferredColorChange = null;
@@ -150,11 +151,18 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
mTrackingWidgetUpdate = false;
}
if (isDeferringUpdates()) {
mDeferredRemoteViews = remoteViews;
return;
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
mLastRemoteViews = remoteViews;
if (isDeferringUpdates()) {
return;
}
} else {
if (isDeferringUpdates()) {
mLastRemoteViews = remoteViews;
return;
}
mLastRemoteViews = null;
}
mDeferredRemoteViews = null;
super.updateAppWidget(remoteViews);
@@ -218,8 +226,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
SparseIntArray deferredColors;
boolean hasDeferredColors;
mDeferUpdatesUntilMillis = 0;
remoteViews = mDeferredRemoteViews;
mDeferredRemoteViews = null;
remoteViews = mLastRemoteViews;
deferredColors = mDeferredColorChange;
hasDeferredColors = mHasDeferredColorChange;
mDeferredColorChange = null;
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.content.Intent;
import android.graphics.Point;
@@ -191,6 +192,17 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
isInState(() -> LauncherState.ALL_APPS));
}
@Test
@PortraitLandscape
public void testAllAppsDeadzoneForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
true /* tapRight */);
mLauncher.getWorkspace().switchToAllApps().dismissByTappingOutsideForTablet(
false /* tapRight */);
}
@Test
@ScreenRecord // b/202433017
public void testWorkspace() throws Exception {
@@ -16,11 +16,6 @@
package com.android.launcher3.tapl;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
@@ -50,25 +45,15 @@ public class Folder {
}
}
private void touchOutsideFolder() {
Rect containerBounds = mLauncher.getVisibleBounds(this.mContainer);
final long downTime = SystemClock.uptimeMillis();
Point containerLeftTopCorner = new Point(containerBounds.left - 1, containerBounds.top - 1);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
}
/**
* CLose opened folder if possible. It throws assertion error if the folder is already closed.
* Close opened folder if possible. It throws assertion error if the folder is already closed.
*/
public Workspace close() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"Want to close opened folder")) {
mLauncher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
touchOutsideFolder();
mLauncher.touchOutsideContainer(this.mContainer, false /* tapRight */);
mLauncher.waitUntilLauncherObjectGone(FOLDER_CONTENT_RES_ID);
return mLauncher.getWorkspace();
}
@@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
public class HomeAllApps extends AllApps {
private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
HomeAllApps(LauncherInstrumentation launcher) {
super(launcher);
@@ -45,4 +46,23 @@ public class HomeAllApps extends AllApps {
protected boolean hasSearchBox() {
return true;
}
/**
* Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
* @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
*/
public Workspace dismissByTappingOutsideForTablet(boolean tapRight) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to tap outside AllApps bottom sheet on the "
+ (tapRight ? "right" : "left"))) {
final UiObject2 allAppsBottomSheet =
mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
"tapped outside AllApps bottom sheet")) {
return mLauncher.getWorkspace();
}
}
}
}
@@ -1831,4 +1831,26 @@ public final class LauncherInstrumentation {
return ResourceUtils.getBoolByName(
"config_supportsRoundedCornersOnWindows", resources, false);
}
/**
* Taps outside container to dismiss.
* @param container container to be dismissed
* @param tapRight tap on the right of the container if true, or left otherwise
*/
void touchOutsideContainer(UiObject2 container, boolean tapRight) {
try (LauncherInstrumentation.Closable c = addContextLayer(
"want to tap outside container on the " + (tapRight ? "right" : "left"))) {
Rect containerBounds = getVisibleBounds(container);
final long downTime = SystemClock.uptimeMillis();
final Point tapTarget = new Point(
tapRight
? (containerBounds.right + getRealDisplaySize().x) / 2
: containerBounds.left / 2,
containerBounds.top + 1);
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
LauncherInstrumentation.GestureScope.INSIDE);
sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
LauncherInstrumentation.GestureScope.INSIDE);
}
}
}
@@ -26,6 +26,7 @@ import androidx.test.uiautomator.UiObject2;
public class SearchResultFromQsb {
// The input resource id in the search box.
private static final String INPUT_RES = "input";
private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
private final LauncherInstrumentation mLauncher;
SearchResultFromQsb(LauncherInstrumentation launcher) {
@@ -47,4 +48,23 @@ public class SearchResultFromQsb {
UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
return new AllAppsAppIcon(mLauncher, icon);
}
/**
* Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
* @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
*/
public Workspace dismissByTappingOutsideForTablet(boolean tapRight) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to tap outside AllApps bottom sheet on the "
+ (tapRight ? "right" : "left"))) {
final UiObject2 allAppsBottomSheet =
mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight);
try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
"tapped outside AllApps bottom sheet")) {
return mLauncher.getWorkspace();
}
}
}
}