From 677978c8aba8f577b39485b40e33902b2015429f Mon Sep 17 00:00:00 2001 From: Stefan Andonian Date: Thu, 11 Aug 2022 20:36:41 +0000 Subject: [PATCH 1/7] Fix for bug where expensive launcher animations were being paused and never resumed. Bug: 240623130 Test: Verified on device that the issue is not reproducible after fix. Change-Id: I7b1e2f70fd75b1eddf340a4f85465cc6d5b65f73 (cherry picked from commit 8b336195a6f287f03d17db02d961c1f2d85c4eda) --- .../src/com/android/launcher3/QuickstepTransitionManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index e1a3b729c1..9aeb3709ac 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -518,6 +518,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener appsView.setAlpha(startAlpha); SCALE_PROPERTY.set(appsView, startScale); appsView.setLayerType(View.LAYER_TYPE_NONE, null); + mLauncher.resumeExpensiveViewUpdates(); }; } else if (mLauncher.isInState(OVERVIEW)) { endListener = composeViewContentAnimator(launcherAnimator, alphas, scales); @@ -647,6 +648,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener overview.setFreezeViewVisibility(false); SCALE_PROPERTY.set(overview, 1f); mLauncher.getStateManager().reapplyState(); + mLauncher.resumeExpensiveViewUpdates(); }; } From 581aa038066526bc50d95a2b3975b08f3dfc53cf Mon Sep 17 00:00:00 2001 From: Holly Sun Date: Fri, 2 Dec 2022 15:15:17 -0800 Subject: [PATCH 2/7] Remove feature flags and use search config flags. Bug: 210661150 Test: manual Change-Id: I6aa176895f80f31840570ce2c0ecbfbfb11079a1 --- src/com/android/launcher3/config/FeatureFlags.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index f9883e379c..260488d489 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -89,14 +89,6 @@ public final class FeatureFlags { getDebugFlag("ENABLE_FLOATING_SEARCH_BAR", false, "Keep All Apps search bar at the bottom (but above keyboard if open)"); - public static final BooleanFlag ENABLE_QUICK_LAUNCH_V2 = new DeviceFlag( - "ENABLE_QUICK_LAUNCH_V2", false, "Use quick launch v2 " - + "behavior. Quick search and quick launch v1 would be unavailable if this is enabled"); - - public static final BooleanFlag GBOARD_UPDATE_ENTER_KEY = new DeviceFlag( - "GBOARD_UPDATE_ENTER_KEY", false, "Update gBoard enter key " - + "icon dynamically based on top search content for Quick Launch V2"); - public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER", true, "Hide header on keyboard before typing in all apps"); From f0de6cc2cad5e2224bccf60cab31b26468ba0231 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Wed, 7 Dec 2022 19:45:55 +0000 Subject: [PATCH 3/7] Revert "Cancel gestures on launcher destroy" Revert "Refactor SchedulerCallback implementation" Revert submission 20555112-launcher-destroy-gestures Reason for revert: causes test failures Reverted Changes: Ib43cdd597:Refactor SchedulerCallback implementation Ia8e936e26:Cancel gestures on launcher destroy Change-Id: I2199e39237b7c9fe25784b46e16960cd5f17c46c --- ...istener.java => LauncherInitListener.java} | 21 ++-- .../android/quickstep/AbsSwipeUpHandler.java | 13 +- .../quickstep/BaseActivityInterface.java | 15 +-- .../quickstep/FallbackActivityInterface.java | 12 +- .../quickstep/LauncherActivityInterface.java | 11 +- .../util/ActiveGestureErrorDetector.java | 9 +- .../quickstep/util/ActivityInitListener.java | 73 +++++++++++ .../util/ActivityLifecycleListener.java | 113 ------------------ .../dragndrop/BaseItemDragListener.java | 2 +- .../dragndrop/PinItemDragListener.java | 4 +- .../launcher3/util/ActivityTracker.java | 94 ++++----------- 11 files changed, 121 insertions(+), 246 deletions(-) rename quickstep/src/com/android/launcher3/{LauncherLifecycleListener.java => LauncherInitListener.java} (78%) create mode 100644 quickstep/src/com/android/quickstep/util/ActivityInitListener.java delete mode 100644 quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java diff --git a/quickstep/src/com/android/launcher3/LauncherLifecycleListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java similarity index 78% rename from quickstep/src/com/android/launcher3/LauncherLifecycleListener.java rename to quickstep/src/com/android/launcher3/LauncherInitListener.java index 82c646d89c..28bd701a48 100644 --- a/quickstep/src/com/android/launcher3/LauncherLifecycleListener.java +++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,19 @@ package com.android.launcher3; import android.animation.AnimatorSet; -import android.annotation.Nullable; import android.annotation.TargetApi; import android.os.Build; import android.os.CancellationSignal; import android.view.RemoteAnimationTarget; import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.quickstep.util.ActivityLifecycleListener; +import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.RemoteAnimationProvider; import java.util.function.BiPredicate; -/** - * {@link ActivityLifecycleListener} for the in-launcher recents. - */ @TargetApi(Build.VERSION_CODES.P) -public class LauncherLifecycleListener extends ActivityLifecycleListener { +public class LauncherInitListener extends ActivityInitListener { private RemoteAnimationProvider mRemoteAnimationProvider; @@ -40,16 +36,13 @@ public class LauncherLifecycleListener extends ActivityLifecycleListener onInitListener, - @Nullable Runnable onDestroyListener) { - super(onInitListener, onDestroyListener, Launcher.ACTIVITY_TRACKER); + public LauncherInitListener(BiPredicate onInitListener) { + super(onInitListener, Launcher.ACTIVITY_TRACKER); } @Override - public boolean handleActivityReady(Launcher launcher, boolean alreadyOnHome) { + public boolean handleInit(Launcher launcher, boolean alreadyOnHome) { if (mRemoteAnimationProvider != null) { QuickstepTransitionManager appTransitionManager = ((QuickstepLauncher) launcher).getAppTransitionManager(); @@ -75,7 +68,7 @@ public class LauncherLifecycleListener extends ActivityLifecycleListener, protected final BaseActivityInterface mActivityInterface; protected final InputConsumerProxy mInputConsumerProxy; - protected final ActivityLifecycleListener mActivityInitListener; + protected final ActivityInitListener mActivityInitListener; // Callbacks to be made once the recents animation starts private final ArrayList mRecentsAnimationStartCallbacks = new ArrayList<>(); private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll; @@ -329,8 +328,7 @@ public abstract class AbsSwipeUpHandler, InputConsumerController inputConsumer) { super(context, deviceState, gestureState); mActivityInterface = gestureState.getActivityInterface(); - mActivityInitListener = mActivityInterface.createActivityLifecycleListener( - this::onActivityInit, this::onActivityDestroy); + mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); mInputConsumerProxy = new InputConsumerProxy(context, /* rotationSupplier = */ () -> { if (mRecentsView == null) { @@ -513,11 +511,6 @@ public abstract class AbsSwipeUpHandler, return true; } - private void onActivityDestroy() { - ActiveGestureLog.INSTANCE.addLog("Launcher activity destroyed", LAUNCHER_DESTROYED); - onGestureCancelled(); - } - /** * Return true if the window should be translated horizontally if the recents view scrolls */ diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 55235185ef..274b686ea4 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -60,7 +60,7 @@ import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.NavigationMode; import com.android.launcher3.views.ScrimView; -import com.android.quickstep.util.ActivityLifecycleListener; +import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -123,17 +123,8 @@ public abstract class BaseActivityInterface callback); - /** - * Creates a activity listener for activity initialized and/or destroyed. One or both of these - * listeners must be provided. - * - * @param onInitListener a callback made when the activity is initialized. The callback should - * return true to continue receiving callbacks (ie. for if the activity is - * recreated). - * @param onDestroyListener a callback made when the activity is destroyed. - */ - public abstract ActivityLifecycleListener createActivityLifecycleListener( - @Nullable Predicate onInitListener, @Nullable Runnable onDestroyListener); + public abstract ActivityInitListener createActivityInitListener( + Predicate onInitListener); /** * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java index a658566496..ae9fb0b385 100644 --- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java +++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java @@ -36,7 +36,7 @@ import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.DisplayController; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.fallback.RecentsState; -import com.android.quickstep.util.ActivityLifecycleListener; +import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.views.RecentsView; @@ -88,12 +88,10 @@ public final class FallbackActivityInterface extends } @Override - public ActivityLifecycleListener createActivityLifecycleListener( - @Nullable Predicate onInitListener, @Nullable Runnable onDestroyListener) { - return new ActivityLifecycleListener<>( - (activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome), - onDestroyListener, - RecentsActivity.ACTIVITY_TRACKER); + public ActivityInitListener createActivityInitListener( + Predicate onInitListener) { + return new ActivityInitListener<>((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); } @Nullable diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index cb54d2e8c2..9ff941671b 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -37,7 +37,7 @@ import androidx.annotation.UiThread; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; -import com.android.launcher3.LauncherLifecycleListener; +import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; @@ -49,7 +49,7 @@ import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.NavigationMode; import com.android.quickstep.GestureState.GestureEndTarget; -import com.android.quickstep.util.ActivityLifecycleListener; +import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; @@ -136,10 +136,9 @@ public final class LauncherActivityInterface extends } @Override - public ActivityLifecycleListener createActivityLifecycleListener( - @Nullable Predicate onInitListener, @Nullable Runnable onDestroyListener) { - return new LauncherLifecycleListener((activity, alreadyOnHome) -> - onInitListener.test(alreadyOnHome), onDestroyListener); + public ActivityInitListener createActivityInitListener(Predicate onInitListener) { + return new LauncherInitListener((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome)); } @Override diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java index 0fdd8b5e36..60065fb16c 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -37,7 +37,7 @@ public class ActiveGestureErrorDetector { ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED, - FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, + FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, /** * These GestureEvents are specifically associated to state flags that get set in @@ -162,13 +162,6 @@ public class ActiveGestureErrorDetector { + "before/without setting end target to new task", writer); break; - case LAUNCHER_DESTROYED: - errorDetected |= printErrorIfTrue( - true, - prefix, - /* errorMessage= */ "Launcher destroyed mid-gesture", - writer); - break; case STATE_GESTURE_COMPLETED: errorDetected |= printErrorIfTrue( !encounteredEvents.contains(GestureEvent.MOTION_UP), diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java new file mode 100644 index 0000000000..aeec36f63c --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import com.android.launcher3.BaseActivity; +import com.android.launcher3.util.ActivityTracker; +import com.android.launcher3.util.ActivityTracker.SchedulerCallback; + +import java.util.function.BiPredicate; + +public class ActivityInitListener implements + SchedulerCallback { + + private BiPredicate mOnInitListener; + private final ActivityTracker mActivityTracker; + + private boolean mIsRegistered = false; + + /** + * @param onInitListener a callback made when the activity is initialized. The callback should + * return true to continue receiving callbacks (ie. for if the activity is + * recreated). + */ + public ActivityInitListener(BiPredicate onInitListener, + ActivityTracker tracker) { + mOnInitListener = onInitListener; + mActivityTracker = tracker; + } + + @Override + public final boolean init(T activity, boolean alreadyOnHome) { + if (!mIsRegistered) { + // Don't receive any more updates + return false; + } + return handleInit(activity, alreadyOnHome); + } + + protected boolean handleInit(T activity, boolean alreadyOnHome) { + return mOnInitListener.test(activity, alreadyOnHome); + } + + /** + * Registers the activity-created listener. If the activity is already created, then the + * callback provided in the constructor will be called synchronously. + */ + public void register() { + mIsRegistered = true; + mActivityTracker.registerCallback(this); + } + + /** + * After calling this, we won't {@link #init} even when the activity is ready. + */ + public void unregister() { + mActivityTracker.unregisterCallback(this); + mIsRegistered = false; + mOnInitListener = null; + } +} diff --git a/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java b/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java deleted file mode 100644 index 1edf1888e8..0000000000 --- a/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.android.launcher3.BaseActivity; -import com.android.launcher3.util.ActivityTracker; -import com.android.launcher3.util.ActivityTracker.SchedulerCallback; - -import java.util.function.BiPredicate; - -/** - * Listener for activity initialized and/or destroyed. - */ -public class ActivityLifecycleListener implements - SchedulerCallback { - - private static final String TAG = "ActivityLifecycleListener"; - - @Nullable private final BiPredicate mOnInitListener; - @Nullable private final Runnable mOnDestroyListener; - private final ActivityTracker mActivityTracker; - - private boolean mIsRegistered = false; - - /** - * One or both of {@code onInitListener} and {@code onInitListener} must be provided, otherwise - * the created instance will effectively be a no-op. - * - * @param onInitListener a callback made when the activity is initialized. The callback should - * return true to continue receiving callbacks (ie. for if the activity is - * recreated). - * @param onDestroyListener a callback made when the activity is destroyed. - */ - public ActivityLifecycleListener( - @Nullable BiPredicate onInitListener, - @Nullable Runnable onDestroyListener, - ActivityTracker tracker) { - if (onInitListener == null && onDestroyListener == null) { - throw new IllegalArgumentException("Both listeners cannot be null"); - } - mOnInitListener = onInitListener; - mOnDestroyListener = onDestroyListener; - mActivityTracker = tracker; - } - - @Override - public final boolean onActivityReady(T activity, boolean alreadyOnHome) { - if (!mIsRegistered) { - // Don't receive any more updates - return false; - } - return handleActivityReady(activity, alreadyOnHome); - } - - protected boolean handleActivityReady(T activity, boolean alreadyOnHome) { - if (mOnInitListener == null) { - Log.e(TAG, "Cannot handle init: init listener is null", new Exception()); - return false; - } - return mOnInitListener.test(activity, alreadyOnHome); - } - - @Override - public void onActivityDestroyed() { - if (mOnDestroyListener == null) { - Log.e(TAG, "Cannot clean up: destroy listener is null", new Exception()); - return; - } - mOnDestroyListener.run(); - } - - /** - * Registers the activity-created listener. If the activity is already created, then the - * callback provided in the constructor will be called synchronously. - */ - public void register() { - mIsRegistered = true; - mActivityTracker.registerCallback(this, getType()); - } - - /** - * After calling this, we won't call {@link #onActivityReady} even when the activity is ready. - */ - public void unregister() { - mActivityTracker.unregisterCallback(this, getType()); - mIsRegistered = false; - } - - private int getType() { - return mOnInitListener != null && mOnDestroyListener != null - ? ActivityTracker.TYPE_BOTH - : (mOnInitListener != null - ? ActivityTracker.TYPE_INIT - : ActivityTracker.TYPE_DESTROY); - } -} diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index 6d127b3998..981e3a65a4 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -74,7 +74,7 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS } @Override - public boolean onActivityReady(Launcher launcher, boolean alreadyOnHome) { + public boolean init(Launcher launcher, boolean alreadyOnHome) { AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome); launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */); launcher.getDragLayer().setOnDragListener(this); diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java index fb924c14d1..af43ae83e2 100644 --- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java @@ -72,8 +72,8 @@ public class PinItemDragListener extends BaseItemDragListener { } @Override - public boolean onActivityReady(Launcher launcher, boolean alreadyOnHome) { - super.onActivityReady(launcher, alreadyOnHome); + public boolean init(Launcher launcher, boolean alreadyOnHome) { + super.init(launcher, alreadyOnHome); if (!alreadyOnHome) { launcher.useFadeOutAnimationForLauncherStart(mCancelSignal); } diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java index 5f93a66d03..7af1a13d67 100644 --- a/src/com/android/launcher3/util/ActivityTracker.java +++ b/src/com/android/launcher3/util/ActivityTracker.java @@ -20,6 +20,8 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseActivity; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashSet; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -28,15 +30,8 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public final class ActivityTracker { - public static final int TYPE_INIT = 0; - public static final int TYPE_DESTROY = 1; - public static final int TYPE_BOTH = 2; - private WeakReference mCurrentActivity = new WeakReference<>(null); - private CopyOnWriteArrayList> mActivityReadyCallbacks = - new CopyOnWriteArrayList<>(); - private CopyOnWriteArrayList> mActivityDestroyedCallbacks = - new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList> mCallbacks = new CopyOnWriteArrayList<>(); @Nullable public R getCreatedActivity() { @@ -47,74 +42,32 @@ public final class ActivityTracker { if (mCurrentActivity.get() == activity) { mCurrentActivity.clear(); } - for (SchedulerCallback cb : mActivityDestroyedCallbacks) { - cb.onActivityDestroyed(); - unregisterCallback(cb, TYPE_DESTROY); - } } - /** Registers an activity create callback. */ + /** + * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the + * activity is ready. If the activity is already created, this is called immediately. + * + * The tracker maintains a strong ref to the callback, so it is up to the caller to return + * {@code false} in the callback OR to unregister the callback explicitly. + * + * @param callback The callback to call init() on when the activity is ready. + */ public void registerCallback(SchedulerCallback callback) { - registerCallback(callback, TYPE_INIT); - } - - /** - * Call {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} when the - * activity is ready and/or {@link SchedulerCallback#onActivityDestroyed()} when the activity - * is destroyed. - * - * If type is {@link ActivityTracker#TYPE_INIT} TYPE_INIT or - * {@link ActivityTracker#TYPE_BOTH} and the activity is already created, this - * {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} is called immediately. - * - * If type is {@link ActivityTracker#TYPE_DESTROY} or - * {@link ActivityTracker#TYPE_BOTH} and the activity is already destroyed, - * {@link SchedulerCallback#onActivityDestroyed()} is called immediately. - * - * The tracker maintains a strong ref to the callbacks, so it is up to the caller to return - * {@code false} in {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} OR to - * unregister the callback explicitly. - * - * @param callback The callback to call init() or cleanUp() on when the activity is ready or - * destroyed. - * @param type whether to use this callback on activity create, destroy or both. - */ - public void registerCallback(SchedulerCallback callback, int type) { T activity = mCurrentActivity.get(); - if (type == TYPE_INIT || type == TYPE_BOTH) { - mActivityReadyCallbacks.add(callback); - if (activity != null) { - if (!callback.onActivityReady(activity, activity.isStarted())) { - unregisterCallback(callback, TYPE_INIT); - } + mCallbacks.add(callback); + if (activity != null) { + if (!callback.init(activity, activity.isStarted())) { + unregisterCallback(callback); } } - if (type == TYPE_DESTROY || type == TYPE_BOTH) { - mActivityDestroyedCallbacks.add(callback); - if (activity == null) { - callback.onActivityDestroyed(); - unregisterCallback(callback, TYPE_DESTROY); - } - } - } - - /** - * Unregisters a registered activity create callback. - */ - public void unregisterCallback(SchedulerCallback callback) { - unregisterCallback(callback, TYPE_INIT); } /** * Unregisters a registered callback. */ - public void unregisterCallback(SchedulerCallback callback, int type) { - if (type == TYPE_INIT || type == TYPE_BOTH) { - mActivityReadyCallbacks.remove(callback); - } - if (type == TYPE_DESTROY || type == TYPE_BOTH) { - mActivityDestroyedCallbacks.remove(callback); - } + public void unregisterCallback(SchedulerCallback callback) { + mCallbacks.remove(callback); } public boolean handleCreate(T activity) { @@ -128,8 +81,8 @@ public final class ActivityTracker { private boolean handleIntent(T activity, boolean alreadyOnHome) { boolean handled = false; - for (SchedulerCallback cb : mActivityReadyCallbacks) { - if (!cb.onActivityReady(activity, alreadyOnHome)) { + for (SchedulerCallback cb : mCallbacks) { + if (!cb.init(activity, alreadyOnHome)) { // Callback doesn't want any more updates unregisterCallback(cb); } @@ -145,11 +98,6 @@ public final class ActivityTracker { * @param alreadyOnHome Whether the activity is already started. * @return Whether to continue receiving callbacks (i.e. if the activity is recreated). */ - boolean onActivityReady(T activity, boolean alreadyOnHome); - - /** - * Called then the activity gets destroyed. - */ - default void onActivityDestroyed() { } + boolean init(T activity, boolean alreadyOnHome); } } From 3e58eea9da701652a558d4116851b371f7825d4e Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 14 Nov 2022 14:30:07 -0800 Subject: [PATCH 4/7] Simiplifying the Parner override definition so that it can be used for multiple overrides Bug: 257555083 Test: Presubmit Change-Id: I14eb98edb19ccf109222d6806e27de707e485457 --- .../android/launcher3/AutoInstallsLayout.java | 22 ++- .../launcher3/DefaultLayoutParser.java | 9 +- .../launcher3/InvariantDeviceProfile.java | 26 ++- .../android/launcher3/LauncherProvider.java | 11 +- src/com/android/launcher3/Partner.java | 148 ------------------ .../launcher3/util/PackageManagerHelper.java | 23 --- src/com/android/launcher3/util/Partner.java | 116 ++++++++++++++ 7 files changed, 160 insertions(+), 195 deletions(-) delete mode 100644 src/com/android/launcher3/Partner.java create mode 100644 src/com/android/launcher3/util/Partner.java diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index efdd5e15e7..55ede6cecb 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -31,7 +31,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; -import android.util.Pair; import android.util.Patterns; import android.util.Xml; @@ -45,7 +44,7 @@ import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.Partner; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.LauncherWidgetHolder; @@ -76,19 +75,16 @@ public class AutoInstallsLayout { static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback) { - Pair customizationApkInfo = PackageManagerHelper.findSystemApk( - ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager()); - if (customizationApkInfo == null) { + Partner partner = Partner.get(context.getPackageManager(), ACTION_LAUNCHER_CUSTOMIZATION); + if (partner == null) { return null; } - String pkg = customizationApkInfo.first; - Resources targetRes = customizationApkInfo.second; InvariantDeviceProfile grid = LauncherAppState.getIDP(context); // Try with grid size and hotseat count String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT, grid.numColumns, grid.numRows, grid.numDatabaseHotseatIcons); - int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); + int layoutId = partner.getXmlResId(layoutName); // Try with only grid size if (layoutId == 0) { @@ -96,21 +92,21 @@ public class AutoInstallsLayout { + " not found. Trying layout without hosteat"); layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES, grid.numColumns, grid.numRows); - layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); + layoutId = partner.getXmlResId(layoutName); } // Try the default layout if (layoutId == 0) { Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout"); - layoutId = targetRes.getIdentifier(LAYOUT_RES, "xml", pkg); + layoutId = partner.getXmlResId(LAYOUT_RES); } if (layoutId == 0) { - Log.e(TAG, "Layout definition not found in package: " + pkg); + Log.e(TAG, "Layout definition not found in package: " + partner.getPackageName()); return null; } - return new AutoInstallsLayout(context, appWidgetHolder, callback, targetRes, layoutId, - TAG_WORKSPACE); + return new AutoInstallsLayout(context, appWidgetHolder, callback, partner.getResources(), + layoutId, TAG_WORKSPACE); } // Object Tags diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java index af13beac1d..c69ae4dac8 100644 --- a/src/com/android/launcher3/DefaultLayoutParser.java +++ b/src/com/android/launcher3/DefaultLayoutParser.java @@ -19,6 +19,7 @@ import android.util.Log; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.Partner; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.LauncherWidgetHolder; @@ -51,6 +52,9 @@ public class DefaultLayoutParser extends AutoInstallsLayout { private static final String ATTR_SHORTCUT_ID = "shortcutId"; private static final String ATTR_PACKAGE_NAME = "packageName"; + public static final String RES_PARTNER_FOLDER = "partner_folder"; + public static final String RES_PARTNER_DEFAULT_LAYOUT = "partner_default_layout"; + // TODO: Remove support for this broadcast, instead use widget options to send bind time options private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; @@ -278,10 +282,9 @@ public class DefaultLayoutParser extends AutoInstallsLayout { // Folder contents come from an external XML resource final Partner partner = Partner.get(mPackageManager); if (partner != null) { - final Resources partnerRes = partner.getResources(); - final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER, - "xml", partner.getPackageName()); + final int resId = partner.getXmlResId(RES_PARTNER_FOLDER); if (resId != 0) { + final Resources partnerRes = partner.getResources(); final XmlPullParser partnerParser = partnerRes.getXml(resId); beginDocument(partnerParser, TAG_FOLDER); diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 1f97535b4c..594d7cbd82 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -60,6 +60,7 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.Partner; import com.android.launcher3.util.Themes; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.util.window.WindowManagerProxy; @@ -112,6 +113,11 @@ public class InvariantDeviceProfile { static final int INDEX_TWO_PANEL_PORTRAIT = 2; static final int INDEX_TWO_PANEL_LANDSCAPE = 3; + /** These resources are used to override the device profile */ + private static final String RES_GRID_NUM_ROWS = "grid_num_rows"; + private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns"; + private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp"; + /** * Number of icons per row and column in the workspace. */ @@ -567,8 +573,24 @@ public class InvariantDeviceProfile { */ private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) { Partner p = Partner.get(context.getPackageManager()); - if (p != null) { - p.applyInvariantDeviceProfileOverrides(this, dm); + if (p == null) { + return; + } + try { + int numRows = p.getIntValue(RES_GRID_NUM_ROWS, -1); + int numColumns = p.getIntValue(RES_GRID_NUM_COLUMNS, -1); + float iconSizePx = p.getDimenValue(RES_GRID_ICON_SIZE_DP, -1); + + if (numRows > 0 && numColumns > 0) { + this.numRows = numRows; + this.numColumns = numColumns; + } + if (iconSizePx > 0) { + this.iconSize[InvariantDeviceProfile.INDEX_DEFAULT] = + Utilities.dpiFromPx(iconSizePx, dm.densityDpi); + } + } catch (Resources.NotFoundException ex) { + Log.e(TAG, "Invalid Partner grid resource!", ex); } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index d002c2b901..8ddbbaaf2e 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -16,6 +16,7 @@ package com.android.launcher3; +import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT; import static com.android.launcher3.provider.LauncherDbUtils.copyTable; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import static com.android.launcher3.provider.LauncherDbUtils.tableExists; @@ -34,7 +35,6 @@ import android.content.Intent; import android.content.OperationApplicationException; import android.content.SharedPreferences; import android.content.pm.ProviderInfo; -import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.SQLException; @@ -70,6 +70,7 @@ import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.NoLocaleSQLiteHelper; import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.Partner; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.LauncherWidgetHolder; @@ -544,13 +545,11 @@ public class LauncherProvider extends ContentProvider { } if (loader == null) { final Partner partner = Partner.get(getContext().getPackageManager()); - if (partner != null && partner.hasDefaultLayout()) { - final Resources partnerRes = partner.getResources(); - int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, - "xml", partner.getPackageName()); + if (partner != null) { + int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT); if (workspaceResId != 0) { loader = new DefaultLayoutParser(getContext(), widgetHolder, - mOpenHelper, partnerRes, workspaceResId); + mOpenHelper, partner.getResources(), workspaceResId); } } } diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java deleted file mode 100644 index 2e27f32226..0000000000 --- a/src/com/android/launcher3/Partner.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import static com.android.launcher3.util.PackageManagerHelper.findSystemApk; - -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.Pair; - -import java.io.File; - -/** - * Utilities to discover and interact with partner customizations. There can - * only be one set of customizations on a device, and it must be bundled with - * the system. - */ -public class Partner { - - static final String TAG = "Launcher.Partner"; - - /** Marker action used to discover partner */ - private static final String - ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION"; - - public static final String RES_FOLDER = "partner_folder"; - public static final String RES_WALLPAPERS = "partner_wallpapers"; - public static final String RES_DEFAULT_LAYOUT = "partner_default_layout"; - - public static final String RES_DEFAULT_WALLPAPER_HIDDEN = "default_wallpapper_hidden"; - public static final String RES_SYSTEM_WALLPAPER_DIR = "system_wallpaper_directory"; - - public static final String RES_REQUIRE_FIRST_RUN_FLOW = "requires_first_run_flow"; - - /** These resources are used to override the device profile */ - public static final String RES_GRID_NUM_ROWS = "grid_num_rows"; - public static final String RES_GRID_NUM_COLUMNS = "grid_num_columns"; - public static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp"; - - /** - * Find and return partner details, or {@code null} if none exists. - */ - public static synchronized Partner get(PackageManager pm) { - Pair apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm); - return apkInfo != null ? new Partner(apkInfo.first, apkInfo.second) : null; - } - - private final String mPackageName; - private final Resources mResources; - - private Partner(String packageName, Resources res) { - mPackageName = packageName; - mResources = res; - } - - public String getPackageName() { - return mPackageName; - } - - public Resources getResources() { - return mResources; - } - - public boolean hasDefaultLayout() { - int defaultLayout = getResources().getIdentifier(Partner.RES_DEFAULT_LAYOUT, - "xml", getPackageName()); - return defaultLayout != 0; - } - - public boolean hasFolder() { - int folder = getResources().getIdentifier(Partner.RES_FOLDER, - "xml", getPackageName()); - return folder != 0; - } - - public boolean hideDefaultWallpaper() { - int resId = getResources().getIdentifier(RES_DEFAULT_WALLPAPER_HIDDEN, "bool", - getPackageName()); - return resId != 0 && getResources().getBoolean(resId); - } - - public File getWallpaperDirectory() { - int resId = getResources().getIdentifier(RES_SYSTEM_WALLPAPER_DIR, "string", - getPackageName()); - return (resId != 0) ? new File(getResources().getString(resId)) : null; - } - - public boolean requiresFirstRunFlow() { - int resId = getResources().getIdentifier(RES_REQUIRE_FIRST_RUN_FLOW, "bool", - getPackageName()); - return resId != 0 && getResources().getBoolean(resId); - } - - public void applyInvariantDeviceProfileOverrides(InvariantDeviceProfile inv, DisplayMetrics dm) { - int numRows = -1; - int numColumns = -1; - float iconSize = -1; - - try { - int resId = getResources().getIdentifier(RES_GRID_NUM_ROWS, - "integer", getPackageName()); - if (resId > 0) { - numRows = getResources().getInteger(resId); - } - - resId = getResources().getIdentifier(RES_GRID_NUM_COLUMNS, - "integer", getPackageName()); - if (resId > 0) { - numColumns = getResources().getInteger(resId); - } - - resId = getResources().getIdentifier(RES_GRID_ICON_SIZE_DP, - "dimen", getPackageName()); - if (resId > 0) { - int px = getResources().getDimensionPixelSize(resId); - iconSize = Utilities.dpiFromPx((float) px, dm.densityDpi); - } - } catch (Resources.NotFoundException ex) { - Log.e(TAG, "Invalid Partner grid resource!", ex); - return; - } - - if (numRows > 0 && numColumns > 0) { - inv.numRows = numRows; - inv.numColumns = numColumns; - } - - if (iconSize > 0) { - inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT] = iconSize; - } - } -} diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 12e8b54547..140440eee2 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -16,8 +16,6 @@ package com.android.launcher3.util; -import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; - import android.app.AppOpsManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -31,7 +29,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -40,7 +37,6 @@ import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import android.widget.Toast; import androidx.annotation.NonNull; @@ -295,25 +291,6 @@ public class PackageManagerHelper { } } - /** - * Finds a system apk which had a broadcast receiver listening to a particular action. - * @param action intent action used to find the apk - * @return a pair of apk package name and the resources. - */ - public static Pair findSystemApk(String action, PackageManager pm) { - final Intent intent = new Intent(action); - for (ResolveInfo info : pm.queryBroadcastReceivers(intent, MATCH_SYSTEM_ONLY)) { - final String packageName = info.activityInfo.packageName; - try { - final Resources res = pm.getResourcesForApplication(packageName); - return Pair.create(packageName, res); - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to find resources for " + packageName); - } - } - return null; - } - /** * Returns true if the intent is a valid launch intent for a launcher activity of an app. * This is used to identify shortcuts which are different from the ones exposed by the diff --git a/src/com/android/launcher3/util/Partner.java b/src/com/android/launcher3/util/Partner.java new file mode 100644 index 0000000000..220ab566aa --- /dev/null +++ b/src/com/android/launcher3/util/Partner.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.util.Log; +import android.util.Pair; + +/** + * Utilities to discover and interact with partner customizations. There can + * only be one set of customizations on a device, and it must be bundled with + * the system. + */ +public class Partner { + + static final String TAG = "Launcher.Partner"; + + /** Marker action used to discover partner */ + private static final String + ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION"; + + /** + * Find and return partner details, or {@code null} if none exists. + */ + public static Partner get(PackageManager pm) { + return get(pm, ACTION_PARTNER_CUSTOMIZATION); + } + + /** + * Find and return partner details, or {@code null} if none exists. + */ + public static Partner get(PackageManager pm, String action) { + Pair apkInfo = findSystemApk(action, pm); + return apkInfo != null ? new Partner(apkInfo.first, apkInfo.second) : null; + } + + private final String mPackageName; + private final Resources mResources; + + private Partner(String packageName, Resources res) { + mPackageName = packageName; + mResources = res; + } + + public String getPackageName() { + return mPackageName; + } + + public Resources getResources() { + return mResources; + } + + /** + * Returns the xml resource Id for the provided name, or 0 is the resource is not found + */ + public int getXmlResId(String layoutName) { + return getResources().getIdentifier(layoutName, "xml", getPackageName()); + } + + /** + * Returns the integer resource value for the provided resource name, + * or default value if the resource name is not present + */ + public int getIntValue(String resName, int defaultValue) { + int resId = getResources().getIdentifier(resName, "integer", getPackageName()); + return resId > 0 ? getResources().getInteger(resId) : defaultValue; + } + + /** + * Returns the dimension value for the provided resource name, + * or default value if the resource name is not present + */ + public float getDimenValue(String resName, int defaultValue) { + int resId = getResources().getIdentifier(resName, "dimen", getPackageName()); + return resId > 0 ? getResources().getDimension(resId) : defaultValue; + } + + /** + * Finds a system apk which had a broadcast receiver listening to a particular action. + * @param action intent action used to find the apk + * @return a pair of apk package name and the resources. + */ + private static Pair findSystemApk(String action, PackageManager pm) { + final Intent intent = new Intent(action); + for (ResolveInfo info : pm.queryBroadcastReceivers(intent, MATCH_SYSTEM_ONLY)) { + final String packageName = info.activityInfo.packageName; + try { + final Resources res = pm.getResourcesForApplication(packageName); + return Pair.create(packageName, res); + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + packageName); + } + } + return null; + } +} From bb3146271b8eb0cf27ff83538cdce2b48a633b57 Mon Sep 17 00:00:00 2001 From: Brandon Dayauon Date: Mon, 5 Dec 2022 15:07:46 -0800 Subject: [PATCH 5/7] Change method name as suggested bug: 261098912 bug: 261099706 test: Manual - presubmit go/forest Change-Id: I2e18ddaf2b9647bccaecccbfa7faf88e51b84f12 --- tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java index bddb5935f7..80e4116406 100644 --- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java +++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java @@ -55,7 +55,7 @@ public class SearchResultFromQsb { } /** Find the web suggestion from search suggestion's title text */ - public void findWebSuggest(String text) { + public void verifyWebSuggestIsPresent(String text) { ArrayList goldenGateResults = new ArrayList<>(mLauncher.waitForObjectsInContainer( mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID), From d954229d1a0985c723c8f6265ffcbed1007264de Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 6 Dec 2022 13:33:01 -0800 Subject: [PATCH 6/7] Adding support for generating manachrome icon from a colored icon Bug: 261625158 Test: Verified on device Change-Id: Ibda922fd2c9c0a856ea02a8e73f43af8573f2450 --- .../launcher3/config/FeatureFlags.java | 5 + .../launcher3/icons/LauncherIcons.java | 17 ++ .../icons/MonochromeIconFactory.java | 180 ++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 src/com/android/launcher3/icons/MonochromeIconFactory.java diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 8363b68e0e..aa7e5d1d98 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -371,6 +371,11 @@ public final class FeatureFlags { "Enable the ability to tap a staged app during split select to launch it in full screen" ); + public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag( + "ENABLE_FORCED_MONO_ICON", false, + "Enable the ability to generate monochromatic icons, if it is not provided by the app" + ); + public static void initialize(Context context) { synchronized (sDebugFlags) { for (DebugFlag flag : sDebugFlags) { diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index 5508c49410..57fa8a256b 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -16,7 +16,10 @@ package com.android.launcher3.icons; +import static com.android.launcher3.config.FeatureFlags.ENABLE_FORCED_MONO_ICON; + import android.content.Context; +import android.graphics.drawable.Drawable; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.graphics.IconShape; @@ -68,6 +71,8 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { private LauncherIcons next; + private MonochromeIconFactory mMonochromeIconFactory; + protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) { super(context, fillResIconDpi, iconBitmapSize, IconShape.getShape().enableShapeDetection()); mMonoIconEnabled = Themes.isThemedIconEnabled(context); @@ -90,6 +95,18 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { } } + @Override + protected Drawable getMonochromeDrawable(Drawable base) { + Drawable mono = super.getMonochromeDrawable(base); + if (mono != null || !ENABLE_FORCED_MONO_ICON.get()) { + return mono; + } + if (mMonochromeIconFactory == null) { + mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize); + } + return mMonochromeIconFactory.wrap(base); + } + @Override public void close() { recycle(); diff --git a/src/com/android/launcher3/icons/MonochromeIconFactory.java b/src/com/android/launcher3/icons/MonochromeIconFactory.java new file mode 100644 index 0000000000..511dcc736e --- /dev/null +++ b/src/com/android/launcher3/icons/MonochromeIconFactory.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.icons; + +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.icons.BaseIconFactory.ClippedMonoDrawable; + +import java.nio.ByteBuffer; + +/** + * Utility class to generate monochrome icons version for a given drawable. + */ +@TargetApi(Build.VERSION_CODES.TIRAMISU) +public class MonochromeIconFactory extends Drawable { + + private final Bitmap mFlatBitmap; + private final Canvas mFlatCanvas; + private final Paint mCopyPaint; + + private final Bitmap mAlphaBitmap; + private final Canvas mAlphaCanvas; + private final byte[] mPixels; + + private final int mBitmapSize; + private final int mEdgePixelLength; + + private final Paint mDrawPaint; + private final Rect mSrcRect; + + MonochromeIconFactory(int iconBitmapSize) { + float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction(); + float viewPortScale = 1 / (1 + 2 * extraFactor); + mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale); + mPixels = new byte[mBitmapSize * mBitmapSize]; + mEdgePixelLength = mBitmapSize * (mBitmapSize - iconBitmapSize) / 2; + + mFlatBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ARGB_8888); + mFlatCanvas = new Canvas(mFlatBitmap); + + mAlphaBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ALPHA_8); + mAlphaCanvas = new Canvas(mAlphaBitmap); + + mDrawPaint = new Paint(FILTER_BITMAP_FLAG); + mDrawPaint.setColor(Color.WHITE); + mSrcRect = new Rect(0, 0, mBitmapSize, mBitmapSize); + + mCopyPaint = new Paint(FILTER_BITMAP_FLAG); + mCopyPaint.setBlendMode(BlendMode.SRC); + + // Crate a color matrix which converts the icon to grayscale and then uses the average + // of RGB components as the alpha component. + ColorMatrix satMatrix = new ColorMatrix(); + satMatrix.setSaturation(0); + float[] vals = satMatrix.getArray(); + vals[15] = vals[16] = vals[17] = .3333f; + vals[18] = vals[19] = 0; + mCopyPaint.setColorFilter(new ColorMatrixColorFilter(vals)); + } + + private void drawDrawable(Drawable drawable) { + if (drawable != null) { + drawable.setBounds(0, 0, mBitmapSize, mBitmapSize); + drawable.draw(mFlatCanvas); + } + } + + /** + * Creates a monochrome version of the provided drawable + */ + @WorkerThread + public Drawable wrap(Drawable icon) { + if (icon instanceof AdaptiveIconDrawable) { + AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon; + mFlatCanvas.drawColor(Color.BLACK); + drawDrawable(aid.getBackground()); + drawDrawable(aid.getForeground()); + generateMono(); + return new ClippedMonoDrawable(this); + } else { + mFlatCanvas.drawColor(Color.WHITE); + drawDrawable(icon); + generateMono(); + return this; + } + } + + @WorkerThread + private void generateMono() { + mAlphaCanvas.drawBitmap(mFlatBitmap, 0, 0, mCopyPaint); + + // Scale the end points: + ByteBuffer buffer = ByteBuffer.wrap(mPixels); + buffer.rewind(); + mAlphaBitmap.copyPixelsToBuffer(buffer); + + int min = 0xFF; + int max = 0; + for (byte b : mPixels) { + min = Math.min(min, b & 0xFF); + max = Math.max(max, b & 0xFF); + } + + if (min < max) { + // rescale pixels to increase contrast + float range = max - min; + + // In order to check if the colors should be flipped, we just take the average color + // of top and bottom edge which should correspond to be background color. If the edge + // colors have more opacity, we flip the colors; + int sum = 0; + for (int i = 0; i < mEdgePixelLength; i++) { + sum += (mPixels[i] & 0xFF); + sum += (mPixels[mPixels.length - 1 - i] & 0xFF); + } + float edgeAverage = sum / (mEdgePixelLength * 2f); + float edgeMapped = (edgeAverage - min) / range; + boolean flipColor = edgeMapped > .5f; + + for (int i = 0; i < mPixels.length; i++) { + int p = mPixels[i] & 0xFF; + int p2 = Math.round((p - min) * 0xFF / range); + mPixels[i] = flipColor ? (byte) (255 - p2) : (byte) (p2); + } + buffer.rewind(); + mAlphaBitmap.copyPixelsFromBuffer(buffer); + } + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mAlphaBitmap, mSrcRect, getBounds(), mDrawPaint); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int i) { + mDrawPaint.setAlpha(i); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mDrawPaint.setColorFilter(colorFilter); + } +} From 5de5ac900886be43a97ffa92c850947123328549 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 7 Dec 2022 18:09:45 +0000 Subject: [PATCH 7/7] Add IS_RUNNING_IN_TEST_HARNESS check for isTransientTaskbar This avoids us trying to read FORCE_PERSISTENT_TASKBAR feature flag which apparently crashes tests due to permission issues. Going forward, when we support persistent taskbar better, we should update the tests and this check as commented in the code. Test: com.google.android.apps.nexuslauncher.TaplTestsNexus#testSearchNoInputs Fixes: 261604544 Change-Id: I35c20825e8171766834f0f32be4f3818d02ab1df --- src/com/android/launcher3/util/DisplayController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index 9ca0dd195c..8e3daf33a4 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -131,7 +131,10 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { * Returns whether taskbar is transient. */ public static boolean isTransientTaskbar(Context context) { - if (FORCE_PERSISTENT_TASKBAR.get()) { + // TODO(b/258604917): When running in test harness, use !sTransientTaskbarStatusForTests + // once tests are updated to expect new persistent behavior such as not allowing long press + // to stash. + if (!Utilities.IS_RUNNING_IN_TEST_HARNESS && FORCE_PERSISTENT_TASKBAR.get()) { return false; } return getNavigationMode(context) == NavigationMode.NO_BUTTON