Merge "Sending original motion events to launcher overlay along with inferred values" into main
diff --git a/Android.bp b/Android.bp
index 61042f6..4354b66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -203,6 +203,7 @@
"animationlib",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
+ "android.appwidget.flags-aconfig-java",
],
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 6d899d9..d379132 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -150,6 +150,13 @@
}
flag {
+ name: "enable_generated_previews"
+ namespace: "launcher"
+ description: "Enables support for RemoteViews previews in the widget picker."
+ bug: "306546610"
+}
+
+flag {
name: "enable_categorized_widget_suggestions"
namespace: "launcher"
description: "Enables widget suggestions in widget picker to be displayed in categories"
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 5af8d51..3256b0b 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -43,7 +43,7 @@
android:layout_height="@dimen/keyboard_quick_switch_no_recent_items_icon_size"
android:layout_marginBottom="@dimen/keyboard_quick_switch_no_recent_items_icon_margin"
android:src="@drawable/ic_empty_recents"
- android:tint="?androidprv:attr/materialColorOnSurfaceInverse"
+ android:tint="?androidprv:attr/materialColorOnSurface"
android:importantForAccessibility="no"
app:layout_constraintVertical_chainStyle="packed"
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 325c255..14f615e 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -105,6 +105,8 @@
<string name="back_gesture_feedback_cancelled">Make sure you swipe from the right or left edge to the middle of the screen and let go</string>
<!-- Feedback shown after completing the back gesture step if the user is following the full gesture tutorial flow. [CHAR LIMIT=100] -->
<string name="back_gesture_feedback_complete_with_overview_follow_up">You learned how to swipe from the right to go back. Next up, learn how to switch apps.</string>
+ <!-- Feedback shown after completing the back gesture step if the user is following the full gesture tutorial flow. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_complete_with_follow_up">You completed the go back gesture. Next up, learn how to switch apps.</string>
<!-- Feedback shown after completing the back gesture step if the user started this tutorial individually. [CHAR LIMIT=100] -->
<string name="back_gesture_feedback_complete_without_follow_up">You completed the go back gesture</string>
<!-- Feedback shown during interactive parts of Back gesture tutorial when the gesture is within the nav bar region. [CHAR LIMIT=100] -->
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index bdc86b2..350c752 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -273,7 +273,7 @@
</style>
<style name="KeyboardQuickSwitchText.OnBackground" parent="KeyboardQuickSwitchText">
- <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceInverse</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="GestureTutorialActivity" parent="@style/AppTheme">
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 7a69c55..747c9cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -803,6 +803,11 @@
private void addJankMonitorListener(
AnimatorSet animator, boolean expanding, @StashAnimation int animationType) {
View v = mControllers.taskbarActivityContext.getDragLayer();
+ if (!v.isAttachedToWindow()) {
+ // If the task bar drag layer is not attached to window, we don't need to monitor jank
+ // (actually we can't pass in an unattached view either).
+ return;
+ }
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
animator.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 404bca9..6757cd8 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -85,7 +85,9 @@
public int getSuccessFeedbackSubtitle() {
return mTutorialFragment.isAtFinalStep()
? R.string.back_gesture_feedback_complete_without_follow_up
- : R.string.back_gesture_feedback_complete_with_overview_follow_up;
+ : ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.string.back_gesture_feedback_complete_with_follow_up
+ : R.string.back_gesture_feedback_complete_with_overview_follow_up;
}
@Override
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 87cbdd1..fc50853 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -6,7 +6,6 @@
import android.view.Surface.Rotation
import android.view.View
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
@@ -14,7 +13,9 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
-import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_NAV_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_START_CONTEXTUAL_BUTTONS
import com.android.systemui.shared.rotation.RotationButton
import java.lang.IllegalStateException
import org.junit.Assume.assumeTrue
@@ -52,11 +53,11 @@
whenever(mockNavLayout.findViewById<View>(R.id.recent_apps)).thenReturn(mockRecentsButton)
// Init top level layout
- whenever(mockParentButtonContainer.findViewById<LinearLayout>(R.id.end_nav_buttons))
+ whenever(mockParentButtonContainer.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS))
.thenReturn(mockNavLayout)
- whenever(mockParentButtonContainer.findViewById<ViewGroup>(R.id.end_contextual_buttons))
+ whenever(mockParentButtonContainer.requireViewById<ViewGroup>(ID_END_CONTEXTUAL_BUTTONS))
.thenReturn(mockEndContextualLayout)
- whenever(mockParentButtonContainer.findViewById<ViewGroup>(R.id.start_contextual_buttons))
+ whenever(mockParentButtonContainer.requireViewById<ViewGroup>(ID_START_CONTEXTUAL_BUTTONS))
.thenReturn(mockStartContextualLayout)
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d124746..99fca62 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -441,17 +441,35 @@
@Override
public void execute(@NonNull final LauncherAppState app,
@NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
+ IconCache iconCache = app.getIconCache();
final IntSet removedIds = new IntSet();
+ HashSet<WorkspaceItemInfo> archivedItemsToCacheRefresh = new HashSet<>();
+ HashSet<String> archivedPackagesToCacheRefresh = new HashSet<>();
synchronized (dataModel) {
for (ItemInfo info : dataModel.itemsIdMap) {
if (info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi()
&& user.equals(info.user)
- && info.getIntent() != null
- && TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.add(info.id);
+ && info.getIntent() != null) {
+ if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
+ removedIds.add(info.id);
+ }
+ if (((WorkspaceItemInfo) info).isArchived()) {
+ WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
+ // Remove package cache icon for archived app in case of a session
+ // failure.
+ mApp.getIconCache().removeIconsForPkg(packageName, user);
+ // Refresh icons on the workspace for archived apps.
+ iconCache.getTitleAndIcon(workspaceItem,
+ workspaceItem.usingLowResIcon());
+ archivedPackagesToCacheRefresh.add(packageName);
+ archivedItemsToCacheRefresh.add(workspaceItem);
+ }
}
}
+ if (!archivedPackagesToCacheRefresh.isEmpty()) {
+ apps.updateIconsAndLabels(archivedPackagesToCacheRefresh, user);
+ }
}
if (!removedIds.isEmpty()) {
@@ -459,6 +477,10 @@
ItemInfoMatcher.ofItemIds(removedIds),
"removed because install session failed");
}
+ if (!archivedItemsToCacheRefresh.isEmpty()) {
+ bindUpdatedWorkspaceItems(archivedItemsToCacheRefresh.stream().toList());
+ bindApplicationsIfNeeded();
+ }
}
});
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 7067fa2..911612f 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -71,7 +71,7 @@
@Override
protected int getVerticalSnapPreference() {
- return SNAP_TO_START;
+ return SNAP_TO_ANY;
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 051cf50..009a2aa6 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -93,11 +93,14 @@
* Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
* the current set of apps.
*
- * <p> Note that shouldPreinflate param should be set to {@code false} for taskbar, because this
- * method is too late to preinflate all apps, as user will open all apps in the same frame.
+ * <p> Note that shouldPreinflate param should be set to {@code false} for taskbar, because
+ * this method is too late to preinflate all apps, as user will open all apps in the frame
+ *
+ * <p>Param: apps are required to be sorted using the comparator COMPONENT_KEY_COMPARATOR
+ * in order to enable binary search on the mApps store
*/
public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map,
- boolean shouldPreinflate) {
+ boolean shouldPreinflate) {
mApps = apps == null ? EMPTY_ARRAY : apps;
mModelFlags = flags;
notifyUpdate();
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
index 311a40e..a0867db 100644
--- a/src/com/android/launcher3/allapps/AppInfoComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -43,9 +43,7 @@
@Override
public int compare(AppInfo a, AppInfo b) {
// Order by the title in the current locale
- int result = mLabelComparator.compare(
- a.title == null ? "" : a.title.toString(),
- b.title == null ? "" : b.title.toString());
+ int result = mLabelComparator.compare(getSortingTitle(a), getSortingTitle(b));
if (result != 0) {
return result;
}
@@ -64,4 +62,14 @@
return aUserSerial.compareTo(bUserSerial);
}
}
+
+ private String getSortingTitle(AppInfo info) {
+ if (info.appTitle != null) {
+ return info.appTitle.toString();
+ }
+ if (info.title != null) {
+ return info.title.toString();
+ }
+ return "";
+ }
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 2f7f51e..d8fa90a 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -219,7 +219,19 @@
CacheEntry entry = cacheLocked(application.componentName,
application.user, () -> null, mLauncherActivityInfoCachingLogic,
false, application.usingLowResIcon());
- if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) {
+ if (entry.bitmap == null || isDefaultIcon(entry.bitmap, application.user)) {
+ return;
+ }
+
+ boolean preferPackageIcon = application.isArchived();
+ if (preferPackageIcon) {
+ String packageName = application.getTargetPackage();
+ CacheEntry packageEntry =
+ cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
+ application.user, () -> null, mLauncherActivityInfoCachingLogic,
+ false, application.usingLowResIcon());
+ applyPackageEntry(packageEntry, application, entry);
+ } else {
applyCacheEntry(entry, application);
}
}
@@ -227,10 +239,14 @@
/**
* Fill in {@param info} with the icon and label for {@param activityInfo}
*/
+ @SuppressWarnings("NewApi")
public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
LauncherActivityInfo activityInfo, boolean useLowResIcon) {
+ boolean isAppArchived = Utilities.enableSupportForArchiving() && activityInfo != null
+ && activityInfo.getActivityInfo().isArchived;
// If we already have activity info, no need to use package icon
- getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon);
+ getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon,
+ isAppArchived);
}
/**
@@ -309,7 +325,7 @@
} else {
Intent intent = info.getIntent();
getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
- true, useLowResIcon);
+ true, useLowResIcon, info.isArchived());
}
}
@@ -334,6 +350,28 @@
}
/**
+ * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
+ */
+ public synchronized void getTitleAndIcon(
+ @NonNull ItemInfoWithIcon infoInOut,
+ @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
+ boolean usePkgIcon, boolean useLowResIcon, boolean preferPackageEntry) {
+ CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
+ activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
+ useLowResIcon);
+ if (preferPackageEntry) {
+ String packageName = infoInOut.getTargetPackage();
+ CacheEntry packageEntry = cacheLocked(
+ new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
+ infoInOut.user, activityInfoProvider, mLauncherActivityInfoCachingLogic,
+ usePkgIcon, useLowResIcon);
+ applyPackageEntry(packageEntry, infoInOut, entry);
+ } else {
+ applyCacheEntry(entry, infoInOut);
+ }
+ }
+
+ /**
* Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles.
*
* @param iconRequestInfos List of IconRequestInfos representing titles and icons to query.
@@ -551,6 +589,19 @@
}
}
+ protected void applyPackageEntry(@NonNull final CacheEntry packageEntry,
+ @NonNull final ItemInfoWithIcon info, @NonNull final CacheEntry fallbackEntry) {
+ info.title = Utilities.trim(packageEntry.title);
+ info.appTitle = Utilities.trim(fallbackEntry.title);
+ info.contentDescription = packageEntry.contentDescription;
+ info.bitmap = packageEntry.bitmap;
+ if (packageEntry.bitmap == null) {
+ // TODO: entry.bitmap can never be null, so this should not happen at all.
+ Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded.");
+ info.bitmap = getDefaultIcon(info.user);
+ }
+ }
+
public Drawable getFullResIcon(LauncherActivityInfo info) {
return mIconProvider.getIcon(info, mIconDpi);
}
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index c99b889..ddf4023 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -1,5 +1,9 @@
package com.android.launcher3.model;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
+
import static com.android.launcher3.Utilities.ATLEAST_S;
import android.annotation.SuppressLint;
@@ -7,13 +11,19 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+import androidx.core.os.BuildCompat;
+
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetManagerHelper;
/**
* An wrapper over various items displayed in a widget picker,
@@ -28,9 +38,11 @@
public final String label;
public final CharSequence description;
public final int spanX, spanY;
+ public final SparseArray<RemoteViews> generatedPreviews;
public WidgetItem(LauncherAppWidgetProviderInfo info,
- InvariantDeviceProfile idp, IconCache iconCache, Context context) {
+ InvariantDeviceProfile idp, IconCache iconCache, Context context,
+ WidgetManagerHelper helper) {
super(info.provider, info.getProfile());
label = iconCache.getTitleNoCache(info);
@@ -40,6 +52,27 @@
spanX = Math.min(info.spanX, idp.numColumns);
spanY = Math.min(info.spanY, idp.numRows);
+
+ if (BuildCompat.isAtLeastV() && Flags.enableGeneratedPreviews()) {
+ generatedPreviews = new SparseArray<>(3);
+ for (int widgetCategory : new int[] {
+ WIDGET_CATEGORY_HOME_SCREEN,
+ WIDGET_CATEGORY_KEYGUARD,
+ WIDGET_CATEGORY_SEARCHBOX,
+ }) {
+ if ((widgetCategory & widgetInfo.generatedPreviewCategories) != 0) {
+ generatedPreviews.put(widgetCategory,
+ helper.loadGeneratedPreview(widgetInfo, widgetCategory));
+ }
+ }
+ } else {
+ generatedPreviews = null;
+ }
+ }
+
+ public WidgetItem(LauncherAppWidgetProviderInfo info,
+ InvariantDeviceProfile idp, IconCache iconCache, Context context) {
+ this(info, idp, iconCache, context, new WidgetManagerHelper(context));
}
public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) {
@@ -50,6 +83,7 @@
widgetInfo = null;
activityInfo = info;
spanX = spanY = 1;
+ generatedPreviews = null;
}
/**
@@ -78,4 +112,15 @@
public boolean isShortcut() {
return activityInfo != null;
}
+
+ /**
+ * Returns whether this {@link WidgetItem} has a generated preview for the given widget
+ * category.
+ */
+ public boolean hasGeneratedPreview(int widgetCategory) {
+ if (!Flags.enableGeneratedPreviews() || generatedPreviews == null) {
+ return false;
+ }
+ return generatedPreviews.contains(widgetCategory);
+ }
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 86393a0..55849c2 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -160,6 +160,13 @@
public CharSequence title;
/**
+ * Optionally set: The appTitle might e.g. be different if {@code title} is used to
+ * display progress (e.g. Downloading..).
+ */
+ @Nullable
+ public CharSequence appTitle;
+
+ /**
* Content description of the item.
*/
@Nullable
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 8f5e2b6..c75f9d1 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -16,6 +16,8 @@
package com.android.launcher3.widget;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx;
@@ -44,6 +46,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.CheckLongPressHelper;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
@@ -241,6 +244,11 @@
mAppWidgetHostViewPreview = createAppWidgetHostView(context);
setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo,
mRemoteViewsPreview);
+ } else if (Flags.enableGeneratedPreviews()
+ && item.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)) {
+ mAppWidgetHostViewPreview = createAppWidgetHostView(context);
+ setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo,
+ item.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN));
} else if (item.hasPreviewLayout()) {
// If the context is a Launcher activity, DragView will show mAppWidgetHostViewPreview
// as a preview during drag & drop. And thus, we should use LauncherAppWidgetHostView,
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 058523b..52767a4 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -24,8 +24,11 @@
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
+import android.widget.RemoteViews;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -130,6 +133,23 @@
appWidgetId).getBoolean(WIDGET_OPTION_RESTORE_COMPLETED);
}
+
+ /**
+ * Load RemoteViews preview for this provider if available.
+ *
+ * @param info The provider info for the widget you want to preview.
+ * @param widgetCategory The widget category for which you want to display previews.
+ *
+ * @return Returns the widget preview that matches selected category, if available.
+ */
+ @Nullable
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public RemoteViews loadGeneratedPreview(@NonNull AppWidgetProviderInfo info,
+ int widgetCategory) {
+ if (!android.appwidget.flags.Flags.generatedPreviews()) return null;
+ return mAppWidgetManager.getWidgetPreview(info.provider, info.getProfile(), widgetCategory);
+ }
+
private static Stream<AppWidgetProviderInfo> allWidgetsSteam(Context context) {
AppWidgetManager awm = context.getSystemService(AppWidgetManager.class);
return Stream.concat(
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 4c5bfd8..8b983fc 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -147,7 +147,8 @@
LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
widgetsAndShortcuts.add(new WidgetItem(
- launcherWidgetInfo, idp, app.getIconCache(), app.getContext()));
+ launcherWidgetInfo, idp, app.getIconCache(), app.getContext(),
+ widgetManager));
updatedItems.add(launcherWidgetInfo);
}
@@ -206,6 +207,7 @@
public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
LauncherAppState app) {
+ WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
if (packageNames.contains(entry.getKey().packageName)) {
List<WidgetItem> items = entry.getValue();
@@ -219,7 +221,7 @@
} else {
items.set(i, new WidgetItem(item.widgetInfo,
app.getInvariantDeviceProfile(), app.getIconCache(),
- app.getContext()));
+ app.getContext(), widgetManager));
}
}
}
@@ -337,4 +339,4 @@
return mMap.values();
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index ed8609e..9ce0777 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -127,6 +127,7 @@
"testables",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
+ "android.appwidget.flags-aconfig-java",
],
manifest: "AndroidManifest-common.xml",
platform_apis: true,
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 978e1f2..679bd01 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -193,6 +193,7 @@
protected AbstractLauncherUiTest() {
mLauncher.enableCheckEventsForSuccessfulGestures();
+ mLauncher.setAnomalyChecker(AbstractLauncherUiTest::verifyKeyguardInvisible);
try {
mDevice.setOrientationNatural();
} catch (RemoteException e) {
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index cdde605..e17994c 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -204,6 +204,9 @@
private LogEventChecker mEventChecker;
+ // UI anomaly checker provided by the test.
+ private Runnable mTestAnomalyChecker;
+
private boolean mCheckEventsForSuccessfulGestures = false;
private Runnable mOnLauncherCrashed;
@@ -573,8 +576,31 @@
checkForAnomaly(false, false);
}
+ /**
+ * Allows the test to provide a pluggable anomaly checker. It’s supposed to throw an exception
+ * if the check fails. The test may provide its own anomaly checker, for example, if it wants to
+ * check for an anomaly that’s recognized by the standard TAPL anomaly checker, but wants a
+ * custom error message, such as adding information whether the keyguard is seen for the first
+ * time during the shard execution.
+ */
+ public void setAnomalyChecker(Runnable anomalyChecker) {
+ mTestAnomalyChecker = anomalyChecker;
+ }
+
+ /**
+ * Verifies that there are no visible UI anomalies. An "anomaly" is a state of UI that should
+ * never happen during the text execution. Anomaly is something different from just “regular”
+ * unexpected state of the Launcher such as when we see Workspace after swiping up to All Apps.
+ * Workspace is a normal state. We can contrast this with an anomaly, when, for example, we see
+ * a lock screen. Launcher tests can never bring the lock screen, so the very presence of the
+ * lock screen is an indication that something went very wrong, and perhaps is caused by reasons
+ * outside of the Launcher and its tests, perhaps, by a crash in System UI. Diagnosing anomalies
+ * helps to understand faster whether the problem is in the Launcher or its tests, or outside.
+ */
public void checkForAnomaly(
boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) {
+ if (mTestAnomalyChecker != null) mTestAnomalyChecker.run();
+
final String systemAnomalyMessage =
getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews);
if (systemAnomalyMessage != null) {
diff --git a/tests/res/layout/test_layout_appwidget_red.xml b/tests/res/layout/test_layout_appwidget_red.xml
index 48d3e81..0f2bda3 100644
--- a/tests/res/layout/test_layout_appwidget_red.xml
+++ b/tests/res/layout/test_layout_appwidget_red.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
android:orientation="vertical"
android:background="#FFFF0000"
android:layout_width="match_parent"
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
index 6fce4c6..0f23165 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.platform.test.annotations.PlatinumTest;
+import android.platform.test.rule.ScreenRecordRule;
import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -130,6 +131,7 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
+ @ScreenRecordRule.ScreenRecord // b/322228038
public void testAllAppsFromHome() {
// Test opening all apps
assertNotNull("switchToAllApps() returned null",
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index f771052..6c35f68 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -2,21 +2,33 @@
import static android.os.Process.myUserHandle;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.TestUtil.DUMMY_CLASS_NAME;
+import static com.android.launcher3.util.TestUtil.DUMMY_PACKAGE;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.BitmapInfo;
@@ -26,12 +38,15 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.TestUtil;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -43,9 +58,18 @@
@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
+ private static final String ARCHIVED_PACKAGE = DUMMY_PACKAGE;
+ private static final String ARCHIVED_CLASS_NAME = DUMMY_CLASS_NAME;
+ private static final String ARCHIVED_TITLE = "Aardwolf";
+
+
private LauncherModelHelper mModelHelper;
private Context mContext;
@@ -57,6 +81,7 @@
mContext = mModelHelper.sandboxContext;
mSession1 = mModelHelper.createInstallerSession(PENDING_APP_1);
mModelHelper.createInstallerSession(PENDING_APP_2);
+ TestUtil.installDummyApp();
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
.atHotseat(1).putFolder("MyFolder")
@@ -73,14 +98,22 @@
.addApp(PENDING_APP_2, TEST_ACTIVITY) // 8
.addApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
.addApp(PENDING_APP_2, TEST_ACTIVITY3) // 10
+
+ // Dummy Test Package
+ .addApp(ARCHIVED_PACKAGE, ARCHIVED_CLASS_NAME) // 11
.build();
mModelHelper.setupDefaultLayoutProvider(builder);
mModelHelper.loadModelSync();
- assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
+ assertEquals(11, mModelHelper.getBgDataModel().itemsIdMap.size());
+
+ UiDevice device = UiDevice.getInstance(getInstrumentation());
+ assertThat(device.executeShellCommand(String.format("pm archive %s", ARCHIVED_PACKAGE)))
+ .isEqualTo("Success\n");
}
@After
- public void tearDown() {
+ public void tearDown() throws IOException {
+ TestUtil.uninstallDummyApp();
mModelHelper.destroy();
}
@@ -138,6 +171,47 @@
});
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ public void testSessionUpdate_archivedApps_sessionInfoPrioritized() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ // Clear all icons from apps list so that its easy to check what was updated
+ allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
+ int mSession2 = mModelHelper.createInstallerSession(ARCHIVED_PACKAGE);
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, ARCHIVED_PACKAGE));
+ List<Integer> pendingArchivedAppIds = List.of(11);
+ // Mark the app items as archived.
+ allItems().forEach(wi -> {
+ if (pendingArchivedAppIds.contains(wi.id)) {
+ wi.runtimeStatusFlags |= FLAG_ARCHIVED;
+ }
+ });
+ // Before cache is updated with sessionInfo, confirm the title.
+ for (WorkspaceItemInfo info : allItems()) {
+ if (pendingArchivedAppIds.contains(info.id)) {
+ assertEquals(info.title, ARCHIVED_TITLE);
+ }
+ }
+
+ // Update the cache with session details.
+ LauncherAppState.getInstance(mContext).getIconCache().updateSessionCache(
+ new PackageUserKey(ARCHIVED_PACKAGE, myUserHandle()),
+ mContext.getPackageManager().getPackageInstaller().getSessionInfo(mSession2));
+
+ // Trigger a refresh for workspace itemInfo objects.
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, ARCHIVED_PACKAGE));
+ // Verify the new title from session is applied to the iconInfo.
+ for (WorkspaceItemInfo info : allItems()) {
+ if (pendingArchivedAppIds.contains(info.id)) {
+ assertEquals(info.title, ARCHIVED_PACKAGE);
+ }
+ }
+ });
+ }
+
private void verifyUpdate(int... idsUpdated) {
IntSet updates = IntSet.wrap(idsUpdated);
for (WorkspaceItemInfo info : allItems()) {
diff --git a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
new file mode 100644
index 0000000..11855e6
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -0,0 +1,142 @@
+package com.android.launcher3.widget
+
+import android.appwidget.AppWidgetProviderInfo
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.view.LayoutInflater
+import android.widget.RemoteViews
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.IconProvider
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.tests.R
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.Executors
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GeneratedPreviewTest {
+ @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ private val providerName =
+ ComponentName(
+ "com.android.launcher3.tests",
+ "com.android.launcher3.testcomponent.AppWidgetNoConfig"
+ )
+ private val generatedPreviewLayout = R.layout.test_layout_appwidget_blue
+ private lateinit var context: Context
+ private lateinit var generatedPreview: RemoteViews
+ private lateinit var widgetCell: WidgetCell
+ private lateinit var helper: WidgetManagerHelper
+ private lateinit var appWidgetProviderInfo: LauncherAppWidgetProviderInfo
+ private lateinit var widgetItem: WidgetItem
+
+ @Before
+ fun setup() {
+ context = getApplicationContext()
+ generatedPreview = RemoteViews(context.packageName, generatedPreviewLayout)
+ widgetCell =
+ LayoutInflater.from(ActivityContextWrapper(context))
+ .inflate(com.android.launcher3.R.layout.widget_cell, null) as WidgetCell
+ appWidgetProviderInfo =
+ AppWidgetProviderInfo()
+ .apply {
+ generatedPreviewCategories = WIDGET_CATEGORY_HOME_SCREEN
+ provider = providerName
+ providerInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() }
+ }
+ .let { LauncherAppWidgetProviderInfo.fromProviderInfo(context, it) }
+ helper =
+ object : WidgetManagerHelper(context) {
+ override fun loadGeneratedPreview(
+ info: AppWidgetProviderInfo,
+ widgetCategory: Int
+ ) =
+ generatedPreview.takeIf {
+ info === appWidgetProviderInfo &&
+ widgetCategory == WIDGET_CATEGORY_HOME_SCREEN
+ }
+ }
+ createWidgetItem()
+ }
+
+ private fun createWidgetItem() {
+ Executors.MODEL_EXECUTOR.submit {
+ val idp = InvariantDeviceProfile()
+ widgetItem =
+ WidgetItem(
+ appWidgetProviderInfo,
+ idp,
+ IconCache(context, idp, null, IconProvider(context)),
+ context,
+ helper,
+ )
+ }
+ .get()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetItem_hasGeneratedPreview() {
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetItem_hasGeneratedPreview_noPreview() {
+ appWidgetProviderInfo.generatedPreviewCategories = 0
+ createWidgetItem()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetItem_hasGeneratedPreview_flagDisabled() {
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+ }
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetItem_getGeneratedPreview() {
+ val preview = widgetItem.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN)
+ assertThat(preview).isEqualTo(generatedPreview)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetCell_showGeneratedPreview() {
+ widgetCell.applyFromCellItem(widgetItem)
+ assertThat(widgetCell.appWidgetHostViewPreview).isNotNull()
+ assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo)
+ .isEqualTo(appWidgetProviderInfo)
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetCell_showGeneratedPreview_flagDisabled() {
+ widgetCell.applyFromCellItem(widgetItem)
+ assertThat(widgetCell.appWidgetHostViewPreview).isNull()
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 60590e7..0286279 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -52,6 +52,7 @@
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import org.junit.Before;
@@ -137,6 +138,7 @@
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+ WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext);
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
@@ -144,7 +146,7 @@
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
- mTestProfile, mIconCache, mContext));
+ mTestProfile, mIconCache, mContext, widgetManager));
}
return widgetItems;
}