Merging ub-launcher3-qt-r1-dev, build 5710902
Test: Manual Bug:115582915 P2 [Testing] Make all launcher tests gesture-stable Bug:127350205 P1 Ensure that Launcher is always created and is in right configuration Bug:131116002 P2 Convert tests to TAPL and enable them Bug:131356741 P1 use transferFocus to implement SWIPE DOWN on homescreen to open noti shade Bug:133891845 P2 TAPL: need clearer diags when a crash dialog is over Launcher or there is lock screeen Bug:136215685 P1 Flake: swiping up from Home to Overview in 2-button mode sometimes fails Bug:136278866 P2 Temporary workarounds to make tests pass on Cuttlefish Bug:136280872 P1 Flake: Flinging in all apps opens workspace Bug:64712476 P3 Import translations for dev branches Change-Id: I39841b30840e9d88a2b98b21c4aba1c44d2bc446
This commit is contained in:
+1
-1
@@ -164,7 +164,7 @@ public abstract class RecentsUiFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
|
||||
if (FeatureFlags.PULL_DOWN_STATUS_BAR
|
||||
&& !launcher.getDeviceProfile().isMultiWindowMode
|
||||
&& !launcher.getDeviceProfile().isVerticalBarLayout()) {
|
||||
list.add(new StatusBarTouchController(launcher));
|
||||
|
||||
@@ -43,6 +43,8 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
@@ -74,6 +76,8 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.logging.EventLogArray;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.model.AppLaunchTracker;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
@@ -91,6 +95,7 @@ import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
|
||||
import com.android.systemui.shared.recents.IOverviewProxy;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
|
||||
import com.android.systemui.shared.system.InputConsumerController;
|
||||
import com.android.systemui.shared.system.InputMonitorCompat;
|
||||
@@ -152,6 +157,7 @@ public class TouchInteractionService extends Service implements
|
||||
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
|
||||
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
|
||||
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
|
||||
MAIN_THREAD_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -502,6 +508,9 @@ public class TouchInteractionService extends Service implements
|
||||
}
|
||||
|
||||
private void onInputEvent(InputEvent ev) {
|
||||
if (TestProtocol.sDebugTracing) {
|
||||
Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "onInputEvent " + ev);
|
||||
}
|
||||
if (!(ev instanceof MotionEvent)) {
|
||||
Log.e(TAG, "Unknown event " + ev);
|
||||
return;
|
||||
@@ -684,6 +693,54 @@ public class TouchInteractionService extends Service implements
|
||||
}
|
||||
}
|
||||
|
||||
private void preloadOverview(boolean fromInit) {
|
||||
if (!mIsUserUnlocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ActivityControlHelper<BaseDraggingActivity> activityControl =
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
if (activityControl.getCreatedActivity() == null) {
|
||||
// Make sure that UI states will be initialized.
|
||||
activityControl.createActivityInitListener((activity, wasVisible) -> {
|
||||
AppLaunchTracker.INSTANCE.get(activity);
|
||||
return false;
|
||||
}).register();
|
||||
} else if (fromInit) {
|
||||
// The activity has been created before the initialization of overview service. It is
|
||||
// usually happens when booting or launcher is the top activity, so we should already
|
||||
// have the latest state.
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass null animation handler to indicate this start is preload.
|
||||
BackgroundExecutor.get().submit(
|
||||
() -> ActivityManagerWrapper.getInstance().startRecentsActivity(
|
||||
mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
|
||||
null /* assistDataReceiver */, null /* animationHandler */,
|
||||
null /* resultCallback */, null /* resultCallbackHandler */));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (!mIsUserUnlocked) {
|
||||
return;
|
||||
}
|
||||
final ActivityControlHelper activityControl =
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
final BaseDraggingActivity activity = activityControl.getCreatedActivity();
|
||||
if (activity == null || activity.isStarted()) {
|
||||
// We only care about the existing background activity.
|
||||
return;
|
||||
}
|
||||
if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
|
||||
activity.getResources().getConfiguration().diff(newConfig))) {
|
||||
return;
|
||||
}
|
||||
|
||||
preloadOverview(false /* fromInit */);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
|
||||
if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
|
||||
|
||||
+5
@@ -19,6 +19,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI
|
||||
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
@@ -26,6 +27,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.quickstep.OverviewCallbacks;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -80,6 +82,9 @@ public class OverviewInputConsumer<T extends BaseDraggingActivity>
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
if (TestProtocol.sDebugTracing) {
|
||||
Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "onMotionEvent " + ev);
|
||||
}
|
||||
if (!mProxyTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
+51
-17
@@ -17,17 +17,22 @@ package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.touch.TouchEventTranslator;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
@@ -36,18 +41,29 @@ import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* TouchController for handling touch events that get sent to the StatusBar. Once the
|
||||
* Once the event delta y passes the touch slop, the events start getting forwarded.
|
||||
* Once the event delta mDownY passes the touch slop, the events start getting forwarded.
|
||||
* All events are offset by initial Y value of the pointer.
|
||||
*/
|
||||
public class StatusBarTouchController implements TouchController {
|
||||
|
||||
private static final String TAG = "StatusBarController";
|
||||
|
||||
/**
|
||||
* Window flag: Enable touches to slide out of a window into neighboring
|
||||
* windows in mid-gesture instead of being captured for the duration of
|
||||
* the gesture.
|
||||
*
|
||||
* This flag changes the behavior of touch focus for this window only.
|
||||
* Touches can slide out of the window but they cannot necessarily slide
|
||||
* back in (unless the other window with touch focus permits it).
|
||||
*/
|
||||
private static final int FLAG_SLIPPERY = 0x20000000;
|
||||
|
||||
protected final Launcher mLauncher;
|
||||
protected final TouchEventTranslator mTranslator;
|
||||
private final float mTouchSlop;
|
||||
private ISystemUiProxy mSysUiProxy;
|
||||
private int mLastAction;
|
||||
private final SparseArray<PointF> mDownEvents;
|
||||
|
||||
/* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
|
||||
private boolean mCanIntercept;
|
||||
@@ -56,7 +72,7 @@ public class StatusBarTouchController implements TouchController {
|
||||
mLauncher = l;
|
||||
// Guard against TAPs by increasing the touch slop.
|
||||
mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
|
||||
mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
|
||||
mDownEvents = new SparseArray<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,7 +80,6 @@ public class StatusBarTouchController implements TouchController {
|
||||
writer.println(prefix + "mCanIntercept:" + mCanIntercept);
|
||||
writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
|
||||
writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
|
||||
|
||||
}
|
||||
|
||||
private void dispatchTouchEvent(MotionEvent ev) {
|
||||
@@ -81,26 +96,31 @@ public class StatusBarTouchController implements TouchController {
|
||||
@Override
|
||||
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getActionMasked();
|
||||
int idx = ev.getActionIndex();
|
||||
int pid = ev.getPointerId(idx);
|
||||
if (action == ACTION_DOWN) {
|
||||
mCanIntercept = canInterceptTouch(ev);
|
||||
if (!mCanIntercept) {
|
||||
return false;
|
||||
}
|
||||
mTranslator.reset();
|
||||
mTranslator.setDownParameters(0, ev);
|
||||
mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
|
||||
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
// Check!! should only set it only when threshold is not entered.
|
||||
mTranslator.setDownParameters(ev.getActionIndex(), ev);
|
||||
// Check!! should only set it only when threshold is not entered.
|
||||
mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
|
||||
}
|
||||
if (!mCanIntercept) {
|
||||
return false;
|
||||
}
|
||||
if (action == ACTION_MOVE) {
|
||||
float dy = ev.getY() - mTranslator.getDownY();
|
||||
float dx = ev.getX() - mTranslator.getDownX();
|
||||
if (dy > mTouchSlop && dy > Math.abs(dx)) {
|
||||
mTranslator.dispatchDownEvents(ev);
|
||||
mTranslator.processMotionEvent(ev);
|
||||
float dy = ev.getY(idx) - mDownEvents.get(pid).y;
|
||||
float dx = ev.getX(idx) - mDownEvents.get(pid).x;
|
||||
// Currently input dispatcher will not do touch transfer if there are more than
|
||||
// one touch pointer. Hence, even if slope passed, only set the slippery flag
|
||||
// when there is single touch event. (context: InputDispatcher.cpp line 1445)
|
||||
if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) {
|
||||
ev.setAction(ACTION_DOWN);
|
||||
dispatchTouchEvent(ev);
|
||||
setWindowSlippery(true);
|
||||
return true;
|
||||
}
|
||||
if (Math.abs(dx) > mTouchSlop) {
|
||||
@@ -110,13 +130,27 @@ public class StatusBarTouchController implements TouchController {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean onControllerTouchEvent(MotionEvent ev) {
|
||||
mTranslator.processMotionEvent(ev);
|
||||
if (ev.getAction() == ACTION_UP || ev.getAction() == ACTION_CANCEL) {
|
||||
dispatchTouchEvent(ev);
|
||||
setWindowSlippery(false);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setWindowSlippery(boolean enable) {
|
||||
Window w = mLauncher.getWindow();
|
||||
WindowManager.LayoutParams wlp = w.getAttributes();
|
||||
if (enable) {
|
||||
wlp.flags |= FLAG_SLIPPERY;
|
||||
} else {
|
||||
wlp.flags &= ~FLAG_SLIPPERY;
|
||||
}
|
||||
w.setAttributes(wlp);
|
||||
}
|
||||
|
||||
private boolean canInterceptTouch(MotionEvent ev) {
|
||||
if (!mLauncher.isInState(LauncherState.NORMAL) ||
|
||||
AbstractFloatingView.getTopOpenViewWithType(mLauncher,
|
||||
@@ -132,4 +166,4 @@ public class StatusBarTouchController implements TouchController {
|
||||
mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
|
||||
return mSysUiProxy != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,15 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class to keep track of the current overview component based off user preferences and app updates
|
||||
@@ -53,22 +57,41 @@ public final class OverviewComponentObserver {
|
||||
}
|
||||
};
|
||||
private final Context mContext;
|
||||
private final ComponentName mMyHomeComponent;
|
||||
private final Intent mCurrentHomeIntent;
|
||||
private final Intent mMyHomeIntent;
|
||||
private final Intent mFallbackIntent;
|
||||
private final SparseIntArray mConfigChangesMap = new SparseIntArray();
|
||||
private String mUpdateRegisteredPackage;
|
||||
private ActivityControlHelper mActivityControlHelper;
|
||||
private Intent mOverviewIntent;
|
||||
private Intent mHomeIntent;
|
||||
private int mSystemUiStateFlags;
|
||||
private boolean mIsHomeAndOverviewSame;
|
||||
private boolean mIsDefaultHome;
|
||||
|
||||
public OverviewComponentObserver(Context context) {
|
||||
mContext = context;
|
||||
|
||||
Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setPackage(mContext.getPackageName());
|
||||
ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
|
||||
mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
|
||||
ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
|
||||
ComponentName myHomeComponent =
|
||||
new ComponentName(context.getPackageName(), info.activityInfo.name);
|
||||
mMyHomeIntent.setComponent(myHomeComponent);
|
||||
mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
|
||||
|
||||
ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
|
||||
mFallbackIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
.setComponent(fallbackComponent)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
try {
|
||||
ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
|
||||
mFallbackIntent.getComponent(), 0 /* flags */);
|
||||
mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
|
||||
|
||||
mContext.registerReceiver(mUserPreferenceChangeReceiver,
|
||||
new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
|
||||
@@ -92,17 +115,14 @@ public final class OverviewComponentObserver {
|
||||
ComponentName defaultHome = PackageManagerWrapper.getInstance()
|
||||
.getHomeActivities(new ArrayList<>());
|
||||
|
||||
final String overviewIntentCategory;
|
||||
ComponentName overviewComponent;
|
||||
mHomeIntent = null;
|
||||
|
||||
if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
|
||||
(defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
|
||||
mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
|
||||
if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
|
||||
&& (defaultHome == null || mIsDefaultHome)) {
|
||||
// User default home is same as out home app. Use Overview integrated in Launcher.
|
||||
overviewComponent = mMyHomeComponent;
|
||||
mActivityControlHelper = new LauncherActivityControllerHelper();
|
||||
mIsHomeAndOverviewSame = true;
|
||||
overviewIntentCategory = Intent.CATEGORY_HOME;
|
||||
mOverviewIntent = mMyHomeIntent;
|
||||
mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
|
||||
|
||||
if (mUpdateRegisteredPackage != null) {
|
||||
// Remove any update listener as we don't care about other packages.
|
||||
@@ -111,14 +131,11 @@ public final class OverviewComponentObserver {
|
||||
}
|
||||
} else {
|
||||
// The default home app is a different launcher. Use the fallback Overview instead.
|
||||
overviewComponent = new ComponentName(mContext, RecentsActivity.class);
|
||||
mActivityControlHelper = new FallbackActivityControllerHelper();
|
||||
mIsHomeAndOverviewSame = false;
|
||||
overviewIntentCategory = Intent.CATEGORY_DEFAULT;
|
||||
mOverviewIntent = mFallbackIntent;
|
||||
mCurrentHomeIntent.setComponent(defaultHome);
|
||||
|
||||
mHomeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setComponent(defaultHome);
|
||||
// User's default home app can change as a result of package updates of this app (such
|
||||
// as uninstalling the app or removing the "Launcher" feature in an update).
|
||||
// Listen for package updates of this app (and remove any previously attached
|
||||
@@ -138,14 +155,6 @@ public final class OverviewComponentObserver {
|
||||
ACTION_PACKAGE_REMOVED));
|
||||
}
|
||||
}
|
||||
|
||||
mOverviewIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(overviewIntentCategory)
|
||||
.setComponent(overviewComponent)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (mHomeIntent == null) {
|
||||
mHomeIntent = mOverviewIntent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,6 +169,32 @@ public final class OverviewComponentObserver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the overview component is able to handle the configuration changes.
|
||||
*/
|
||||
boolean canHandleConfigChanges(ComponentName component, int changes) {
|
||||
final int orientationChange =
|
||||
ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE;
|
||||
if ((changes & orientationChange) == orientationChange) {
|
||||
// This is just an approximate guess for simple orientation change because the changes
|
||||
// may contain non-public bits (e.g. window configuration).
|
||||
return true;
|
||||
}
|
||||
|
||||
int configMask = mConfigChangesMap.get(component.hashCode());
|
||||
return configMask != 0 && (~configMask & changes) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the intent for overview activity. It is used when lockscreen is shown and home was died
|
||||
* in background, we still want to restart the one that will be used after unlock.
|
||||
*
|
||||
* @return the overview intent
|
||||
*/
|
||||
Intent getOverviewIntentIgnoreSysUiState() {
|
||||
return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current intent for going to the overview activity.
|
||||
*
|
||||
@@ -173,7 +208,7 @@ public final class OverviewComponentObserver {
|
||||
* Get the current intent for going to the home activity.
|
||||
*/
|
||||
public Intent getHomeIntent() {
|
||||
return mHomeIntent;
|
||||
return mCurrentHomeIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,7 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest {
|
||||
mLauncher.pressHome();
|
||||
final DigitalWellBeingToast toast = getToast();
|
||||
|
||||
assertTrue("Toast is not visible", toast.hasLimit());
|
||||
waitForLauncherCondition("Toast is not visible", launcher -> toast.hasLimit());
|
||||
assertEquals("Toast text: ", "5 minutes left today", toast.getText());
|
||||
|
||||
// Unset time limit for app.
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.tapl.LauncherInstrumentation;
|
||||
import com.android.launcher3.util.RaceConditionReproducer;
|
||||
import com.android.quickstep.NavigationModeSwitchRule.Mode;
|
||||
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
|
||||
@@ -79,6 +80,8 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
|
||||
@Test
|
||||
@NavigationModeSwitch
|
||||
public void testStressPressHome() {
|
||||
if (LauncherInstrumentation.isAvd()) return; // b/136278866
|
||||
|
||||
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
|
||||
// Destroy Launcher activity.
|
||||
closeLauncherActivity();
|
||||
@@ -91,6 +94,8 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
|
||||
@Test
|
||||
@NavigationModeSwitch
|
||||
public void testStressSwipeToOverview() {
|
||||
if (LauncherInstrumentation.isAvd()) return; // b/136278866
|
||||
|
||||
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
|
||||
// Destroy Launcher activity.
|
||||
closeLauncherActivity();
|
||||
|
||||
@@ -78,4 +78,5 @@ public final class TestProtocol {
|
||||
public static final String NO_START_TAG = "b/132900132";
|
||||
public static final String NO_START_TASK_TAG = "b/133765434";
|
||||
public static final String NO_OVERVIEW_EVENT_TAG = "b/134532571";
|
||||
public static final String EVENTS_TO_OVERVIEW_MISSING_TAG = "b/133867119";
|
||||
}
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.touch;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.MotionEvent.PointerCoords;
|
||||
import android.view.MotionEvent.PointerProperties;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* To minimize the size of the MotionEvent, historic events are not copied and passed via the
|
||||
* listener.
|
||||
*/
|
||||
public class TouchEventTranslator {
|
||||
|
||||
private static final String TAG = "TouchEventTranslator";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private class DownState {
|
||||
long timeStamp;
|
||||
float downX;
|
||||
float downY;
|
||||
public DownState(long timeStamp, float downX, float downY) {
|
||||
this.timeStamp = timeStamp;
|
||||
this.downX = downX;
|
||||
this.downY = downY;
|
||||
}
|
||||
};
|
||||
private final DownState ZERO = new DownState(0, 0f, 0f);
|
||||
|
||||
private final Consumer<MotionEvent> mListener;
|
||||
|
||||
private final SparseArray<DownState> mDownEvents;
|
||||
private final SparseArray<PointF> mFingers;
|
||||
|
||||
private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache;
|
||||
|
||||
public TouchEventTranslator(Consumer<MotionEvent> listener) {
|
||||
mDownEvents = new SparseArray<>();
|
||||
mFingers = new SparseArray<>();
|
||||
mCache = new SparseArray<>();
|
||||
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mDownEvents.clear();
|
||||
mFingers.clear();
|
||||
}
|
||||
|
||||
public float getDownX() {
|
||||
return mDownEvents.get(0).downX;
|
||||
}
|
||||
|
||||
public float getDownY() {
|
||||
return mDownEvents.get(0).downY;
|
||||
}
|
||||
|
||||
public void setDownParameters(int idx, MotionEvent e) {
|
||||
DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
|
||||
mDownEvents.append(idx, ev);
|
||||
}
|
||||
|
||||
public void dispatchDownEvents(MotionEvent ev) {
|
||||
for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
|
||||
int pid = ev.getPointerId(i);
|
||||
put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
|
||||
}
|
||||
}
|
||||
|
||||
public void processMotionEvent(MotionEvent ev) {
|
||||
if (DEBUG) {
|
||||
printSamples(TAG + " processMotionEvent", ev);
|
||||
}
|
||||
int index = ev.getActionIndex();
|
||||
float x = ev.getX(index);
|
||||
float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY;
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
int pid = ev.getPointerId(index);
|
||||
if(mFingers.get(pid, null) != null) {
|
||||
for(int i=0; i < ev.getPointerCount(); i++) {
|
||||
pid = ev.getPointerId(i);
|
||||
position(pid, x, y);
|
||||
}
|
||||
generateEvent(ev.getAction(), ev);
|
||||
} else {
|
||||
put(pid, index, x, y, ev);
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for(int i=0; i < ev.getPointerCount(); i++) {
|
||||
pid = ev.getPointerId(i);
|
||||
position(pid, x, y);
|
||||
}
|
||||
generateEvent(ev.getAction(), ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_UP:
|
||||
pid = ev.getPointerId(index);
|
||||
lift(pid, index, x, y, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
cancel(ev);
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Didn't process ");
|
||||
printSamples(TAG, ev);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
|
||||
return put(id, index, x, y, ev.getEventTime(), ev);
|
||||
}
|
||||
|
||||
private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
|
||||
checkFingerExistence(id, false);
|
||||
boolean isInitialDown = (mFingers.size() == 0);
|
||||
|
||||
mFingers.put(id, new PointF(x, y));
|
||||
int n = mFingers.size();
|
||||
|
||||
if (mCache.get(n) == null) {
|
||||
PointerProperties[] properties = new PointerProperties[n];
|
||||
PointerCoords[] coords = new PointerCoords[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
properties[i] = new PointerProperties();
|
||||
coords[i] = new PointerCoords();
|
||||
}
|
||||
mCache.put(n, new Pair(properties, coords));
|
||||
}
|
||||
|
||||
int action;
|
||||
if (isInitialDown) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else {
|
||||
action = MotionEvent.ACTION_POINTER_DOWN;
|
||||
// Set the id of the changed pointer.
|
||||
action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
}
|
||||
generateEvent(action, ms, ev);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchEventTranslator position(int id, float x, float y) {
|
||||
checkFingerExistence(id, true);
|
||||
mFingers.get(id).set(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
private TouchEventTranslator lift(int id, int index, MotionEvent ev) {
|
||||
checkFingerExistence(id, true);
|
||||
boolean isFinalUp = (mFingers.size() == 1);
|
||||
int action;
|
||||
if (isFinalUp) {
|
||||
action = MotionEvent.ACTION_UP;
|
||||
} else {
|
||||
action = MotionEvent.ACTION_POINTER_UP;
|
||||
// Set the id of the changed pointer.
|
||||
action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
}
|
||||
generateEvent(action, ev);
|
||||
mFingers.remove(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) {
|
||||
checkFingerExistence(id, true);
|
||||
mFingers.get(id).set(x, y);
|
||||
return lift(id, index, ev);
|
||||
}
|
||||
|
||||
public TouchEventTranslator cancel(MotionEvent ev) {
|
||||
generateEvent(MotionEvent.ACTION_CANCEL, ev);
|
||||
mFingers.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void checkFingerExistence(int id, boolean shouldExist) {
|
||||
if (shouldExist != (mFingers.get(id, null) != null)) {
|
||||
throw new IllegalArgumentException(
|
||||
shouldExist ? "Finger does not exist" : "Finger already exists");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to debug MotionEvents being sent/received.
|
||||
*/
|
||||
public void printSamples(String msg, MotionEvent ev) {
|
||||
System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
|
||||
final int pointerCount = ev.getPointerCount();
|
||||
System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
|
||||
System.out.printf(" t=%d:", ev.getEventTime());
|
||||
for (int p = 0; p < pointerCount; p++) {
|
||||
System.out.printf(" id=%d: (%f,%f)",
|
||||
ev.getPointerId(p), ev.getX(p), ev.getY(p));
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private void generateEvent(int action, MotionEvent ev) {
|
||||
generateEvent(action, ev.getEventTime(), ev);
|
||||
}
|
||||
|
||||
private void generateEvent(int action, long ms, MotionEvent ev) {
|
||||
Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
|
||||
MotionEvent event = MotionEvent.obtain(
|
||||
mDownEvents.get(0).timeStamp,
|
||||
ms,
|
||||
action,
|
||||
state.first.length,
|
||||
state.first,
|
||||
state.second,
|
||||
ev.getMetaState(),
|
||||
ev.getButtonState() /* buttonState */,
|
||||
ev.getXPrecision() /* xPrecision */,
|
||||
ev.getYPrecision() /* yPrecision */,
|
||||
ev.getDeviceId(),
|
||||
ev.getEdgeFlags(),
|
||||
ev.getSource(),
|
||||
ev.getFlags() /* flags */);
|
||||
if (DEBUG) {
|
||||
printSamples(TAG + " generateEvent", event);
|
||||
}
|
||||
if (event.getPointerId(event.getActionIndex()) < 0) {
|
||||
printSamples(TAG + "generateEvent", event);
|
||||
throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
|
||||
}
|
||||
mListener.accept(event);
|
||||
event.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the fingers' state expected by MotionEvent.
|
||||
*/
|
||||
private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
|
||||
int nFingers = mFingers.size();
|
||||
|
||||
Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers);
|
||||
PointerProperties[] properties = result.first;
|
||||
PointerCoords[] coordinates = result.second;
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < mFingers.size(); i++) {
|
||||
int id = mFingers.keyAt(i);
|
||||
PointF location = mFingers.get(id);
|
||||
|
||||
PointerProperties property = properties[i];
|
||||
property.id = id;
|
||||
property.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||
properties[index] = property;
|
||||
|
||||
PointerCoords coordinate = coordinates[i];
|
||||
coordinate.x = location.x;
|
||||
coordinate.y = location.y;
|
||||
coordinate.pressure = 1.0f;
|
||||
coordinates[index] = coordinate;
|
||||
|
||||
index++;
|
||||
}
|
||||
return mCache.get(nFingers);
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,9 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
|
||||
}
|
||||
|
||||
private TouchController findControllerToHandleTouch(MotionEvent ev) {
|
||||
if (TestProtocol.sDebugTracing) {
|
||||
Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "findControllerToHandleTouch " + ev);
|
||||
}
|
||||
if (shouldDisableGestures(ev)) return null;
|
||||
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
||||
@@ -314,6 +317,9 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
|
||||
* Proxies the touch events to the gesture handlers
|
||||
*/
|
||||
public boolean proxyTouchEvent(MotionEvent ev) {
|
||||
if (TestProtocol.sDebugTracing) {
|
||||
Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "proxyTouchEvent " + ev);
|
||||
}
|
||||
boolean handled;
|
||||
if (mProxyTouchController != null) {
|
||||
handled = mProxyTouchController.onControllerTouchEvent(ev);
|
||||
|
||||
@@ -20,7 +20,6 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import static java.lang.System.exit;
|
||||
|
||||
@@ -38,10 +37,7 @@ import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.BySelector;
|
||||
import androidx.test.uiautomator.Direction;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
import androidx.test.uiautomator.Until;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
@@ -50,12 +46,12 @@ import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.ResourceUtils;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
import com.android.launcher3.model.AppLaunchTracker;
|
||||
import com.android.launcher3.tapl.LauncherInstrumentation;
|
||||
import com.android.launcher3.tapl.TestHelpers;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.FailureWatcher;
|
||||
import com.android.launcher3.util.rule.LauncherActivityRule;
|
||||
@@ -86,8 +82,7 @@ public abstract class AbstractLauncherUiTest {
|
||||
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
|
||||
|
||||
public static final long SHORT_UI_TIMEOUT = 300;
|
||||
public static final long DEFAULT_UI_TIMEOUT = 10000;
|
||||
public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
|
||||
private static final String TAG = "AbstractLauncherUiTest";
|
||||
|
||||
protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
|
||||
@@ -116,6 +111,23 @@ public abstract class AbstractLauncherUiTest {
|
||||
public ShellCommandRule mDisableHeadsUpNotification =
|
||||
ShellCommandRule.disableHeadsUpNotification();
|
||||
|
||||
protected void clearPackageData(String pkg) throws IOException, InterruptedException {
|
||||
final CountDownLatch count = new CountDownLatch(2);
|
||||
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
count.countDown();
|
||||
}
|
||||
};
|
||||
mTargetContext.registerReceiver(broadcastReceiver,
|
||||
PackageManagerHelper.getPackageFilter(pkg,
|
||||
Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED));
|
||||
|
||||
mDevice.executeShellCommand("pm clear " + pkg);
|
||||
assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
|
||||
mTargetContext.unregisterReceiver(broadcastReceiver);
|
||||
}
|
||||
|
||||
// Annotation for tests that need to be run in portrait and landscape modes.
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@@ -170,38 +182,13 @@ public abstract class AbstractLauncherUiTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected void clearLauncherData() throws IOException {
|
||||
protected void clearLauncherData() throws IOException, InterruptedException {
|
||||
if (TestHelpers.isInLauncherProcess()) {
|
||||
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
resetLoaderState();
|
||||
} else {
|
||||
mDevice.executeShellCommand("pm clear " + mDevice.getLauncherPackageName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the {@param container} until it finds an object matching {@param condition}.
|
||||
*
|
||||
* @return the matching object.
|
||||
*/
|
||||
protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
|
||||
final int margin = ResourceUtils.getNavbarSize(
|
||||
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
|
||||
container.setGestureMargins(0, 0, 0, margin);
|
||||
|
||||
int i = 0;
|
||||
for (; ; ) {
|
||||
// findObject can only execute after spring settles.
|
||||
mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
|
||||
UiObject2 widget = container.findObject(condition);
|
||||
if (widget != null && widget.getVisibleBounds().intersects(
|
||||
0, 0, mDevice.getDisplayWidth(),
|
||||
mDevice.getDisplayHeight() - margin)) {
|
||||
return widget;
|
||||
}
|
||||
if (++i > 40) fail("Too many attempts");
|
||||
container.scroll(Direction.DOWN, 1f);
|
||||
clearPackageData(mDevice.getLauncherPackageName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +216,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
|
||||
final AppIcon app = allApps.getAppIcon("TestActivity7");
|
||||
assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
|
||||
test.executeOnLauncher(launcher -> assertTrue(
|
||||
"Launcher activity is the top activity; expecting another activity to be the top "
|
||||
"Launcher activity is the top activity; expecting another activity to be the "
|
||||
+ "top "
|
||||
+ "one",
|
||||
test.isInBackground(launcher)));
|
||||
} finally {
|
||||
@@ -304,11 +305,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
|
||||
switchToAllApps();
|
||||
allApps.freeze();
|
||||
try {
|
||||
allApps.
|
||||
getAppIcon(APP_NAME).
|
||||
dragToWorkspace().
|
||||
getWorkspaceAppIcon(APP_NAME).
|
||||
launch(getAppPackageName());
|
||||
allApps.getAppIcon(APP_NAME).dragToWorkspace();
|
||||
mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
|
||||
} finally {
|
||||
allApps.unfreeze();
|
||||
}
|
||||
@@ -335,13 +333,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
|
||||
getMenuItem(0);
|
||||
final String shortcutName = menuItem.getText();
|
||||
|
||||
// 4. Drag the first shortcut to the home screen.
|
||||
// 5. Verify that the shortcut works on home screen
|
||||
// (the app opens and has the same text as the shortcut).
|
||||
menuItem.
|
||||
dragToWorkspace().
|
||||
getWorkspaceAppIcon(shortcutName).
|
||||
launch(getAppPackageName());
|
||||
menuItem.dragToWorkspace();
|
||||
mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
|
||||
} finally {
|
||||
allApps.unfreeze();
|
||||
}
|
||||
|
||||
@@ -18,28 +18,15 @@ package com.android.launcher3.ui;
|
||||
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static androidx.test.InstrumentationRegistry.getTargetContext;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.BySelector;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
import androidx.test.uiautomator.Until;
|
||||
|
||||
import com.android.launcher3.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.compat.AppWidgetManagerCompat;
|
||||
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
|
||||
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
|
||||
@@ -50,21 +37,6 @@ import java.util.function.Function;
|
||||
public class TestViewHelpers {
|
||||
private static final String TAG = "TestViewHelpers";
|
||||
|
||||
private static UiDevice getDevice() {
|
||||
return UiDevice.getInstance(getInstrumentation());
|
||||
}
|
||||
|
||||
public static UiObject2 findViewById(int id) {
|
||||
return getDevice().wait(Until.findObject(getSelectorForId(id)),
|
||||
AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
|
||||
}
|
||||
|
||||
public static BySelector getSelectorForId(int id) {
|
||||
final Context targetContext = getTargetContext();
|
||||
String name = targetContext.getResources().getResourceEntryName(id);
|
||||
return By.res(targetContext.getPackageName(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a widget provider which can fit on the home screen.
|
||||
*
|
||||
@@ -91,92 +63,6 @@ public class TestViewHelpers {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drags an icon to the center of homescreen.
|
||||
*
|
||||
* @param icon object that is either app icon or shortcut icon
|
||||
*/
|
||||
public static void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
|
||||
Point center = icon.getVisibleCenter();
|
||||
|
||||
// Action Down
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
sendPointer(downTime, MotionEvent.ACTION_DOWN, center);
|
||||
|
||||
UiObject2 dragLayer = findViewById(R.id.drag_layer);
|
||||
|
||||
if (expectedToShowShortcuts) {
|
||||
// Make sure shortcuts show up, and then move a bit to hide them.
|
||||
assertNotNull(findViewById(R.id.deep_shortcuts_container));
|
||||
|
||||
Point moveLocation = new Point(center);
|
||||
int distanceToMove =
|
||||
getTargetContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.deep_shortcuts_start_drag_threshold) + 50;
|
||||
if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
|
||||
moveLocation.y -= distanceToMove;
|
||||
} else {
|
||||
moveLocation.y += distanceToMove;
|
||||
}
|
||||
movePointer(downTime, center, moveLocation);
|
||||
|
||||
assertNull(findViewById(R.id.deep_shortcuts_container));
|
||||
}
|
||||
|
||||
// Wait until Remove/Delete target is visible
|
||||
assertNotNull(findViewById(R.id.delete_target_text));
|
||||
|
||||
Point moveLocation = dragLayer.getVisibleCenter();
|
||||
|
||||
// Move to center
|
||||
movePointer(downTime, center, moveLocation);
|
||||
sendPointer(downTime, MotionEvent.ACTION_UP, moveLocation);
|
||||
|
||||
// Wait until remove target is gone.
|
||||
getDevice().wait(Until.gone(getSelectorForId(R.id.delete_target_text)),
|
||||
AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
|
||||
}
|
||||
|
||||
private static void movePointer(long downTime, Point from, Point to) {
|
||||
while (!from.equals(to)) {
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
from.x = getNextMoveValue(to.x, from.x);
|
||||
from.y = getNextMoveValue(to.y, from.y);
|
||||
sendPointer(downTime, MotionEvent.ACTION_MOVE, from);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getNextMoveValue(int targetValue, int oldValue) {
|
||||
if (targetValue - oldValue > 10) {
|
||||
return oldValue + 10;
|
||||
} else if (targetValue - oldValue < -10) {
|
||||
return oldValue - 10;
|
||||
} else {
|
||||
return targetValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendPointer(long downTime, int action, Point point) {
|
||||
MotionEvent event = MotionEvent.obtain(downTime,
|
||||
SystemClock.uptimeMillis(), action, point.x, point.y, 0);
|
||||
getInstrumentation().sendPointerSync(event);
|
||||
event.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens widget tray and returns the recycler view.
|
||||
*/
|
||||
public static UiObject2 openWidgetsTray() {
|
||||
final UiDevice device = getDevice();
|
||||
device.pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
|
||||
device.waitForIdle();
|
||||
return findViewById(R.id.widgets_list_view);
|
||||
}
|
||||
|
||||
public static View findChildView(ViewGroup parent, Function<View, Boolean> condition) {
|
||||
for (int i = 0; i < parent.getChildCount(); ++i) {
|
||||
final View child = parent.getChildAt(i);
|
||||
|
||||
@@ -27,20 +27,18 @@ import android.view.View;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.tapl.Widgets;
|
||||
import com.android.launcher3.testcomponent.WidgetConfigActivity;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.ui.TestViewHelpers;
|
||||
import com.android.launcher3.util.Condition;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.ShellCommandRule;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
@@ -71,7 +69,6 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
// Convert test to TAPL b/131116002
|
||||
public void testWidgetConfig() throws Throwable {
|
||||
runTest(false, true);
|
||||
}
|
||||
@@ -83,7 +80,6 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
// Convert test to TAPL b/131116002
|
||||
public void testConfigCancelled() throws Throwable {
|
||||
runTest(false, false);
|
||||
}
|
||||
@@ -104,15 +100,13 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
|
||||
clearHomescreen();
|
||||
mActivityMonitor.startLauncher();
|
||||
|
||||
// Open widget tray and wait for load complete.
|
||||
final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
|
||||
Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
|
||||
final Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
|
||||
|
||||
// Drag widget to homescreen
|
||||
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
|
||||
UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
|
||||
.hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
|
||||
TestViewHelpers.dragToWorkspace(widget, false);
|
||||
widgets.
|
||||
getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
|
||||
dragToWorkspace();
|
||||
// Widget id for which the config activity was opened
|
||||
mWidgetId = monitor.getWidgetId();
|
||||
|
||||
|
||||
@@ -19,20 +19,12 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.Workspace.ItemOperator;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.ui.TestViewHelpers;
|
||||
import com.android.launcher3.util.Condition;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.ShellCommandRule;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
@@ -61,7 +53,6 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
|
||||
performTest();
|
||||
}
|
||||
|
||||
// Convert to TAPL b/131116002
|
||||
private void performTest() throws Throwable {
|
||||
clearHomescreen();
|
||||
mActivityMonitor.startLauncher();
|
||||
@@ -69,22 +60,15 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
|
||||
final LauncherAppWidgetProviderInfo widgetInfo =
|
||||
TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
|
||||
|
||||
// Open widget tray and wait for load complete.
|
||||
final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
|
||||
Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
|
||||
mLauncher.
|
||||
getWorkspace().
|
||||
openAllWidgets().
|
||||
getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
|
||||
dragToWorkspace();
|
||||
|
||||
// Drag widget to homescreen
|
||||
UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
|
||||
.hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
|
||||
TestViewHelpers.dragToWorkspace(widget, false);
|
||||
|
||||
assertTrue(mActivityMonitor.itemExists(new ItemOperator() {
|
||||
@Override
|
||||
public boolean evaluate(ItemInfo info, View view) {
|
||||
return info instanceof LauncherAppWidgetInfo &&
|
||||
assertTrue(mActivityMonitor.itemExists(
|
||||
(info, view) -> info instanceof LauncherAppWidgetInfo &&
|
||||
((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
|
||||
widgetInfo.provider.getClassName());
|
||||
}
|
||||
}).call());
|
||||
widgetInfo.provider.getClassName())).call());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@
|
||||
>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
|
||||
</manifest>
|
||||
|
||||
@@ -181,6 +181,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer {
|
||||
* Flings backward (up) and waits the fling's end.
|
||||
*/
|
||||
public void flingBackward() {
|
||||
mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
|
||||
try (LauncherInstrumentation.Closable c =
|
||||
mLauncher.addContextLayer("want to fling backward in all apps")) {
|
||||
final UiObject2 allAppsContainer = verifyActiveContainer();
|
||||
@@ -189,6 +190,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer {
|
||||
allAppsContainer, Direction.UP, 1, new Rect(0, mHeight / 2, 0, 0), 10);
|
||||
verifyActiveContainer();
|
||||
}
|
||||
mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,7 +68,7 @@ abstract class Launchable {
|
||||
/**
|
||||
* Drags an object to the center of homescreen.
|
||||
*/
|
||||
public Workspace dragToWorkspace() {
|
||||
public void dragToWorkspace() {
|
||||
final Point launchableCenter = getObject().getVisibleCenter();
|
||||
final Point displaySize = mLauncher.getRealDisplaySize();
|
||||
final int width = displaySize.x / 2;
|
||||
@@ -80,10 +80,6 @@ abstract class Launchable {
|
||||
launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
|
||||
displaySize.y / 2),
|
||||
getLongPressIndicator());
|
||||
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
||||
"dragged launchable to workspace")) {
|
||||
return new Workspace(mLauncher);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String getLongPressIndicator();
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.DropBoxManager;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
@@ -263,10 +264,122 @@ public final class LauncherInstrumentation {
|
||||
}
|
||||
}
|
||||
|
||||
private String getAnomalyMessage() {
|
||||
final UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
|
||||
if (object != null) {
|
||||
return "System alert popup is visible: " + object.getText();
|
||||
}
|
||||
|
||||
if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
|
||||
|
||||
if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getVisibleStateMessage() {
|
||||
if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
|
||||
if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
|
||||
if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
|
||||
if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
|
||||
return "Background";
|
||||
}
|
||||
|
||||
private static String truncateCrash(String text, int maxLines) {
|
||||
String[] lines = text.split("\\r?\\n");
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (int i = 0; i < maxLines && i < lines.length; i++) {
|
||||
ret.append(lines[i]);
|
||||
ret.append('\n');
|
||||
}
|
||||
if (lines.length > maxLines) {
|
||||
ret.append("... ");
|
||||
ret.append(lines.length - maxLines);
|
||||
ret.append(" more lines truncated ...\n");
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
private String checkCrash(String label) {
|
||||
DropBoxManager dropbox = (DropBoxManager) getContext().getSystemService(
|
||||
Context.DROPBOX_SERVICE);
|
||||
Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
|
||||
|
||||
long timestamp = 0;
|
||||
DropBoxManager.Entry entry;
|
||||
int crashCount = 0;
|
||||
StringBuilder errorDetails = new StringBuilder();
|
||||
while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
|
||||
String dropboxSnippet;
|
||||
try {
|
||||
dropboxSnippet = entry.getText(4096);
|
||||
} finally {
|
||||
entry.close();
|
||||
}
|
||||
|
||||
crashCount++;
|
||||
errorDetails.append(label);
|
||||
errorDetails.append(": ");
|
||||
errorDetails.append(truncateCrash(dropboxSnippet, 40));
|
||||
errorDetails.append(" ...\n");
|
||||
|
||||
timestamp = entry.getTimeMillis();
|
||||
}
|
||||
Assert.assertEquals(errorDetails.toString(), 0, crashCount);
|
||||
return crashCount > 0 ? errorDetails.toString() : null;
|
||||
}
|
||||
|
||||
private String getSystemHealthMessage() {
|
||||
try {
|
||||
StringBuilder errors = new StringBuilder();
|
||||
|
||||
final String testPackage = getContext().getPackageName();
|
||||
try {
|
||||
mDevice.executeShellCommand("pm grant " + testPackage +
|
||||
" android.permission.READ_LOGS");
|
||||
mDevice.executeShellCommand("pm grant " + testPackage +
|
||||
" android.permission.PACKAGE_USAGE_STATS");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
final String[] labels = {
|
||||
"system_server_crash",
|
||||
"system_server_native_crash",
|
||||
"system_server_anr",
|
||||
};
|
||||
|
||||
for (String label : labels) {
|
||||
final String crash = checkCrash(label);
|
||||
if (crash != null) errors.append(crash);
|
||||
}
|
||||
|
||||
return errors.length() != 0 ? errors.toString() : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(String message) {
|
||||
log("Hierarchy dump for: " + getContextDescription() + message);
|
||||
message = "http://go/tapl : " + getContextDescription() + message;
|
||||
|
||||
final String anomaly = getAnomalyMessage();
|
||||
if (anomaly != null) {
|
||||
message = anomaly + ", which causes:\n" + message;
|
||||
} else {
|
||||
message = message + " (visible state: " + getVisibleStateMessage() + ")";
|
||||
}
|
||||
|
||||
final String systemHealth = getSystemHealthMessage();
|
||||
if (systemHealth != null) {
|
||||
message = message + ", which might be a consequence of system health problems:\n<<<\n"
|
||||
+ systemHealth + "\n>>>";
|
||||
}
|
||||
|
||||
log("Hierarchy dump for: " + message);
|
||||
dumpViewHierarchy();
|
||||
Assert.fail("http://go/tapl : " + getContextDescription() + message);
|
||||
|
||||
Assert.fail(message);
|
||||
}
|
||||
|
||||
private String getContextDescription() {
|
||||
@@ -333,6 +446,17 @@ public final class LauncherInstrumentation {
|
||||
private UiObject2 verifyContainerType(ContainerType containerType) {
|
||||
assertEquals("Unexpected display rotation",
|
||||
mExpectedRotation, mDevice.getDisplayRotation());
|
||||
|
||||
// b/136278866
|
||||
for (int i = 0; i != 100; ++i) {
|
||||
if (getNavigationModeMismatchError() == null) break;
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
final String error = getNavigationModeMismatchError();
|
||||
assertTrue(error, error == null);
|
||||
log("verifyContainerType: " + containerType);
|
||||
@@ -614,12 +738,12 @@ public final class LauncherInstrumentation {
|
||||
|
||||
@NonNull
|
||||
UiObject2 waitForLauncherObject(BySelector selector) {
|
||||
return waitForObjectBySelector(selector.pkg(getLauncherPackageName()));
|
||||
return waitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
|
||||
return tryWaitForObjectBySelector(selector.pkg(getLauncherPackageName()), timeout);
|
||||
return tryWaitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()), timeout);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -18,7 +18,16 @@ package com.android.launcher3.tapl;
|
||||
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
|
||||
public class Widget {
|
||||
Widget(LauncherInstrumentation launcher, UiObject2 widget) {
|
||||
/**
|
||||
* Widget in workspace or a widget list.
|
||||
*/
|
||||
public final class Widget extends Launchable {
|
||||
Widget(LauncherInstrumentation launcher, UiObject2 icon) {
|
||||
super(launcher, icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLongPressIndicator() {
|
||||
return "drop_target_bar";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
package com.android.launcher3.tapl;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.BySelector;
|
||||
import androidx.test.uiautomator.Direction;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
|
||||
@@ -75,4 +81,27 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
protected LauncherInstrumentation.ContainerType getContainerType() {
|
||||
return LauncherInstrumentation.ContainerType.WIDGETS;
|
||||
}
|
||||
|
||||
public Widget getWidget(String label) {
|
||||
final int margin = ResourceUtils.getNavbarSize(
|
||||
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
|
||||
final UiObject2 widgetsContainer = verifyActiveContainer();
|
||||
widgetsContainer.setGestureMargins(0, 0, 0, margin);
|
||||
|
||||
final Point displaySize = mLauncher.getRealDisplaySize();
|
||||
|
||||
int i = 0;
|
||||
final BySelector selector = By.
|
||||
clazz("com.android.launcher3.widget.WidgetCell").
|
||||
hasDescendant(By.text(label));
|
||||
|
||||
for (; ; ) {
|
||||
final UiObject2 widget = mLauncher.tryWaitForLauncherObject(selector, 300);
|
||||
if (widget != null && widget.getVisibleBounds().bottom <= displaySize.y - margin) {
|
||||
return new Widget(mLauncher, widget);
|
||||
}
|
||||
if (++i > 40) fail("Too many attempts");
|
||||
widgetsContainer.scroll(Direction.DOWN, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user