From ea529083bd45bae8edcb86d0be056ff90921d0c1 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 31 Oct 2017 13:33:03 -0700 Subject: [PATCH] Using view elevation for shadow during click feedback instead of creating a shadow bitmap Change-Id: I331186664c3c448596af3172e0e080921a6a1908 --- res/layout/deep_shortcut.xml | 1 + res/layout/system_shortcut.xml | 1 + res/values/dimens.xml | 5 +- src/com/android/launcher3/AppInfo.java | 33 +++-- src/com/android/launcher3/CellLayout.java | 11 +- .../android/launcher3/ClickShadowView.java | 129 ++++++++++++++---- .../android/launcher3/ItemInfoWithIcon.java | 7 +- .../android/launcher3/LauncherAnimUtils.java | 13 ++ .../allapps/AllAppsContainerView.java | 9 +- .../graphics/HolographicOutlineHelper.java | 16 ++- .../android/launcher3/model/LoaderCursor.java | 14 +- .../android/launcher3/model/LoaderTask.java | 15 +- 12 files changed, 186 insertions(+), 68 deletions(-) diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml index 4a2ad42254..4a3db1f136 100644 --- a/res/layout/deep_shortcut.xml +++ b/res/layout/deep_shortcut.xml @@ -35,6 +35,7 @@ android:textColor="?android:attr/textColorPrimary" android:fontFamily="sans-serif" launcher:layoutHorizontal="true" + launcher:deferShadowGeneration="true" launcher:iconDisplay="shortcut_popup" launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" /> diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml index 04f3d027dc..1888e2254d 100644 --- a/res/layout/system_shortcut.xml +++ b/res/layout/system_shortcut.xml @@ -34,6 +34,7 @@ android:fontFamily="sans-serif" launcher:iconDisplay="shortcut_popup" launcher:layoutHorizontal="true" + launcher:deferShadowGeneration="true" android:focusable="false" /> - + + 4dp + + 8dp 32dp 1dp diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index a5422aa7be..9796d18f1f 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -21,9 +21,12 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.os.Build; +import android.os.Process; import android.os.UserHandle; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageManagerHelper; @@ -59,17 +62,12 @@ public class AppInfo extends ItemInfoWithIcon { this.componentName = info.getComponentName(); this.container = ItemInfo.NO_ID; this.user = user; - if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) { - runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; - } - if (quietModeEnabled) { - runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_QUIET_USER; - } - intent = makeLaunchIntent(info); - runtimeStatusFlags |= (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0 - ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES; + if (quietModeEnabled) { + runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER; + } + updateRuntimeFlagsForActivityTarget(this, info); } public AppInfo(AppInfo info) { @@ -102,4 +100,21 @@ public class AppInfo extends ItemInfoWithIcon { .setComponent(cn) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); } + + public static void updateRuntimeFlagsForActivityTarget( + ItemInfoWithIcon info, LauncherActivityInfo lai) { + ApplicationInfo appInfo = lai.getApplicationInfo(); + if (PackageManagerHelper.isAppSuspended(appInfo)) { + info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED; + } + info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 + ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES; + + if (FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO + && appInfo.targetSdkVersion >= Build.VERSION_CODES.O + && Process.myUserHandle().equals(lai.getUser())) { + // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon. + info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON; + } + } } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 3162286c1c..79a34a0988 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -385,16 +385,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { @Override public void setPressedIcon(BubbleTextView icon, Bitmap background) { - if (icon == null || background == null) { - mTouchFeedbackView.setBitmap(null); - mTouchFeedbackView.animate().cancel(); - } else { - if (mTouchFeedbackView.setBitmap(background)) { - mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets, - null /* clipAgainstView */); - mTouchFeedbackView.animateShadow(); - } - } + mTouchFeedbackView.setPressedIcon(icon, background); } void setIsDragOverlapping(boolean isDragOverlapping) { diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java index aad1112984..5391b4d465 100644 --- a/src/com/android/launcher3/ClickShadowView.java +++ b/src/com/android/launcher3/ClickShadowView.java @@ -16,15 +16,28 @@ package com.android.launcher3; +import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_DURATION; +import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR; +import static com.android.launcher3.LauncherAnimUtils.ELEVATION; +import static com.android.launcher3.graphics.HolographicOutlineHelper.ADAPTIVE_ICON_SHADOW_BITMAP; + +import android.animation.ObjectAnimator; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.Property; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; public class ClickShadowView extends View { @@ -32,6 +45,8 @@ public class ClickShadowView extends View { private static final int SHADOW_LOW_ALPHA = 30; private static final int SHADOW_HIGH_ALPHA = 60; + private static float sAdaptiveIconScaleFactor = 1f; + private final Paint mPaint; @ViewDebug.ExportedProperty(category = "launcher") @@ -40,6 +55,10 @@ public class ClickShadowView extends View { private final float mShadowPadding; private Bitmap mBitmap; + private ObjectAnimator mAnim; + + private Drawable mAdaptiveIcon; + private ViewOutlineProvider mOutlineProvider; public ClickShadowView(Context context) { super(context); @@ -50,6 +69,10 @@ public class ClickShadowView extends View { mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift); } + public static void setAdaptiveIconScaleFactor(float factor) { + sAdaptiveIconScaleFactor = factor; + } + /** * @return extra space required by the view to show the shadow. */ @@ -57,11 +80,64 @@ public class ClickShadowView extends View { return (int) (SHADOW_SIZE_FACTOR * mShadowPadding); } + public void setPressedIcon(BubbleTextView icon, Bitmap background) { + if (icon == null) { + setBitmap(null); + cancelAnim(); + return; + } + if (background == null) { + if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) { + // clear animation shadow + } + setBitmap(null); + cancelAnim(); + icon.setOutlineProvider(null); + } else if (setBitmap(background)) { + if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) { + setupAdaptiveShadow(icon); + cancelAnim(); + startAnim(icon, ELEVATION, + getResources().getDimension(R.dimen.click_shadow_elevation)); + } else { + alignWithIconView(icon); + startAnim(this, ALPHA, 1); + } + } + } + + @TargetApi(Build.VERSION_CODES.O) + private void setupAdaptiveShadow(final BubbleTextView view) { + if (mAdaptiveIcon == null) { + mAdaptiveIcon = new AdaptiveIconDrawable(null, null); + mOutlineProvider = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + mAdaptiveIcon.getOutline(outline); + } + }; + } + + int iconWidth = view.getRight() - view.getLeft(); + int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft(); + int drawableWidth = view.getIcon().getBounds().width(); + + Rect bounds = new Rect(); + bounds.left = view.getCompoundPaddingLeft() + (iconHSpace - drawableWidth) / 2; + bounds.right = bounds.left + drawableWidth; + bounds.top = view.getPaddingTop(); + bounds.bottom = bounds.top + view.getIcon().getBounds().height(); + Utilities.scaleRectAboutCenter(bounds, sAdaptiveIconScaleFactor); + + mAdaptiveIcon.setBounds(bounds); + view.setOutlineProvider(mOutlineProvider); + } + /** * Applies the new bitmap. * @return true if the view was invalidated. */ - public boolean setBitmap(Bitmap b) { + private boolean setBitmap(Bitmap b) { if (b != mBitmap){ mBitmap = b; invalidate(); @@ -80,48 +156,51 @@ public class ClickShadowView extends View { } } - public void animateShadow() { - setAlpha(0); - animate().alpha(1) - .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION) - .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR) - .start(); + private void cancelAnim() { + if (mAnim != null) { + mAnim.cancel(); + mAnim.setCurrentPlayTime(0); + mAnim = null; + } + } + + private void startAnim(View target, Property property, float endValue) { + cancelAnim(); + property.set(target, 0f); + mAnim = ObjectAnimator.ofFloat(target, property, endValue); + mAnim.setDuration(CLICK_FEEDBACK_DURATION) + .setInterpolator(CLICK_FEEDBACK_INTERPOLATOR); + mAnim.start(); } /** * Aligns the shadow with {@param view} - * @param viewParent immediate parent of {@param view}. It must be a sibling of this view. + * Note: {@param view} must be a descendant of my parent. */ - public void alignWithIconView(BubbleTextView view, ViewGroup viewParent, View clipAgainstView) { - float leftShift = view.getLeft() + viewParent.getLeft() - getLeft(); - float topShift = view.getTop() + viewParent.getTop() - getTop(); + private void alignWithIconView(BubbleTextView view) { + int[] coords = new int[] {0, 0}; + Utilities.getDescendantCoordRelativeToAncestor( + (ViewGroup) view.getParent(), (View) getParent(), coords, false); + + float leftShift = view.getLeft() + coords[0] - getLeft(); + float topShift = view.getTop() + coords[1] - getTop(); int iconWidth = view.getRight() - view.getLeft(); int iconHeight = view.getBottom() - view.getTop(); int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft(); float drawableWidth = view.getIcon().getBounds().width(); - if (clipAgainstView != null) { - // Set the bounds to clip against - int[] coords = new int[] {0, 0}; - Utilities.getDescendantCoordRelativeToAncestor(clipAgainstView, (View) getParent(), - coords, false); - int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding); - int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ; - setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight)); - } else { - // Reset the clip bounds - setClipBounds(null); - } + // Set the bounds to clip against + int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding); + int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ; + setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight)); setTranslationX(leftShift - + viewParent.getTranslationX() + view.getCompoundPaddingLeft() * view.getScaleX() + (iconHSpace - drawableWidth) * view.getScaleX() / 2 /* drawable gap */ + iconWidth * (1 - view.getScaleX()) / 2 /* gap due to scale */ - mShadowPadding /* extra shadow size */ ); setTranslationY(topShift - + viewParent.getTranslationY() + view.getPaddingTop() * view.getScaleY() /* drawable gap */ + view.getHeight() * (1 - view.getScaleY()) / 2 /* gap due to scale */ - mShadowPadding /* extra shadow size */ diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index 1c4e88b5b0..fea4ddae8e 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -67,7 +67,6 @@ public abstract class ItemInfoWithIcon extends ItemInfo { FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED | FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER; - /** * The item points to a system app. */ @@ -80,6 +79,12 @@ public abstract class ItemInfoWithIcon extends ItemInfo { public static final int FLAG_SYSTEM_MASK = FLAG_SYSTEM_YES | FLAG_SYSTEM_NO; + /** + * Flag indicating that the icon is an {@link android.graphics.drawable.AdaptiveIconDrawable} + * that can be optimized in various way. + */ + public static final int FLAG_ADAPTIVE_ICON = 1 << 8; + /** * Status associated with the system state of the underlying item. This is calculated every * time a new info is created and not persisted on the disk. diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index dfe51af868..9869fdf7e2 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -164,4 +164,17 @@ public class LauncherAnimUtils { view.setScaleY(scale); } }; + + public static final Property ELEVATION = + new Property(Float.class, "elevation") { + @Override + public Float get(View view) { + return view.getElevation(); + } + + @Override + public void set(View view, Float elevation) { + view.setElevation(elevation); + } + }; } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index a1f37ba222..81f58423f9 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -157,14 +157,7 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, @Override public void setPressedIcon(BubbleTextView icon, Bitmap background) { - if (icon == null || background == null) { - mTouchFeedbackView.setBitmap(null); - mTouchFeedbackView.animate().cancel(); - } else if (mTouchFeedbackView.setBitmap(background)) { - View rv = findViewById(R.id.apps_list_view); - mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv); - mTouchFeedbackView.animateShadow(); - } + mTouchFeedbackView.setPressedIcon(icon, background); } /** diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java index 9e67f56e3f..fdf2d679a9 100644 --- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java @@ -16,6 +16,8 @@ package com.android.launcher3.graphics; +import static com.android.launcher3.ItemInfoWithIcon.FLAG_ADAPTIVE_ICON; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; @@ -29,6 +31,7 @@ import android.graphics.drawable.Drawable; import android.util.SparseArray; import com.android.launcher3.BubbleTextView; +import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; /** @@ -37,6 +40,12 @@ import com.android.launcher3.R; */ public class HolographicOutlineHelper { + /** + * Bitmap used as shadow for Adaptive icons + */ + public static final Bitmap ADAPTIVE_ICON_SHADOW_BITMAP = + Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); + private static HolographicOutlineHelper sInstance; private final Canvas mCanvas = new Canvas(); @@ -63,6 +72,11 @@ public class HolographicOutlineHelper { } public Bitmap createMediumDropShadow(BubbleTextView view) { + if (view.getTag() instanceof ItemInfoWithIcon && + ((((ItemInfoWithIcon) view.getTag()).runtimeStatusFlags & FLAG_ADAPTIVE_ICON) + != 0)) { + return ADAPTIVE_ICON_SHADOW_BITMAP; + } Drawable drawable = view.getIcon(); if (drawable == null) { return null; @@ -119,7 +133,7 @@ public class HolographicOutlineHelper { } public void recycleShadowBitmap(Bitmap bitmap) { - if (bitmap != null) { + if (bitmap != null && bitmap != ADAPTIVE_ICON_SHADOW_BITMAP) { mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap); } } diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 47f370a175..ccef9b7c7d 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -16,15 +16,11 @@ package com.android.launcher3.model; -import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO; -import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_YES; - import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.database.Cursor; import android.database.CursorWrapper; @@ -36,6 +32,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; +import com.android.launcher3.AppInfo; import com.android.launcher3.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; @@ -52,7 +49,6 @@ import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.LongArrayMap; -import com.android.launcher3.util.PackageManagerHelper; import java.net.URISyntaxException; import java.security.InvalidParameterException; @@ -206,7 +202,6 @@ public class LoaderCursor extends CursorWrapper { return TextUtils.isEmpty(title) ? "" : Utilities.trim(title); } - /** * Make an ShortcutInfo object for a restored application or shortcut item that points * to a package that is not yet installed on the system. @@ -279,12 +274,7 @@ public class LoaderCursor extends CursorWrapper { } if (lai != null) { - ApplicationInfo appInfo = lai.getApplicationInfo(); - if (PackageManagerHelper.isAppSuspended(appInfo)) { - info.runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; - } - info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 - ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES; + AppInfo.updateRuntimeFlagsForActivityTarget(info, lai); } // from the db diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index c2cfebb85f..310416f0b8 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; import android.graphics.Bitmap; +import android.graphics.drawable.AdaptiveIconDrawable; import android.os.Handler; import android.os.Process; import android.os.UserHandle; @@ -35,6 +36,7 @@ import android.util.MutableInt; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; +import com.android.launcher3.ClickShadowView; import com.android.launcher3.FolderInfo; import com.android.launcher3.IconCache; import com.android.launcher3.InstallShortcutReceiver; @@ -52,6 +54,7 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIconPreviewVerifier; +import com.android.launcher3.graphics.IconNormalizer; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.ImportDataTask; @@ -145,7 +148,9 @@ public class LoaderTask implements Runnable { TraceHelper.beginSection(TAG); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { - TraceHelper.partitionSection(TAG, "step 1.1: loading workspace"); + TraceHelper.partitionSection(TAG, "step 1.1: loading UI resources"); + loadUiResources(); + TraceHelper.partitionSection(TAG, "step 1.2: loading workspace"); loadWorkspace(); verifyNotStopped(); @@ -208,6 +213,14 @@ public class LoaderTask implements Runnable { this.notify(); } + public void loadUiResources() { + if (Utilities.ATLEAST_OREO) { + ClickShadowView.setAdaptiveIconScaleFactor( + IconNormalizer.getInstance(mApp.getContext()).getScale( + new AdaptiveIconDrawable(null, null), null, null, null)); + } + } + private void loadWorkspace() { final Context context = mApp.getContext(); final ContentResolver contentResolver = context.getContentResolver();