Snap for 11565217 from be65a75e82 to 24Q3-release

Change-Id: I5904f5fa53134d5bddafd79e71218f39cc19ea0c
This commit is contained in:
Android Build Coastguard Worker
2024-03-12 23:21:05 +00:00
30 changed files with 195 additions and 345 deletions
@@ -135,8 +135,7 @@ public class QuickstepAtomicAnimationFactory extends
config.duration = Math.max(config.duration, scrollDuration);
// Sync scroll so that it ends before or at the same time as the taskbar animation.
if (DisplayController.isTransientTaskbar(mActivity)
&& mActivity.getDeviceProfile().isTaskbarPresent) {
if (mActivity.getDeviceProfile().isTaskbarPresent) {
config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
}
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
@@ -328,11 +328,15 @@ public interface TaskShortcutFactory {
// No "save app pair" menu item if:
// - app pairs feature is not enabled
// - we are in 3p launcher
// - the task in question is a single task
// - at least one app in app pair is unpinnable
// - the Overview Actions Button should be visible
if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks()
|| hasUnpinnableApp || shouldShowActionsButtonInstead) {
if (!FeatureFlags.enableAppPairs()
|| !recentsView.supportsAppPairs()
|| !taskView.containsMultipleTasks()
|| hasUnpinnableApp
|| shouldShowActionsButtonInstead) {
return null;
}
@@ -40,7 +40,6 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -734,15 +733,17 @@ public class TouchInteractionService extends Service {
// an ACTION_HOVER_ENTER will fire as well.
boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
&& isHoverActionWithoutConsumer(event);
if (mTaskAnimationManager.isRecentsAnimationStartPending()
&& (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
ActiveGestureLog.INSTANCE.addLog(
new CompoundString("TIS.onInputEvent: ")
.append("Cannot process input event: a recents animation has been ")
.append("requested, but hasn't started."),
RECENTS_ANIMATION_START_PENDING);
return;
}
// TODO(b/285636175): Uncomment this once WM can properly guarantee all animation callbacks
// if (mTaskAnimationManager.isRecentsAnimationStartPending()
// && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
// ActiveGestureLog.INSTANCE.addLog(
// new CompoundString("TIS.onInputEvent: ")
// .append("Cannot process input event: a recents animation has been ")
// .append("requested, but hasn't started."),
// RECENTS_ANIMATION_START_PENDING);
// return;
// }
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
@@ -45,8 +45,7 @@ public class FallbackNavBarTouchController implements TouchController,
NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
DisplayController.INSTANCE.get(mActivity).getInfo());
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
true /* disableHorizontalSwipe */, navBarPosition,
null /* onInterceptTouch */, this);
true /* disableHorizontalSwipe */, navBarPosition, this);
} else {
mTriggerSwipeUpTracker = null;
}
@@ -78,7 +77,4 @@ public class FallbackNavBarTouchController implements TouchController,
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
}
@Override
public void onSwipeUpCancelled() {}
}
@@ -302,4 +302,10 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
protected boolean canLaunchFullscreenTask() {
return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
}
/** Returns if app pairs are supported in this launcher. */
@Override
public boolean supportsAppPairs() {
return false;
}
}
@@ -51,7 +51,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer,
mGestureState = gestureState;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
deviceState.getNavBarPosition(), this::onInterceptTouch, this);
deviceState.getNavBarPosition(), this);
}
@Override
@@ -69,7 +69,8 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer,
mTriggerSwipeUpTracker.onMotionEvent(ev);
}
private void onInterceptTouch() {
@Override
public void onSwipeUpTouchIntercepted() {
if (mInputMonitor != null) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -93,7 +94,4 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer,
.build())
.log(LAUNCHER_HOME_GESTURE);
}
@Override
public void onSwipeUpCancelled() {}
}
@@ -54,7 +54,7 @@ public class SysUiOverlayInputConsumer implements InputConsumer,
mContext = context;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
deviceState.getNavBarPosition(), this::onInterceptTouch, this);
deviceState.getNavBarPosition(), this);
}
@Override
@@ -72,7 +72,8 @@ public class SysUiOverlayInputConsumer implements InputConsumer,
mTriggerSwipeUpTracker.onMotionEvent(ev);
}
private void onInterceptTouch() {
@Override
public void onSwipeUpTouchIntercepted() {
if (mInputMonitor != null) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -88,9 +89,4 @@ public class SysUiOverlayInputConsumer implements InputConsumer,
Log.e(TAG, "Exception calling closeSystemDialogs " + e.getMessage());
}
}
@Override
public void onSwipeUpCancelled() {
}
}
@@ -65,7 +65,7 @@ public class NavBarGestureHandler implements OnTouchListener,
mSwipeUpTouchTracker =
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
null /*onInterceptTouch*/, this);
this);
mMotionPauseDetector = new MotionPauseDetector(context);
final Resources resources = context.getResources();
@@ -28,6 +28,8 @@ import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import androidx.annotation.NonNull;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -41,21 +43,20 @@ public class TriggerSwipeUpTouchTracker {
private final float mMinFlingVelocity;
private final boolean mDisableHorizontalSwipe;
private final NavBarPosition mNavBarPosition;
private final Runnable mOnInterceptTouch;
@NonNull
private final OnSwipeUpListener mOnSwipeUp;
private boolean mInterceptedTouch;
private VelocityTracker mVelocityTracker;
public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
NavBarPosition navBarPosition, Runnable onInterceptTouch,
OnSwipeUpListener onSwipeUp) {
NavBarPosition navBarPosition, @NonNull OnSwipeUpListener onSwipeUp) {
mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
mMinFlingVelocity = context.getResources().getDimension(
R.dimen.quickstep_fling_threshold_speed);
mNavBarPosition = navBarPosition;
mDisableHorizontalSwipe = disableHorizontalSwipe;
mOnInterceptTouch = onInterceptTouch;
mOnSwipeUp = onSwipeUp;
init();
@@ -103,10 +104,7 @@ public class TriggerSwipeUpTouchTracker {
}
mInterceptedTouch = true;
if (mOnInterceptTouch != null) {
mOnInterceptTouch.run();
}
mOnSwipeUp.onSwipeUpTouchIntercepted();
}
}
break;
@@ -124,7 +122,8 @@ public class TriggerSwipeUpTouchTracker {
}
}
private void endTouchTracking() {
/** Finishes the tracking. All events after this call are ignored */
public void endTouchTracking() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -151,12 +150,10 @@ public class TriggerSwipeUpTouchTracker {
isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
}
if (mOnSwipeUp != null) {
if (isSwipeUp) {
mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
} else {
mOnSwipeUp.onSwipeUpCancelled();
}
if (isSwipeUp) {
mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
} else {
mOnSwipeUp.onSwipeUpCancelled();
}
}
@@ -172,6 +169,9 @@ public class TriggerSwipeUpTouchTracker {
void onSwipeUp(boolean wasFling, PointF finalVelocity);
/** Called on touch up if a swipe up was not detected. */
void onSwipeUpCancelled();
default void onSwipeUpCancelled() { }
/** Called when the touch for swipe up is intercepted. */
default void onSwipeUpTouchIntercepted() { }
}
}
@@ -106,6 +106,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
public @interface AppPairButtonHiddenFlags { }
public static final int FLAG_SINGLE_TASK_HIDE_APP_PAIR = 1 << 0;
public static final int FLAG_SMALL_SCREEN_HIDE_APP_PAIR = 1 << 1;
public static final int FLAG_3P_LAUNCHER_HIDE_APP_PAIR = 1 << 2;
private MultiValueAlpha mMultiValueAlpha;
@@ -254,6 +255,13 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, isSmallScreen);
}
/**
* Updates flags to hide and show actions buttons for 1p/3p launchers.
*/
public void updateFor3pLauncher(boolean is3pLauncher) {
updateAppPairButtonHiddenFlags(FLAG_3P_LAUNCHER_HIDE_APP_PAIR, is3pLauncher);
}
/**
* Updates the proper flags to indicate whether the "Screenshot" button should be hidden.
*
@@ -4022,6 +4022,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mActionsView.updateForGroupedTask(isCurrentSplit);
// Update flags to see if actions bar should show buttons for tablets or phones.
mActionsView.updateForSmallScreen(!mActivity.getDeviceProfile().isTablet);
// Update flags for 1p/3p launchers
mActionsView.updateFor3pLauncher(!supportsAppPairs());
if (isDesktopModeSupported()) {
boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
@@ -4029,6 +4031,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
}
/** Returns if app pairs are supported in this launcher. Overridden in subclasses. */
public boolean supportsAppPairs() {
return true;
}
/**
* Returns all the tasks in the top row, without the focused task
*/
@@ -5162,9 +5169,13 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
public float getMaxScaleForFullScreen() {
if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
if (mLastComputedCarouselTaskSize.isEmpty()) {
mSizeStrategy.calculateCarouselTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedCarouselTaskSize, getPagedOrientationHandler());
}
mTempRect.set(mLastComputedCarouselTaskSize);
} else {
if (mLastComputedTaskSize.height() == 0 || mLastComputedTaskSize.width() == 0) {
if (mLastComputedTaskSize.isEmpty()) {
getTaskSize(mLastComputedTaskSize);
}
mTempRect.set(mLastComputedTaskSize);
@@ -30,6 +30,8 @@ import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncest
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -861,6 +863,7 @@ public class TaskView extends FrameLayout implements Reusable {
@Nullable
public RunnableList launchTaskAnimated() {
if (mTask != null) {
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTaskAnimated");
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);
@@ -909,6 +912,7 @@ public class TaskView extends FrameLayout implements Reusable {
*/
public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
if (mTask != null) {
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTaskAnimated");
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -37,6 +39,8 @@ import android.app.prediction.AppTargetId;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
@@ -81,6 +85,8 @@ public final class WidgetsPredicationUpdateTaskTest {
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
private LauncherApps mLauncherApps;
@Before
public void setup() throws Exception {
@@ -103,12 +109,18 @@ public final class WidgetsPredicationUpdateTaskTest {
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
mApp4Provider1, mApp4Provider2, mApp5Provider1);
mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
String pkg = i.getArgument(0);
return ApplicationInfoBuilder.newBuilder().setPackageName(pkg).setName(
"App " + pkg).build();
}).when(mModelHelper.sandboxContext.getPackageManager())
.getApplicationInfo(anyString(), anyInt());
ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
.setPackageName(pkg)
.setName("App " + pkg)
.build();
applicationInfo.category = CATEGORY_PRODUCTIVITY;
applicationInfo.flags = FLAG_INSTALLED;
return applicationInfo;
}).when(mLauncherApps).getApplicationInfo(anyString(), anyInt(), any());
AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class);
doReturn(allWidgets).when(manager).getInstalledProviders();
doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle()));
@@ -76,17 +76,21 @@ import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class FallbackRecentsTest {
private static final String FALLBACK_LAUNCHER_TITLE = "Test launcher";
private static final Pattern COMPONENT_INFO_REGEX = Pattern.compile("ComponentInfo\\{(.*)\\}");
private final UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
@@ -253,7 +257,7 @@ public class FallbackRecentsTest {
//@NavigationModeSwitch
@Test
@ScreenRecordRule.ScreenRecord // b/321775748
public void testOverview() {
public void testOverview() throws IOException {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
@@ -261,7 +265,10 @@ public class FallbackRecentsTest {
Wait.atMost("Expected three apps in the task list",
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
checkTestLauncher();
BaseOverview overview = mLauncher.getLaunchedAppState().switchToOverview();
checkTestLauncher();
executeOnRecents(recents -> {
assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
});
@@ -303,6 +310,17 @@ public class FallbackRecentsTest {
mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
}
private void checkTestLauncher() throws IOException {
final Matcher matcher = COMPONENT_INFO_REGEX.matcher(
mDevice.executeShellCommand("cmd shortcut get-default-launcher"));
assertTrue("Incorrect output from get-default-launcher", matcher.find());
assertEquals("Current Launcher activity is incorrect",
"com.google.android.apps.nexuslauncher.tests/com.android"
+ ".launcher3.testcomponent.TestLauncherActivity",
matcher.group(1)
);
}
private int getCurrentOverviewPage(RecentsActivity recents) {
return recents.<RecentsView>getOverviewPanel().getCurrentPage();
}
+7 -5
View File
@@ -75,12 +75,14 @@
<!-- Widget suggestions header title in the full widgets picker for large screen devices
in landscape mode. [CHAR_LIMIT=50] -->
<string name="suggested_widgets_header_title">Suggestions</string>
<string name="productivity_widget_recommendation_category_label">Your Daily Essentials</string>
<string name="news_widget_recommendation_category_label">News For You</string>
<string name="productivity_widget_recommendation_category_label">Essentials</string>
<string name="news_widget_recommendation_category_label">News &amp; magazines</string>
<string name="social_and_entertainment_widget_recommendation_category_label">Your Chill Zone</string>
<string name="fitness_widget_recommendation_category_label">Reach Your Fitness Goals</string>
<string name="weather_widget_recommendation_category_label">Stay Ahead of the Weather</string>
<string name="others_widget_recommendation_category_label">You Might Also Like</string>
<string name="entertainment_widget_recommendation_category_label">Entertainment</string>
<string name="social_widget_recommendation_category_label">Social</string>
<string name="fitness_widget_recommendation_category_label">Health &amp; fitness</string>
<string name="weather_widget_recommendation_category_label">Weather</string>
<string name="others_widget_recommendation_category_label">Suggested for you</string>
<!-- accessibilityPaneTitle for the right pane when showing suggested widgets. -->
<string name="widget_picker_right_pane_accessibility_title"><xliff:g id="selected_header" example="Calendar">%1$s</xliff:g> widgets on right, search and options on left</string>
<!-- Label for showing the number of widgets an app has in the full widgets picker.
@@ -72,7 +72,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class InvariantDeviceProfile {
@@ -578,45 +577,6 @@ public class InvariantDeviceProfile {
return filteredProfiles;
}
/**
* Returns the GridOption associated to the given file name or null if the fileName is not
* supported.
* Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid"
*/
public GridOption getGridOptionFromFileName(Context context, String fileName) {
return parseAllGridOptions(context).stream()
.filter(gridOption -> Objects.equals(gridOption.dbFile, fileName))
.findFirst()
.orElse(null);
}
/**
* Returns the name of the given size on the current device or empty string if the size is not
* supported. Ej. 4x4 -> normal, 5x4 -> practical, etc.
* (Note: the name of the grid can be different for the same grid size depending of
* the values of the InvariantDeviceProfile)
*
*/
public String getGridNameFromSize(Context context, Point size) {
return parseAllGridOptions(context).stream()
.filter(gridOption -> gridOption.numColumns == size.x
&& gridOption.numRows == size.y)
.map(gridOption -> gridOption.name)
.findFirst()
.orElse("");
}
/**
* Returns the grid option for the given gridName on the current device (Note: the gridOption
* be different for the same gridName depending on the values of the InvariantDeviceProfile).
*/
public GridOption getGridOptionFromName(Context context, String gridName) {
return parseAllGridOptions(context).stream()
.filter(gridOption -> Objects.equals(gridOption.name, gridName))
.findFirst()
.orElse(null);
}
/**
* @return all the grid options that can be shown on the device
*/
@@ -123,6 +123,7 @@ public class WidgetItem extends ComponentKey {
if (!Flags.enableGeneratedPreviews() || generatedPreviews == null) {
return false;
}
return generatedPreviews.contains(widgetCategory);
return generatedPreviews.contains(widgetCategory)
&& generatedPreviews.get(widgetCategory) != null;
}
}
@@ -50,10 +50,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -123,48 +121,7 @@ public class RestoreDbTask {
// executed again.
LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
if (Flags.narrowGridRestore()) {
String oldPhoneFileName = idp.dbFile;
removeOldDBs(context, oldPhoneFileName);
trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName);
} else {
idp.reinitializeAfterRestore(context);
}
}
/**
* Try setting the gird used in the previous phone to the new one. If the current device doesn't
* support the previous grid option it will not be set.
*/
private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp,
String oldPhoneDbFileName) {
InvariantDeviceProfile.GridOption gridOption = idp.getGridOptionFromFileName(context,
oldPhoneDbFileName);
if (gridOption != null) {
/*
* We do this because in some cases different devices have different names for grid
* options, in one device the grid option "normal" can be 4x4 while in other it
* could be "practical". Calling this changes the current device grid to the same
* we had in the other phone, in the case the current phone doesn't support the grid
* option we use the default and migrate the db to the default. Migration occurs on
* {@code GridSizeMigrationUtil#migrateGridIfNeeded}
*/
idp.setCurrentGrid(context, gridOption.name);
}
}
/**
* Only keep the last database used on the previous device.
*/
private static void removeOldDBs(Context context, String oldPhoneDbFileName) {
// At this point idp.dbFile contains the name of the dbFile from the previous phone
LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> !dbName.equals(oldPhoneDbFileName))
.forEach(dbName -> {
if (context.getDatabasePath(dbName).delete()) {
FileLog.d(TAG, "Removed old grid db file: " + dbName);
}
});
idp.reinitializeAfterRestore(context);
}
private static boolean performRestore(Context context, ModelDbController controller) {
@@ -18,13 +18,13 @@ package com.android.launcher3.widget.picker;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -54,6 +54,7 @@ public class WidgetRecommendationCategoryProvider implements ResourceBasedOverri
* to display the recommendation grouped by categories.
*/
@WorkerThread
@Nullable
public WidgetRecommendationCategory getWidgetRecommendationCategory(Context context,
WidgetItem item) {
// This is a default implementation that uses application category to derive the category to
@@ -61,17 +62,16 @@ public class WidgetRecommendationCategoryProvider implements ResourceBasedOverri
// via the overridden WidgetRecommendationCategoryProvider resource.
Preconditions.assertWorkerThread();
PackageManager pm = context.getPackageManager();
PackageManagerHelper pmHelper = new PackageManagerHelper(context);
if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
String widgetComponentName = item.widgetInfo.getComponent().getClassName();
try {
int predictionCategory = pm.getApplicationInfo(
item.widgetInfo.getComponent().getPackageName(), 0 /* flags */).category;
ApplicationInfo applicationInfo = pmHelper.getApplicationInfo(
item.widgetInfo.getComponent().getPackageName(), item.widgetInfo.getUser(),
0 /* flags */);
if (applicationInfo != null) {
int predictionCategory = applicationInfo.category;
return getCategoryFromApplicationCategory(context, predictionCategory,
widgetComponentName);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to retrieve application category when determining the "
+ "widget category for " + widgetComponentName, e);
}
}
return null;
@@ -112,17 +112,22 @@ public class WidgetRecommendationCategoryProvider implements ResourceBasedOverri
R.string.news_widget_recommendation_category_label, /*order=*/1);
}
if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL
|| applicationCategory == ApplicationInfo.CATEGORY_AUDIO
if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL) {
return new WidgetRecommendationCategory(
R.string.social_widget_recommendation_category_label,
/*order=*/5);
}
if (applicationCategory == ApplicationInfo.CATEGORY_AUDIO
|| applicationCategory == ApplicationInfo.CATEGORY_VIDEO
|| applicationCategory == ApplicationInfo.CATEGORY_IMAGE) {
return new WidgetRecommendationCategory(
R.string.social_and_entertainment_widget_recommendation_category_label,
/*order=*/4);
R.string.entertainment_widget_recommendation_category_label,
/*order=*/6);
}
return new WidgetRecommendationCategory(
R.string.others_widget_recommendation_category_label, /*order=*/5);
R.string.others_widget_recommendation_category_label, /*order=*/4);
}
}
+5 -4
View File
@@ -177,7 +177,7 @@ android_library {
name: "launcher-testing-shared",
srcs: [
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt"
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt",
],
resource_dirs: [],
manifest: "multivalentTests/shared/AndroidManifest.xml",
@@ -225,8 +225,8 @@ android_robolectric_test {
// multivalentTests directory is a shared folder for not only robolectric converted test
// classes but also shared helper classes.
srcs: [
"multivalentTests/src/com/android/launcher3/util/*.java",
"multivalentTests/src/com/android/launcher3/util/*.kt",
"multivalentTests/src/**/*.java",
"multivalentTests/src/**/*.kt",
// Test util classes
":launcher-testing-helpers",
@@ -246,7 +246,8 @@ android_robolectric_test {
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
"inline-mockito-robolectric-prebuilt",
"mockito-robolectric-prebuilt",
"mockito-kotlin2",
"platform-parametric-runner-lib",
"testables",
"Launcher3TestResources",
Binary file not shown.
@@ -1,95 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.util.rule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherPrefs
import java.io.File
import java.nio.file.Paths
import kotlin.io.path.pathString
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* Removes all launcher's DBs from the device and copies the dbs in
* assets/databases/BackupAndRestore to the device. It also set's the needed LauncherPrefs variables
* needed to kickstart a backup and restore.
*/
class BackAndRestoreRule : TestRule {
private val phoneContext = getInstrumentation().targetContext
private fun isWorkspaceDatabase(rawFileName: String): Boolean {
val fileName = Paths.get(rawFileName).fileName.pathString
return fileName.startsWith("launcher") && fileName.endsWith(".db")
}
fun getDatabaseFiles() =
File(phoneContext.dataDir.path, "/databases").listFiles().filter {
isWorkspaceDatabase(it.name)
}
private fun deleteDBs() = getDatabaseFiles().forEach { it.delete() }
/**
* Setting RESTORE_DEVICE would trigger a restore next time the Launcher starts, and we remove
* the widgets and apps ids to prevent issues when loading the database.
*/
private fun setRestoreConstants() {
LauncherPrefs.get(phoneContext)
.put(LauncherPrefs.RESTORE_DEVICE.to(InvariantDeviceProfile.TYPE_MULTI_DISPLAY))
LauncherPrefs.get(phoneContext)
.remove(LauncherPrefs.OLD_APP_WIDGET_IDS, LauncherPrefs.APP_WIDGET_IDS)
}
private fun uploadDatabase(dbName: String) {
val file = File(File(getInstrumentation().targetContext.dataDir, "/databases"), dbName)
file.writeBytes(
InstrumentationRegistry.getInstrumentation()
.context
.assets
.open("databases/BackupAndRestore/$dbName")
.readBytes()
)
file.setWritable(true, false)
}
fun before() {
setRestoreConstants()
deleteDBs()
uploadDatabase("launcher.db")
uploadDatabase("launcher_4_by_4.db")
uploadDatabase("launcher_4_by_5.db")
uploadDatabase("launcher_3_by_3.db")
}
fun after() {
deleteDBs()
}
override fun apply(base: Statement?, description: Description?): Statement =
object : Statement() {
override fun evaluate() {
before()
base?.evaluate()
after()
}
}
}
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.backuprestore
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.Flags
import com.android.launcher3.model.ModelDbController
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.rule.BackAndRestoreRule
import com.android.launcher3.util.rule.setFlags
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not
* the last one used when we restore the device.
*/
@RunWith(AndroidJUnit4::class)
@MediumTest
class BackupAndRestoreDBSelectionTest {
@JvmField @Rule var backAndRestoreRule = BackAndRestoreRule()
@JvmField
@Rule
val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
@Before
fun setUp() {
setFlagsRule.setFlags(true, Flags.FLAG_NARROW_GRID_RESTORE)
}
@Test
fun oldDatabasesNotPresentAfterRestore() {
val dbController = ModelDbController(getInstrumentation().targetContext)
dbController.tryMigrateDB(null)
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
assert(backAndRestoreRule.getDatabaseFiles().size == 1) {
"There should only be one database after restoring, the last one used. Actual databases ${backAndRestoreRule.getDatabaseFiles()}"
}
}
}
}
@@ -110,6 +110,18 @@ class GeneratedPreviewTest {
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
}
@Test
@RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview_nullPreview() {
appWidgetProviderInfo.generatedPreviewCategories =
WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD
createWidgetItem()
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
// loadGeneratedPreview returns null for KEYGUARD, so this should still be false.
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
}
@Test
@RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview_flagDisabled() {
@@ -23,6 +23,7 @@ import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED;
import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -30,8 +31,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
@@ -41,7 +41,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.LauncherApps;
import android.os.Process;
import androidx.test.core.content.pm.ApplicationInfoBuilder;
@@ -70,10 +70,25 @@ import java.util.Map;
public class WidgetRecommendationCategoryProviderTest {
private static final String TEST_PACKAGE = "com.foo.test";
private static final String TEST_APP_NAME = "foo";
public static final WidgetRecommendationCategory SOCIAL_AND_ENTERTAINMENT_CATEGORY =
private static final WidgetRecommendationCategory PRODUCTIVITY =
new WidgetRecommendationCategory(
R.string.social_and_entertainment_widget_recommendation_category_label,
/*order=*/4);
R.string.productivity_widget_recommendation_category_label,
/*order=*/0);
private static final WidgetRecommendationCategory NEWS =
new WidgetRecommendationCategory(
R.string.news_widget_recommendation_category_label, /*order=*/1);
private static final WidgetRecommendationCategory SUGGESTED_FOR_YOU =
new WidgetRecommendationCategory(
R.string.others_widget_recommendation_category_label, /*order=*/4);
private static final WidgetRecommendationCategory SOCIAL =
new WidgetRecommendationCategory(
R.string.social_widget_recommendation_category_label,
/*order=*/5);
private static final WidgetRecommendationCategory ENTERTAINMENT =
new WidgetRecommendationCategory(
R.string.entertainment_widget_recommendation_category_label,
/*order=*/6);
private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
TEST_PACKAGE).setName(TEST_APP_NAME).build();
private Context mContext;
@@ -82,7 +97,7 @@ public class WidgetRecommendationCategoryProviderTest {
private WidgetItem mTestWidgetItem;
@Mock
private PackageManager mPackageManager;
private LauncherApps mLauncherApps;
private InvariantDeviceProfile mTestProfile;
@Before
@@ -90,10 +105,12 @@ public class WidgetRecommendationCategoryProviderTest {
MockitoAnnotations.initMocks(this);
mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
@Override
public PackageManager getPackageManager() {
return mPackageManager;
public Object getSystemService(String name) {
return LAUNCHER_APPS_SERVICE.equals(name) ? mLauncherApps : super.getSystemService(
name);
}
};
mTestAppInfo.flags = FLAG_INSTALLED;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
@@ -103,25 +120,22 @@ public class WidgetRecommendationCategoryProviderTest {
@Test
public void getWidgetRecommendationCategory_returnsMappedCategory() throws Exception {
ImmutableMap<Integer, WidgetRecommendationCategory> testCategories = ImmutableMap.of(
CATEGORY_PRODUCTIVITY, new WidgetRecommendationCategory(
R.string.productivity_widget_recommendation_category_label,
/*order=*/
0),
CATEGORY_NEWS, new WidgetRecommendationCategory(
R.string.news_widget_recommendation_category_label, /*order=*/1),
CATEGORY_SOCIAL, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
CATEGORY_AUDIO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
CATEGORY_IMAGE, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
CATEGORY_VIDEO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
CATEGORY_UNDEFINED, new WidgetRecommendationCategory(
R.string.others_widget_recommendation_category_label, /*order=*/5));
CATEGORY_PRODUCTIVITY, PRODUCTIVITY,
CATEGORY_NEWS, NEWS,
CATEGORY_SOCIAL, SOCIAL,
CATEGORY_AUDIO, ENTERTAINMENT,
CATEGORY_IMAGE, ENTERTAINMENT,
CATEGORY_VIDEO, ENTERTAINMENT,
CATEGORY_UNDEFINED, SUGGESTED_FOR_YOU);
for (Map.Entry<Integer, WidgetRecommendationCategory> testCategory :
testCategories.entrySet()) {
mTestAppInfo.category = testCategory.getKey();
when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
mTestAppInfo);
when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
/*flags=*/ eq(0),
/*user=*/ eq(Process.myUserHandle())))
.thenReturn(mTestAppInfo);
WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
@@ -26,11 +26,11 @@ import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD;
import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -141,7 +141,7 @@ public final class LaunchedAppState extends Background {
return new Taskbar(mLauncher);
} finally {
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
Log.d(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
"swipeUpToUnstashTaskbar: completed gesture");
mLauncher.getTestInfo(REQUEST_DISABLE_BLOCK_TIMEOUT);
}
@@ -19,8 +19,10 @@ package com.android.launcher3.tapl;
import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.DEFAULT;
import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT;
import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT;
import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import android.graphics.Rect;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
@@ -219,6 +221,7 @@ public final class OverviewTask {
return new LaunchedAppState(mLauncher);
}
} else {
Log.d(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTaskAnimated");
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
return new LaunchedAppState(mLauncher);
}