diff options
Diffstat (limited to 'src')
14 files changed, 361 insertions, 167 deletions
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 424ffde1f..64550560f 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -175,6 +175,10 @@ public abstract class BaseActivity extends Activity mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE; mForceInvisible = 0; super.onStop(); + + // Reset the overridden sysui flags used for the task-swipe launch animation, this is a + // catch all for if we do not get resumed (and therefore not paused below) + getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0); } @Override diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index ca410aa4d..4e905c726 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -80,6 +80,7 @@ import android.widget.Toast; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.PropertyListBuilder; @@ -460,13 +461,18 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private void onIdpChanged(InvariantDeviceProfile idp) { mUserEventDispatcher = null; + DeviceProfile oldWallpaperProfile = getWallpaperDeviceProfile(); initDeviceProfile(idp); dispatchDeviceProfileChanged(); reapplyUi(); mDragLayer.recreateControllers(); - // TODO: We can probably avoid rebind when only screen size changed. - rebindModel(); + // Calling onSaveInstanceState ensures that static cache used by listWidgets is + // initialized properly. + onSaveInstanceState(new Bundle()); + if (oldWallpaperProfile != getWallpaperDeviceProfile()) { + rebindModel(); + } } public void onAssistantVisibilityChanged(float visibility) { @@ -2243,8 +2249,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } mPendingExecutor = executor; if (!isInState(ALL_APPS)) { - mAppsView.getAppsStore().setDeferUpdates(true); - mPendingExecutor.execute(() -> mAppsView.getAppsStore().setDeferUpdates(false)); + mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW); + mPendingExecutor.execute(() -> mAppsView.getAppsStore().disableDeferUpdates( + AllAppsStore.DEFER_UPDATES_NEXT_DRAW)); } executor.attachTo(this); diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java index fb50dfbbb..4bddc6a4e 100644 --- a/src/com/android/launcher3/LauncherAppTransitionManager.java +++ b/src/com/android/launcher3/LauncherAppTransitionManager.java @@ -51,4 +51,8 @@ public class LauncherAppTransitionManager implements ResourceBasedOverride { } return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); } + + public boolean supportsAdaptiveIconAnimation() { + return false; + } } diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java index 49b380b41..f964b8d9e 100644 --- a/src/com/android/launcher3/LauncherRootView.java +++ b/src/com/android/launcher3/LauncherRootView.java @@ -8,18 +8,22 @@ import android.app.ActivityManager; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.view.ViewDebug; +import android.view.WindowInsets; import java.util.Collections; import java.util.List; public class LauncherRootView extends InsettableFrameLayout { + private final Rect mTempRect = new Rect(); + private final Launcher mLauncher; private final Paint mOpaquePaint; @@ -56,9 +60,7 @@ public class LauncherRootView extends InsettableFrameLayout { super.onFinishInflate(); } - @TargetApi(23) - @Override - protected boolean fitSystemWindows(Rect insets) { + private void handleSystemWindowInsets(Rect insets) { mConsumedInsets.setEmpty(); boolean drawInsetBar = false; if (mLauncher.isInMultiWindowMode() @@ -66,13 +68,13 @@ public class LauncherRootView extends InsettableFrameLayout { mConsumedInsets.left = insets.left; mConsumedInsets.right = insets.right; mConsumedInsets.bottom = insets.bottom; - insets = new Rect(0, insets.top, 0, 0); + insets.set(0, insets.top, 0, 0); drawInsetBar = true; } else if ((insets.right > 0 || insets.left > 0) && getContext().getSystemService(ActivityManager.class).isLowRamDevice()) { mConsumedInsets.left = insets.left; mConsumedInsets.right = insets.right; - insets = new Rect(0, insets.top, 0, insets.bottom); + insets.set(0, insets.top, 0, insets.bottom); drawInsetBar = true; } @@ -99,8 +101,19 @@ public class LauncherRootView extends InsettableFrameLayout { if (resetState) { mLauncher.getStateManager().reapplyState(true /* cancelCurrentAnimation */); } + } - return false; // Let children get the full insets + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mTempRect.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); + handleSystemWindowInsets(mTempRect); + if (Utilities.ATLEAST_Q) { + return insets.inset(mConsumedInsets.left, mConsumedInsets.top, + mConsumedInsets.right, mConsumedInsets.bottom); + } else { + return insets.replaceSystemWindowInsets(mTempRect); + } } @Override diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index cc9bda710..7bdbb95f7 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -69,6 +69,7 @@ import com.android.launcher3.compat.ShortcutConfigActivityInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutKey; @@ -562,6 +563,20 @@ public final class Utilities { return spanned; } + /** + * Prefixes a text with the provided icon + */ + public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) { + // Update the hint to contain the icon. + // Prefix the original hint with two spaces. The first space gets replaced by the icon + // using span. The second space is used for a singe space character between the hint + // and the icon. + SpannableString spanned = new SpannableString(" " + msg); + spanned.setSpan(new TintedDrawableSpan(context, iconRes), + 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + return spanned; + } + public static SharedPreferences getPrefs(Context context) { return context.getSharedPreferences( LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index bd97dc86d..ea9b077c6 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -632,11 +632,12 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo final boolean result = super.dispatchTouchEvent(ev); switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: - if (result) mAllAppsStore.setDeferUpdates(true); + if (result) mAllAppsStore.enableDeferUpdates( + AllAppsStore.DEFER_UPDATES_USER_INTERACTION); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mAllAppsStore.setDeferUpdates(false); + mAllAppsStore.disableDeferUpdates(AllAppsStore.DEFER_UPDATES_USER_INTERACTION); break; } return result; diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 8e7fec863..160042e69 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; -import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -38,12 +37,19 @@ import java.util.function.Predicate; */ public class AllAppsStore { + // Defer updates flag used to defer all apps updates to the next draw. + public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0; + // Defer updates flag used to defer all apps updates while the user interacts with all apps. + public static final int DEFER_UPDATES_USER_INTERACTION = 1 << 1; + // Defer updates flag used to defer all apps updates by a test's request. + public static final int DEFER_UPDATES_TEST = 1 << 2; + private PackageUserKey mTempKey = new PackageUserKey(null, null); private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>(); private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>(); private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>(); - private boolean mDeferUpdates = false; + private int mDeferUpdatesFlags = 0; private boolean mUpdatePending = false; public Collection<AppInfo> getApps() { @@ -62,17 +68,22 @@ public class AllAppsStore { return mComponentToAppMap.get(key); } - public void setDeferUpdates(boolean deferUpdates) { - if (mDeferUpdates != deferUpdates) { - mDeferUpdates = deferUpdates; + public void enableDeferUpdates(int flag) { + mDeferUpdatesFlags |= flag; + } - if (!mDeferUpdates && mUpdatePending) { - notifyUpdate(); - mUpdatePending = false; - } + public void disableDeferUpdates(int flag) { + mDeferUpdatesFlags &= ~flag; + if (mDeferUpdatesFlags == 0 && mUpdatePending) { + notifyUpdate(); + mUpdatePending = false; } } + public int getDeferUpdatesFlags() { + return mDeferUpdatesFlags; + } + /** * Adds or updates existing apps in the list */ @@ -95,7 +106,7 @@ public class AllAppsStore { private void notifyUpdate() { - if (mDeferUpdates) { + if (mDeferUpdatesFlags != 0) { mUpdatePending = true; return; } diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index 1ff484b49..31fcc8c64 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -20,6 +20,7 @@ import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER; +import static com.android.launcher3.Utilities.prefixTextWithIcon; import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; import android.content.Context; @@ -40,6 +41,7 @@ import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AlphabeticalAppsList; @@ -89,14 +91,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText mFixedTranslationY = getTranslationY(); mMarginTopAdjusting = mFixedTranslationY - getPaddingTop(); - // Update the hint to contain the icon. - // Prefix the original hint with two spaces. The first space gets replaced by the icon - // using span. The second space is used for a singe space character between the hint - // and the icon. - SpannableString spanned = new SpannableString(" " + getHint()); - spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), - 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); - setHint(spanned); + setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint())); } @Override diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 7e20d11c2..54d0db100 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -105,7 +105,7 @@ abstract class BaseFlags { "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview"); public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag( - "ENABLE_HINTS_IN_OVERVIEW", false, + "ENABLE_HINTS_IN_OVERVIEW", true, "Show chip hints and gleams on the overview screen"); public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag( diff --git a/src/com/android/launcher3/graphics/TintedDrawableSpan.java b/src/com/android/launcher3/graphics/TintedDrawableSpan.java index d7195750d..0bfc435cc 100644 --- a/src/com/android/launcher3/graphics/TintedDrawableSpan.java +++ b/src/com/android/launcher3/graphics/TintedDrawableSpan.java @@ -32,7 +32,7 @@ public class TintedDrawableSpan extends DynamicDrawableSpan { public TintedDrawableSpan(Context context, int resourceId) { super(ALIGN_BOTTOM); - mDrawable = context.getDrawable(resourceId); + mDrawable = context.getDrawable(resourceId).mutate(); mOldTint = 0; mDrawable.setTint(0); } diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index b8476aa1d..d2e196138 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -23,9 +23,13 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherState; +import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.util.ResourceBasedOverride; +import java.util.concurrent.ExecutionException; + public class TestInformationHandler implements ResourceBasedOverride { public static TestInformationHandler newInstance(Context context) { @@ -77,6 +81,32 @@ public class TestInformationHandler implements ResourceBasedOverride { case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING: TestProtocol.sDebugTracing = false; break; + + case TestProtocol.REQUEST_FREEZE_APP_LIST: + new MainThreadExecutor().execute(() -> + mLauncher.getAppsView().getAppsStore().enableDeferUpdates( + AllAppsStore.DEFER_UPDATES_TEST)); + break; + + case TestProtocol.REQUEST_UNFREEZE_APP_LIST: + new MainThreadExecutor().execute(() -> + mLauncher.getAppsView().getAppsStore().disableDeferUpdates( + AllAppsStore.DEFER_UPDATES_TEST)); + break; + + case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: { + try { + final int deferUpdatesFlags = new MainThreadExecutor().submit(() -> + mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get(); + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, + deferUpdatesFlags); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + break; + } } return response; } diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 02e6bbdd7..6ffc2d9cd 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -57,6 +57,7 @@ public final class TestProtocol { } public static final String TEST_INFO_RESPONSE_FIELD = "response"; + public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT = "home-to-overview-swipe-height"; public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT = @@ -65,6 +66,10 @@ public final class TestProtocol { "all-apps-to-overview-swipe-height"; public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT = "home-to-all-apps-swipe-height"; + public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list"; + public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list"; + public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags"; + public static boolean sDebugTracing = false; public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing"; public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing"; diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index f858dc4c6..85f763d09 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -49,6 +49,7 @@ import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.widget.PendingAppWidgetHostView; import com.android.launcher3.widget.WidgetAddFlowHandler; @@ -259,6 +260,10 @@ public class ItemClickHandler { intent.setPackage(null); } } + if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation()) { + // Preload the icon to reduce latency b/w swapping the floating view with the original. + FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */); + } launcher.startActivitySafely(v, intent, item, sourceContainer); } } diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 339681cd6..b6c4fedac 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -17,6 +17,7 @@ package com.android.launcher3.views; import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA; import static com.android.launcher3.Utilities.getBadge; +import static com.android.launcher3.Utilities.getFullDrawable; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; @@ -42,6 +43,7 @@ import android.os.Build; import android.os.CancellationSignal; import android.os.Handler; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; @@ -65,6 +67,7 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.DeepShortcutView; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; @@ -77,6 +80,11 @@ import androidx.dynamicanimation.animation.SpringForce; public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener { + private static final String TAG = FloatingIconView.class.getSimpleName(); + + // Manages loading the icon on a worker thread + private static @Nullable IconLoadResult sIconLoadResult; + public static final float SHAPE_PROGRESS_DURATION = 0.10f; private static final int FADE_DURATION_MS = 200; private static final Rect sTmpRect = new Rect(); @@ -126,6 +134,8 @@ public class FloatingIconView extends View implements private boolean mIsAdaptiveIcon = false; private boolean mIsOpening; + private IconLoadResult mIconLoadResult; + private @Nullable Drawable mBadge; private @Nullable Drawable mForeground; private @Nullable Drawable mBackground; @@ -299,8 +309,8 @@ public class FloatingIconView extends View implements * @param v The view to copy * @param positionOut Rect that will hold the size and position of v. */ - private void matchPositionOf(View v, RectF positionOut) { - float rotation = getLocationBoundsForView(v, positionOut); + private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) { + float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut); final LayoutParams lp = new LayoutParams( Math.round(positionOut.width()), Math.round(positionOut.height())); @@ -328,8 +338,9 @@ public class FloatingIconView extends View implements * - For DeepShortcutView, we return the bounds of the icon view. * - For BubbleTextView, we return the icon bounds. */ - private float getLocationBoundsForView(View v, RectF outRect) { - boolean ignoreTransform = !mIsOpening; + private static float getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, + RectF outRect) { + boolean ignoreTransform = !isOpening; if (v instanceof DeepShortcutView) { v = ((DeepShortcutView) v).getBubbleText(); ignoreTransform = false; @@ -353,7 +364,7 @@ public class FloatingIconView extends View implements float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right, iconBounds.bottom}; float[] rotation = new float[] {0}; - Utilities.getDescendantCoordRelativeToAncestor(v, mLauncher.getDragLayer(), points, + Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points, false, ignoreTransform, rotation); outRect.set( Math.min(points[0], points[2]), @@ -363,148 +374,217 @@ public class FloatingIconView extends View implements return rotation[0]; } + /** + * Loads the icon and saves the results to {@link #sIconLoadResult}. + * Runs onIconLoaded callback (if any), which signifies that the FloatingIconView is + * ready to display the icon. Otherwise, the FloatingIconView will grab the results when its + * initialized. + * + * @param originalView The View that the FloatingIconView will replace. + * @param info ItemInfo of the originalView + * @param pos The position of the view. + */ @WorkerThread @SuppressWarnings("WrongThread") - private void getIcon(View v, ItemInfo info, boolean isOpening, - Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) { - final LayoutParams lp = (LayoutParams) getLayoutParams(); + private static void getIconResult(Launcher l, View originalView, ItemInfo info, RectF pos, + IconLoadResult iconLoadResult) { Drawable drawable = null; + Drawable badge = null; boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - Drawable btvIcon = v instanceof BubbleTextView ? ((BubbleTextView) v).getIcon() : null; + Drawable btvIcon = originalView instanceof BubbleTextView + ? ((BubbleTextView) originalView).getIcon() : null; if (info instanceof SystemShortcut) { - if (v instanceof ImageView) { - drawable = ((ImageView) v).getDrawable(); - } else if (v instanceof DeepShortcutView) { - drawable = ((DeepShortcutView) v).getIconView().getBackground(); + if (originalView instanceof ImageView) { + drawable = ((ImageView) originalView).getDrawable(); + } else if (originalView instanceof DeepShortcutView) { + drawable = ((DeepShortcutView) originalView).getIconView().getBackground(); } else { - drawable = v.getBackground(); + drawable = originalView.getBackground(); } } else { - boolean isFolderIcon = v instanceof FolderIcon; - int width = isFolderIcon ? v.getWidth() : lp.width; - int height = isFolderIcon ? v.getHeight() : lp.height; + boolean isFolderIcon = originalView instanceof FolderIcon; + int width = isFolderIcon ? originalView.getWidth() : (int) pos.width(); + int height = isFolderIcon ? originalView.getHeight() : (int) pos.height(); if (supportsAdaptiveIcons) { - drawable = Utilities.getFullDrawable(mLauncher, info, width, height, false, - sTmpObjArray); + drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray); if (drawable instanceof AdaptiveIconDrawable) { - mBadge = getBadge(mLauncher, info, sTmpObjArray[0]); + badge = getBadge(l, info, sTmpObjArray[0]); } else { // The drawable we get back is not an adaptive icon, so we need to use the // BubbleTextView icon that is already legacy treated. drawable = btvIcon; } } else { - if (v instanceof BubbleTextView) { + if (originalView instanceof BubbleTextView) { // Similar to DragView, we simply use the BubbleTextView icon here. drawable = btvIcon; } else { - drawable = Utilities.getFullDrawable(mLauncher, info, width, height, false, - sTmpObjArray); + drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray); } } } - Drawable finalDrawable = drawable == null ? null - : drawable.getConstantState().newDrawable(); - boolean isAdaptiveIcon = supportsAdaptiveIcons - && finalDrawable instanceof AdaptiveIconDrawable; - int iconOffset = getOffsetForIconBounds(finalDrawable); + drawable = drawable == null ? null : drawable.getConstantState().newDrawable(); + int iconOffset = getOffsetForIconBounds(l, drawable, pos); + synchronized (iconLoadResult) { + iconLoadResult.drawable = drawable; + iconLoadResult.badge = badge; + iconLoadResult.iconOffset = iconOffset; + if (iconLoadResult.onIconLoaded != null) { + l.getMainExecutor().execute(iconLoadResult.onIconLoaded); + iconLoadResult.onIconLoaded = null; + } + iconLoadResult.isIconLoaded = true; + } + } - mLauncher.getMainExecutor().execute(() -> { - if (isAdaptiveIcon) { - mIsAdaptiveIcon = true; - boolean isFolderIcon = finalDrawable instanceof FolderAdaptiveIcon; + /** + * Sets the drawables of the {@param originalView} onto this view. + * + * @param originalView The View that the FloatingIconView will replace. + * @param drawable The drawable of the original view. + * @param badge The badge of the original view. + * @param iconOffset The amount of offset needed to match this view with the original view. + */ + @UiThread + private void setIcon(View originalView, @Nullable Drawable drawable, @Nullable Drawable badge, + int iconOffset) { + mBadge = badge; - AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) finalDrawable; - Drawable background = adaptiveIcon.getBackground(); - if (background == null) { - background = new ColorDrawable(Color.TRANSPARENT); - } - mBackground = background; - Drawable foreground = adaptiveIcon.getForeground(); - if (foreground == null) { - foreground = new ColorDrawable(Color.TRANSPARENT); - } - mForeground = foreground; + mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable; + if (mIsAdaptiveIcon) { + boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon; - final int originalHeight = lp.height; - final int originalWidth = lp.width; + AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable; + Drawable background = adaptiveIcon.getBackground(); + if (background == null) { + background = new ColorDrawable(Color.TRANSPARENT); + } + mBackground = background; + Drawable foreground = adaptiveIcon.getForeground(); + if (foreground == null) { + foreground = new ColorDrawable(Color.TRANSPARENT); + } + mForeground = foreground; - int blurMargin = mBlurSizeOutline / 2; - mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight); - if (!isFolderIcon) { - mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin); - } - mForeground.setBounds(mFinalDrawableBounds); - mBackground.setBounds(mFinalDrawableBounds); + final LayoutParams lp = (LayoutParams) getLayoutParams(); + final int originalHeight = lp.height; + final int originalWidth = lp.width; - mStartRevealRect.set(0, 0, originalWidth, originalHeight); + int blurMargin = mBlurSizeOutline / 2; + mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight); - if (mBadge != null) { - mBadge.setBounds(mStartRevealRect); - if (!isOpening && !isFolderIcon) { - DRAWABLE_ALPHA.set(mBadge, 0); - } + if (!isFolderIcon) { + mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin); + } + mForeground.setBounds(mFinalDrawableBounds); + mBackground.setBounds(mFinalDrawableBounds); + + mStartRevealRect.set(0, 0, originalWidth, originalHeight); + + if (mBadge != null) { + mBadge.setBounds(mStartRevealRect); + if (!mIsOpening && !isFolderIcon) { + DRAWABLE_ALPHA.set(mBadge, 0); } + } - if (isFolderIcon) { - ((FolderIcon) v).getPreviewBounds(sTmpRect); - float bgStroke = ((FolderIcon) v).getBackgroundStrokeWidth(); - if (mForeground instanceof ShiftedBitmapDrawable) { - ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground; - sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke); - sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke); - } - if (mBadge instanceof ShiftedBitmapDrawable) { - ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge; - sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke); - sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke); - } - } else { - Utilities.scaleRectAboutCenter(mStartRevealRect, - IconShape.getNormalizationScale()); + if (isFolderIcon) { + ((FolderIcon) originalView).getPreviewBounds(sTmpRect); + float bgStroke = ((FolderIcon) originalView).getBackgroundStrokeWidth(); + if (mForeground instanceof ShiftedBitmapDrawable) { + ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground; + sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke); + sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke); + } + if (mBadge instanceof ShiftedBitmapDrawable) { + ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge; + sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke); + sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke); } + } else { + Utilities.scaleRectAboutCenter(mStartRevealRect, + IconShape.getNormalizationScale()); + } - float aspectRatio = mLauncher.getDeviceProfile().aspectRatio; - if (mIsVerticalBarLayout) { - lp.width = (int) Math.max(lp.width, lp.height * aspectRatio); - } else { - lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); + float aspectRatio = mLauncher.getDeviceProfile().aspectRatio; + if (mIsVerticalBarLayout) { + lp.width = (int) Math.max(lp.width, lp.height * aspectRatio); + } else { + lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); + } + layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin + + lp.height); + + float scale = Math.max((float) lp.height / originalHeight, + (float) lp.width / originalWidth); + float bgDrawableStartScale; + if (mIsOpening) { + bgDrawableStartScale = 1f; + mOutline.set(0, 0, originalWidth, originalHeight); + } else { + bgDrawableStartScale = scale; + mOutline.set(0, 0, lp.width, lp.height); + } + setBackgroundDrawableBounds(bgDrawableStartScale); + mEndRevealRect.set(0, 0, lp.width, lp.height); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mOutline, mTaskCornerRadius); } - layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin - + lp.height); + }); + setClipToOutline(true); + } else { + setBackground(drawable); + setClipToOutline(false); + } - float scale = Math.max((float) lp.height / originalHeight, - (float) lp.width / originalWidth); - float bgDrawableStartScale; + invalidate(); + invalidateOutline(); + } + + /** + * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a + * callback to set the icon once the icon result is loaded. + */ + private void checkIconResult(View originalView, boolean isOpening) { + CancellationSignal cancellationSignal = new CancellationSignal(); + if (!isOpening) { + // Hide immediately since the floating view starts at a different location. + originalView.setVisibility(INVISIBLE); + cancellationSignal.setOnCancelListener(() -> originalView.setVisibility(VISIBLE)); + } + + if (mIconLoadResult == null) { + Log.w(TAG, "No icon load result found in checkIconResult"); + return; + } + + synchronized (mIconLoadResult) { + if (mIconLoadResult.isIconLoaded) { + setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, + mIconLoadResult.iconOffset); if (isOpening) { - bgDrawableStartScale = 1f; - mOutline.set(0, 0, originalWidth, originalHeight); - } else { - bgDrawableStartScale = scale; - mOutline.set(0, 0, lp.width, lp.height); + originalView.setVisibility(INVISIBLE); } - setBackgroundDrawableBounds(bgDrawableStartScale); - mEndRevealRect.set(0, 0, lp.width, lp.height); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(mOutline, mTaskCornerRadius); - } - }); - setClipToOutline(true); } else { - setBackground(finalDrawable); - setClipToOutline(false); - } - - if (!loadIconSignal.isCanceled()) { - onIconLoadedRunnable.run(); + mIconLoadResult.onIconLoaded = () -> { + if (cancellationSignal.isCanceled()) { + return; + } + if (mIconLoadResult.isIconLoaded) { + setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, + mIconLoadResult.iconOffset); + } + // Delay swapping views until the icon is loaded to prevent a flash. + setVisibility(VISIBLE); + originalView.setVisibility(INVISIBLE); + }; } - invalidate(); - invalidateOutline(); - }); + } + mLoadIconSignal = cancellationSignal; } private void setBackgroundDrawableBounds(float scale) { @@ -521,17 +601,19 @@ public class FloatingIconView extends View implements @WorkerThread @SuppressWarnings("WrongThread") - private int getOffsetForIconBounds(Drawable drawable) { + private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !(drawable instanceof AdaptiveIconDrawable)) { return 0; } + int blurSizeOutline = + l.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); - final LayoutParams lp = (LayoutParams) getLayoutParams(); - Rect bounds = new Rect(0, 0, lp.width + mBlurSizeOutline, lp.height + mBlurSizeOutline); - bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2); + Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline, + (int) position.height() + blurSizeOutline); + bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2); - try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) { + try (LauncherIcons li = LauncherIcons.obtain(l)) { Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null, null, null)); } @@ -587,7 +669,11 @@ public class FloatingIconView extends View implements } @Override - public void onAnimationStart(Animator animator) {} + public void onAnimationStart(Animator animator) { + if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) { + setVisibility(View.VISIBLE); + } + } @Override public void onAnimationCancel(Animator animator) {} @@ -598,7 +684,8 @@ public class FloatingIconView extends View implements @Override public void onGlobalLayout() { if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) { - float rotation = getLocationBoundsForView(mOriginalIcon, sTmpRectF); + float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, + sTmpRectF); if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) { updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams()); if (mOnTargetChangeRunnable != null) { @@ -613,6 +700,22 @@ public class FloatingIconView extends View implements } /** + * Loads the icon drawable on a worker thread to reduce latency between swapping views. + */ + @UiThread + public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { + IconLoadResult result = new IconLoadResult(); + new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> { + RectF position = new RectF(); + getLocationBoundsForView(l, v, isOpening, position); + getIconResult(l, v, info, position, result); + }); + + sIconLoadResult = result; + return result; + } + + /** * Creates a floating icon view for {@param originalView}. * @param originalView The view to copy * @param hideOriginal If true, it will hide {@param originalView} while this view is visible. @@ -624,38 +727,30 @@ public class FloatingIconView extends View implements boolean hideOriginal, RectF positionOut, boolean isOpening) { final DragLayer dragLayer = launcher.getDragLayer(); ViewGroup parent = (ViewGroup) dragLayer.getParent(); - FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view, launcher, parent); view.recycle(); + // Get the drawable on the background thread + boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal; + view.mIconLoadResult = sIconLoadResult; + if (shouldLoadIcon && view.mIconLoadResult == null) { + view.mIconLoadResult = fetchIcon(launcher, originalView, + (ItemInfo) originalView.getTag(), isOpening); + } + sIconLoadResult = null; + view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout(); view.mIsOpening = isOpening; - view.mOriginalIcon = originalView; view.mPositionOut = positionOut; + // Match the position of the original view. - view.matchPositionOf(originalView, positionOut); + view.matchPositionOf(launcher, originalView, isOpening, positionOut); - // Get the drawable on the background thread // Must be called after matchPositionOf so that we know what size to load. - if (originalView.getTag() instanceof ItemInfo && hideOriginal) { - view.mLoadIconSignal = new CancellationSignal(); - Runnable onIconLoaded = () -> { - // Delay swapping views until the icon is loaded to prevent a flash. - view.setVisibility(VISIBLE); - originalView.setVisibility(INVISIBLE); - }; - if (!isOpening) { - // Hide immediately since the floating view starts at a different location. - originalView.setVisibility(INVISIBLE); - view.mLoadIconSignal.setOnCancelListener(() -> originalView.setVisibility(VISIBLE)); - } - CancellationSignal loadIconSignal = view.mLoadIconSignal; - new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> { - view.getIcon(originalView, (ItemInfo) originalView.getTag(), isOpening, - onIconLoaded, loadIconSignal); - }); + if (shouldLoadIcon) { + view.checkIconResult(originalView, isOpening); } // We need to add it to the overlay, but keep it invisible until animation starts.. @@ -779,5 +874,14 @@ public class FloatingIconView extends View implements mFgSpringY.cancel(); mBadge = null; sTmpObjArray[0] = null; + mIconLoadResult = null; + } + + private static class IconLoadResult { + Drawable drawable; + Drawable badge; + int iconOffset; + Runnable onIconLoaded; + boolean isIconLoaded; } } |