diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java index 5c4eb9d92f..dcc3b052c9 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -21,6 +21,7 @@ import android.app.PendingIntent; import android.app.Person; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.LauncherUserInfo; @@ -156,6 +157,13 @@ public class ApiWrapper { } } + /** + * Checks if an activity is flagged as non-resizeable. + */ + public static boolean isNonResizeableActivity(LauncherActivityInfo lai) { + return lai.getActivityInfo().resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + } + private static class NoopDrawable extends ColorDrawable { @Override public int getIntrinsicHeight() { diff --git a/res/values/strings.xml b/res/values/strings.xml index 379cdda8db..fa7057f19d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -46,6 +46,10 @@ Save app pair %1$s | %2$s + + This pair isn\'t supported at this screen size + + Unfold your device to use this pair diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java index 9b85a655e0..48d0fbd485 100644 --- a/src/com/android/launcher3/apppairs/AppPairIcon.java +++ b/src/com/android/launcher3/apppairs/AppPairIcon.java @@ -17,6 +17,7 @@ package com.android.launcher3.apppairs; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; @@ -33,11 +34,13 @@ import com.android.launcher3.R; import com.android.launcher3.Reorderable; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.MultiTranslateDelegate; import com.android.launcher3.views.ActivityContext; import java.util.Collections; import java.util.Comparator; +import java.util.function.Predicate; /** * A {@link android.widget.FrameLayout} used to represent an app pair icon on the workspace. @@ -48,6 +51,11 @@ import java.util.Comparator; public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable { private static final String TAG = "AppPairIcon"; + /** + * Indicates that the app pair is currently launchable on the current screen. + */ + private boolean mIsLaunchableAtScreenSize = true; + // A view that holds the app pair icon graphic. private AppPairIconGraphic mIconGraphic; // A view that holds the app pair's title. @@ -86,6 +94,13 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab icon.setOnClickListener(activity.getItemOnClickListener()); icon.mInfo = appPairInfo; + if (icon.mInfo.contents.size() != 2) { + Log.wtf(TAG, "AppPair contents not 2, size: " + icon.mInfo.contents.size()); + return icon; + } + + icon.checkScreenSize(); + // Set up icon drawable area icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic); icon.mIconGraphic.init(activity.getDeviceProfile(), icon); @@ -109,11 +124,6 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab * Returns a formatted accessibility title for app pairs. */ public String getAccessibilityTitle(FolderInfo appPairInfo) { - if (appPairInfo.contents.size() != 2) { - Log.wtf(TAG, "AppPair contents not 2, size: " + appPairInfo.contents.size()); - return ""; - } - CharSequence app1 = appPairInfo.contents.get(0).title; CharSequence app2 = appPairInfo.contents.get(1).title; return getContext().getString(R.string.app_pair_name_format, app1, app2); @@ -167,4 +177,40 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab public View getIconDrawableArea() { return mIconGraphic; } + + public boolean isLaunchableAtScreenSize() { + return mIsLaunchableAtScreenSize; + } + + /** + * Checks if the app pair is launchable in the current device configuration. + * + * App pairs can be "disabled" in two ways: + * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused + * by the user or can't be launched). + * 2) This specific instance of an app pair can't be launched due to screen size requirements. + * + * This method checks and updates #2. Both #1 and #2 are checked when app pairs are drawn + * {@link AppPairIconGraphic#dispatchDraw(Canvas)} or clicked on + * {@link com.android.launcher3.touch.ItemClickHandler#onClickAppPairIcon(View)} + */ + public void checkScreenSize() { + DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile(); + // If user is on a small screen, we can't launch if either of the apps is non-resizeable + mIsLaunchableAtScreenSize = + dp.isTablet || getInfo().contents.stream().noneMatch( + wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)); + } + + /** + * Called when WorkspaceItemInfos get updated, and the app pair icon may need to be redrawn. + */ + public void maybeRedrawForWorkspaceUpdate(Predicate itemCheck) { + // If either of the app pair icons return true on the predicate (i.e. in the list of + // updated apps), redraw the icon graphic (icon background and both icons). + if (getInfo().contents.stream().anyMatch(itemCheck)) { + checkScreenSize(); + mIconGraphic.invalidate(); + } + } } diff --git a/src/com/android/launcher3/apppairs/AppPairIconBackground.java b/src/com/android/launcher3/apppairs/AppPairIconBackground.java index 4e60ece170..b5011f126c 100644 --- a/src/com/android/launcher3/apppairs/AppPairIconBackground.java +++ b/src/com/android/launcher3/apppairs/AppPairIconBackground.java @@ -157,7 +157,7 @@ class AppPairIconBackground extends Drawable { @Override public void setAlpha(int i) { - // Required by Drawable but not used. + mBackgroundPaint.setAlpha(i); } @Override diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt index ab5ba5499b..365edf8125 100644 --- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt +++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt @@ -46,6 +46,9 @@ class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: Attr private const val CENTER_CHANNEL_SCALE = 1 / 30f private const val BIG_RADIUS_SCALE = 1 / 5f private const val SMALL_RADIUS_SCALE = 1 / 15f + // Disabled alpha is 38%, or 97/255 + private const val DISABLED_ALPHA = 97 + private const val ENABLED_ALPHA = 255 } // App pair icons are slightly smaller than regular icons, so we pad the icon by this much on @@ -133,7 +136,13 @@ class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: Attr override fun dispatchDraw(canvas: Canvas) { super.dispatchDraw(canvas) + + val drawAlpha = + if (!parentIcon.isLaunchableAtScreenSize || parentIcon.info.isDisabled) DISABLED_ALPHA + else ENABLED_ALPHA + // Draw background + appPairBackground.alpha = drawAlpha appPairBackground.draw(canvas) // Make sure icons are loaded and fresh @@ -147,6 +156,7 @@ class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: Attr } else { canvas.translate(width / 2f - memberIconSize / 2f, innerPadding) } + appIcon1?.alpha = drawAlpha appIcon1?.draw(canvas) canvas.restore() @@ -164,6 +174,7 @@ class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: Attr height - (innerPadding + memberIconSize) ) } + appIcon2?.alpha = drawAlpha appIcon2?.draw(canvas) canvas.restore() } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 4d06c2e079..0ba468d5f0 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -48,6 +48,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.pm.UserCache; import com.android.launcher3.shortcuts.ShortcutRequest; +import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInfoMatcher; @@ -284,6 +285,12 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { } } if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) { + if (activities != null && !activities.isEmpty()) { + si.status = ApiWrapper + .isNonResizeableActivity(activities.get(0)) + ? si.status | WorkspaceItemInfo.FLAG_NON_RESIZEABLE + : si.status & ~WorkspaceItemInfo.FLAG_NON_RESIZEABLE; + } iconCache.getTitleAndIcon(si, si.usingLowResIcon()); infoUpdated = true; } diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt index e38af0184d..59f56dfaa8 100644 --- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt +++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt @@ -38,6 +38,7 @@ import com.android.launcher3.model.data.LauncherAppWidgetInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.pm.PackageInstallInfo import com.android.launcher3.shortcuts.ShortcutKey +import com.android.launcher3.uioverrides.ApiWrapper import com.android.launcher3.util.ComponentKey import com.android.launcher3.util.PackageManagerHelper import com.android.launcher3.util.PackageUserKey @@ -321,6 +322,9 @@ class WorkspaceItemProcessor( } val activityInfo = c.launcherActivityInfo if (activityInfo != null) { + if (ApiWrapper.isNonResizeableActivity(activityInfo)) { + info.status = info.status or WorkspaceItemInfo.FLAG_NON_RESIZEABLE + } info.setProgressLevel( PackageManagerHelper.getLoadingProgress(activityInfo), PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java index 435d223af8..9917ad7a64 100644 --- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java @@ -74,6 +74,12 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { */ public static final int FLAG_START_FOR_RESULT = 1 << 4; + /** + * The app is flagged non-resizeable, meaning that it does not support multi-window on small + * screens. + */ + public static final int FLAG_NON_RESIZEABLE = 1 << 5; + /** * The intent used to start the application. */ diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 4abefc7a3c..111931eeeb 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -41,6 +41,7 @@ import android.widget.Toast; import com.android.launcher3.BubbleTextView; import com.android.launcher3.BuildConfig; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -149,7 +150,15 @@ public class ItemClickHandler { private static void onClickAppPairIcon(View v) { Launcher launcher = Launcher.getLauncher(v.getContext()); AppPairIcon appPairIcon = (AppPairIcon) v; - if (appPairIcon.getInfo().isDisabled()) { + if (!appPairIcon.isLaunchableAtScreenSize()) { + // Display a message for app pairs that are disabled due to screen size + boolean isFoldable = InvariantDeviceProfile.INSTANCE.get(launcher) + .supportedProfiles.stream().anyMatch(dp -> dp.isTwoPanels); + Toast.makeText(launcher, isFoldable + ? R.string.app_pair_needs_unfold + : R.string.app_pair_unlaunchable_at_screen_size, + Toast.LENGTH_SHORT).show(); + } else if (appPairIcon.getInfo().isDisabled()) { WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0); WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1); // Show the user why the app pair is disabled. diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java index f73940bfee..69786bb5e5 100644 --- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java +++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java @@ -19,6 +19,7 @@ import android.graphics.drawable.Drawable; import android.view.View; import com.android.launcher3.BubbleTextView; +import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.PreloadIconDrawable; @@ -58,6 +59,8 @@ public interface LauncherBindableItemsContainer { : null); } else if (info instanceof FolderInfo && v instanceof FolderIcon) { ((FolderIcon) v).updatePreviewItems(updates::contains); + } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) { + appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains); } // Iterate all items @@ -86,6 +89,8 @@ public interface LauncherBindableItemsContainer { ((PendingAppWidgetHostView) v).applyState(); } else if (v instanceof FolderIcon && info instanceof FolderInfo) { ((FolderIcon) v).updatePreviewItems(updates::contains); + } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) { + appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains); } // process all the shortcuts return false; diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java index b9f9ac57da..efde7d863a 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java @@ -107,6 +107,15 @@ public class ApiWrapper { .authority(context.getPackageName()).build()); } + /** + * Checks if an activity is flagged as non-resizeable. + */ + public static boolean isNonResizeableActivity(LauncherActivityInfo lai) { + // Overridden in quickstep + return false; + } + + private static class NoopDrawable extends ColorDrawable { @Override public int getIntrinsicHeight() {