diff options
18 files changed, 272 insertions, 172 deletions
diff --git a/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt new file mode 100644 index 0000000000..29586c4afc --- /dev/null +++ b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 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.dagger + +import dagger.Module + +/** + * Module containing bindings for the final derivative app, an implementation of this module should + * be included in the final app code. + */ +@Module abstract class AppModule {} diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 1f33e08ffe..a530325f04 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -34,7 +34,6 @@ <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string> <string name="taskbar_edu_tooltip_controller_class" translatable="false">com.android.launcher3.taskbar.TaskbarEduTooltipController</string> <string name="nav_handle_long_press_handler_class" translatable="false"></string> - <string name="contextual_search_state_manager_class" translatable="false"></string> <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also determines how many thumbnails will be fetched in the background. --> diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt index 04c1d5e461..52be4132f2 100644 --- a/quickstep/src/com/android/launcher3/dagger/Modules.kt +++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt @@ -21,9 +21,11 @@ import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl import com.android.launcher3.util.ApiWrapper import com.android.launcher3.util.PluginManagerWrapper import com.android.launcher3.util.window.WindowManagerProxy +import com.android.quickstep.util.GestureExclusionManager import com.android.quickstep.util.SystemWindowManagerProxy import dagger.Binds import dagger.Module +import dagger.Provides private object Modules {} @@ -42,3 +44,11 @@ abstract class PluginManagerWrapperModule { @Binds abstract fun bindPluginManagerWrapper(impl: PluginManagerWrapperImpl): PluginManagerWrapper } + +@Module +object StaticObjectModule { + + @Provides + @JvmStatic + fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE +} diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index d4305a5469..0c89a80703 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -64,12 +64,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.launcher3.dagger.ApplicationContext; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.util.DaggerSingletonObject; +import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.NavigationMode; -import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.SettingsCache; import com.android.quickstep.TopTaskTracker.CachedTaskInfo; import com.android.quickstep.util.ActiveGestureLog; @@ -85,13 +88,14 @@ import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import java.io.PrintWriter; -import java.util.ArrayList; + +import javax.inject.Inject; /** * Manages the state of the system during a swipe up gesture. */ -public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener, - SafeCloseable { +@LauncherAppSingleton +public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener { static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; @@ -99,8 +103,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f; private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 1.414f; - public static MainThreadInitializedObject<RecentsAnimationDeviceState> INSTANCE = - new MainThreadInitializedObject<>(RecentsAnimationDeviceState::new); + public static DaggerSingletonObject<RecentsAnimationDeviceState> INSTANCE = + new DaggerSingletonObject<>(LauncherAppComponent::getRecentsAnimationDeviceState); private final Context mContext; private final DisplayController mDisplayController; @@ -110,12 +114,11 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E private final RotationTouchHelper mRotationTouchHelper; private final TaskStackChangeListener mPipListener; + private final DaggerSingletonTracker mLifeCycle; // Cache for better performance since it doesn't change at runtime. private final boolean mCanImeRenderGesturalNavButtons = InputMethodService.canImeRenderGesturalNavButtons(); - private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>(); - private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE; private NavigationMode mMode = THREE_BUTTONS; private NavBarPosition mNavBarPosition; @@ -134,35 +137,39 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION; private boolean mExclusionListenerRegistered; - private RecentsAnimationDeviceState(Context context) { - this(context, GestureExclusionManager.INSTANCE); - } - @VisibleForTesting - RecentsAnimationDeviceState(Context context, GestureExclusionManager exclusionManager) { + @Inject + RecentsAnimationDeviceState( + @ApplicationContext Context context, + GestureExclusionManager exclusionManager, + DisplayController displayController, + ContextualSearchStateManager contextualSearchStateManager, + RotationTouchHelper rotationTouchHelper, + SettingsCache settingsCache, + DaggerSingletonTracker lifeCycle) { mContext = context; - mDisplayController = DisplayController.INSTANCE.get(context); + mDisplayController = displayController; mExclusionManager = exclusionManager; - mContextualSearchStateManager = ContextualSearchStateManager.INSTANCE.get(context); + mContextualSearchStateManager = contextualSearchStateManager; + mRotationTouchHelper = rotationTouchHelper; + mLifeCycle = lifeCycle; mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false); - mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context); // Register for exclusion updates - runOnDestroy(this::unregisterExclusionListener); + mLifeCycle.addCloseable(this::unregisterExclusionListener); // Register for display changes changes mDisplayController.addChangeListener(this); onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL); - runOnDestroy(() -> mDisplayController.removeChangeListener(this)); + mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this)); - SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext); if (mIsOneHandedModeSupported) { Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED); SettingsCache.OnChangeListener onChangeListener = enabled -> mIsOneHandedModeEnabled = enabled; settingsCache.register(oneHandedUri, onChangeListener); mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri); - runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener)); + mLifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener)); } else { mIsOneHandedModeEnabled = false; } @@ -173,14 +180,16 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E enabled -> mIsSwipeToNotificationEnabled = enabled; settingsCache.register(swipeBottomNotificationUri, onChangeListener); mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri); - runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener)); + mLifeCycle.addCloseable( + () -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener)); Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE); mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0); if (!mIsUserSetupComplete) { SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e; settingsCache.register(setupCompleteUri, userSetupChangeListener); - runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener)); + mLifeCycle.addCloseable( + () -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener)); } try { @@ -201,21 +210,10 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E } }; TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener); - runOnDestroy(() -> + mLifeCycle.addCloseable(() -> TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener)); } - private void runOnDestroy(Runnable action) { - mOnDestroyActions.add(action); - } - - @Override - public void close() { - for (Runnable r : mOnDestroyActions) { - r.run(); - } - } - /** * Adds a listener for the nav mode change, guaranteed to be called after the device state's * mode has changed. @@ -228,7 +226,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E }; mDisplayController.addChangeListener(listener); callback.run(); - runOnDestroy(() -> mDisplayController.removeChangeListener(listener)); + mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(listener)); } @Override diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java index f54b6554fd..a614327e9d 100644 --- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java +++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java @@ -33,13 +33,16 @@ import android.content.res.Resources; import android.view.MotionEvent; import android.view.OrientationEventListener; +import com.android.launcher3.dagger.ApplicationContext; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.testing.shared.TestProtocol; +import com.android.launcher3.util.DaggerSingletonObject; +import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.NavigationMode; -import com.android.launcher3.util.SafeCloseable; import com.android.quickstep.util.RecentsOrientedState; import com.android.systemui.shared.Flags; import com.android.systemui.shared.system.QuickStepContract; @@ -48,16 +51,20 @@ import com.android.systemui.shared.system.TaskStackChangeListeners; import java.io.PrintWriter; +import javax.inject.Inject; + /** * Helper class for transforming touch events */ -public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable { +@LauncherAppSingleton +public class RotationTouchHelper implements DisplayInfoChangeListener { - public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE = - new MainThreadInitializedObject<>(RotationTouchHelper::new); + public static final DaggerSingletonObject<RotationTouchHelper> INSTANCE = + new DaggerSingletonObject<>(LauncherAppComponent::getRotationTouchHelper); private final OrientationTouchTransformer mOrientationTouchTransformer; private final DisplayController mDisplayController; + private final SystemUiProxy mSystemUiProxy; private final int mDisplayId; private int mDisplayRotation; @@ -127,12 +134,17 @@ public class RotationTouchHelper implements DisplayInfoChangeListener, SafeClose private boolean mTaskListFrozen; private final Context mContext; - private RotationTouchHelper(Context context) { + @Inject + RotationTouchHelper(@ApplicationContext Context context, + DisplayController displayController, + SystemUiProxy systemUiProxy, + DaggerSingletonTracker lifeCycle) { mContext = context; - mDisplayController = DisplayController.INSTANCE.get(mContext); - Resources resources = mContext.getResources(); + mDisplayController = displayController; + mSystemUiProxy = systemUiProxy; mDisplayId = DEFAULT_DISPLAY; + Resources resources = mContext.getResources(); mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode, () -> QuickStepContract.getWindowCornerRadius(mContext)); @@ -160,14 +172,13 @@ public class RotationTouchHelper implements DisplayInfoChangeListener, SafeClose } } }; - } - @Override - public void close() { - mDisplayController.removeChangeListener(this); - mOrientationListener.disable(); - TaskStackChangeListeners.getInstance() - .unregisterTaskStackListener(mFrozenTaskListener); + lifeCycle.addCloseable(() -> { + mDisplayController.removeChangeListener(this); + mOrientationListener.disable(); + TaskStackChangeListeners.getInstance() + .unregisterTaskStackListener(mFrozenTaskListener); + }); } public boolean isTaskListFrozen() { @@ -340,8 +351,7 @@ public class RotationTouchHelper implements DisplayInfoChangeListener, SafeClose } private void notifySysuiOfCurrentRotation(int rotation) { - UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext) - .notifyPrioritizedRotation(rotation)); + UI_HELPER_EXECUTOR.execute(() -> mSystemUiProxy.notifyPrioritizedRotation(rotation)); } /** diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java index 1d40d76ac6..fe25f3246a 100644 --- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java +++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java @@ -21,10 +21,13 @@ import com.android.launcher3.dagger.LauncherBaseAppComponent; import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.statehandlers.DesktopVisibilityController; import com.android.quickstep.OverviewComponentObserver; +import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.RotationTouchHelper; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.fallback.window.RecentsDisplayModel; import com.android.quickstep.util.AsyncClockEventDelegate; +import com.android.quickstep.util.ContextualSearchStateManager; /** * Launcher Quickstep base component for Dagger injection. @@ -49,4 +52,10 @@ public interface QuickstepBaseAppComponent extends LauncherBaseAppComponent { DesktopVisibilityController getDesktopVisibilityController(); TopTaskTracker getTopTaskTracker(); + + RotationTouchHelper getRotationTouchHelper(); + + ContextualSearchStateManager getContextualSearchStateManager(); + + RecentsAnimationDeviceState getRecentsAnimationDeviceState(); } diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java index f75d3b3acb..ed963999fa 100644 --- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java +++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java @@ -21,7 +21,6 @@ import static android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTE import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN; import android.app.PendingIntent; @@ -44,11 +43,13 @@ import androidx.annotation.CallSuper; import androidx.annotation.VisibleForTesting; import com.android.launcher3.R; +import com.android.launcher3.dagger.ApplicationContext; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.util.DaggerSingletonObject; +import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.EventLogArray; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; -import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.quickstep.DeviceConfigWrapper; @@ -58,12 +59,14 @@ import com.android.quickstep.TopTaskTracker; import java.io.PrintWriter; import java.util.Optional; +import javax.inject.Inject; + /** Long-lived class to manage Contextual Search states like the user setting and availability. */ -public class ContextualSearchStateManager implements ResourceBasedOverride, SafeCloseable { +@LauncherAppSingleton +public class ContextualSearchStateManager { - public static final MainThreadInitializedObject<ContextualSearchStateManager> INSTANCE = - forOverride(ContextualSearchStateManager.class, - R.string.contextual_search_state_manager_class); + public static final DaggerSingletonObject<ContextualSearchStateManager> INSTANCE = + new DaggerSingletonObject<>(LauncherAppComponent::getContextualSearchStateManager); private static final String TAG = "ContextualSearchStMgr"; private static final int MAX_DEBUG_EVENT_SIZE = 20; @@ -73,23 +76,29 @@ public class ContextualSearchStateManager implements ResourceBasedOverride, Safe private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi; private final SimpleBroadcastReceiver mContextualSearchPackageReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, (unused) -> requestUpdateProperties()); - private final SettingsCache.OnChangeListener mContextualSearchSettingChangedListener = - this::onContextualSearchSettingChanged; protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE); // Cached value whether the ContextualSearch intent filter matched any enabled components. private boolean mIsContextualSearchIntentAvailable; private boolean mIsContextualSearchSettingEnabled; - protected Context mContext; - protected String mContextualSearchPackage; - - public ContextualSearchStateManager() {} - - public ContextualSearchStateManager(Context context) { + protected final Context mContext; + protected final String mContextualSearchPackage; + protected final SystemUiProxy mSystemUiProxy; + protected final TopTaskTracker mTopTaskTracker; + + @Inject + public ContextualSearchStateManager( + @ApplicationContext Context context, + SettingsCache settingsCache, + SystemUiProxy systemUiProxy, + TopTaskTracker topTaskTracker, + DaggerSingletonTracker lifeCycle) { mContext = context; mContextualSearchPackage = mContext.getResources().getString( com.android.internal.R.string.config_defaultContextualSearchPackageName); + mSystemUiProxy = systemUiProxy; + mTopTaskTracker = topTaskTracker; if (areAllContextualSearchFlagsDisabled() || !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) { @@ -106,11 +115,20 @@ public class ContextualSearchStateManager implements ResourceBasedOverride, Safe context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED); - SettingsCache.INSTANCE.get(context).register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, - mContextualSearchSettingChangedListener); - onContextualSearchSettingChanged( - SettingsCache.INSTANCE.get(context).getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI)); - SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener); + SettingsCache.OnChangeListener settingChangedListener = + isEnabled -> mIsContextualSearchSettingEnabled = isEnabled; + settingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener); + mIsContextualSearchSettingEnabled = + settingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI); + + systemUiProxy.addOnStateChangeListener(mSysUiStateChangeListener); + + lifeCycle.addCloseable(() -> { + mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext); + unregisterSearchScreenSystemAction(); + settingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener); + systemUiProxy.removeOnStateChangeListener(mSysUiStateChangeListener); + }); } /** Return {@code true} if the Settings toggle is enabled. */ @@ -118,10 +136,6 @@ public class ContextualSearchStateManager implements ResourceBasedOverride, Safe return mIsContextualSearchSettingEnabled; } - private void onContextualSearchSettingChanged(boolean isEnabled) { - mIsContextualSearchSettingEnabled = isEnabled; - } - /** Whether search supports showing on the lockscreen. */ protected boolean supportsShowWhenLocked() { return false; @@ -208,7 +222,7 @@ public class ContextualSearchStateManager implements ResourceBasedOverride, Safe protected final void updateOverridesToSysUi() { // LPH commit haptic is always enabled - SystemUiProxy.INSTANCE.get(mContext).setOverrideHomeButtonLongPress( + mSystemUiProxy.setOverrideHomeButtonLongPress( getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true); Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";" + getLPHCustomSlopMultiplier().orElse(0f)); @@ -227,10 +241,8 @@ public class ContextualSearchStateManager implements ResourceBasedOverride, Safe new ContextualSearchInvoker(mContext).show( ENTRYPOINT_SYSTEM_ACTION); if (contextualSearchInvoked) { - String runningPackage = - TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask( - /* filterOnlyVisibleRecents */ - true).getPackageName(); + String runningPackage = mTopTaskTracker.getCachedTopTask( + /* filterOnlyVisibleRecents */ true).getPackageName(); StatsLogManager.newInstance(mContext).logger() .withPackageName(runningPackage) .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION); @@ -259,15 +271,6 @@ public class ContextualSearchStateManager implements ResourceBasedOverride, Safe } } - @Override - public void close() { - mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext); - unregisterSearchScreenSystemAction(); - SettingsCache.INSTANCE.get(mContext).unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, - mContextualSearchSettingChangedListener); - SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener); - } - protected final void addEventLog(String event) { synchronized (mEventLogArray) { mEventLogArray.addLog(event); diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt index f05b422066..af741f6831 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt @@ -86,17 +86,11 @@ class LauncherSwipeHandlerV2Test { whenever(displayManager.displays).thenReturn(arrayOf(display)) sandboxContext.initDaggerComponent( - DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy) + DaggerTestComponent.builder() + .bindSystemUiProxy(systemUiProxy) + .bindRotationHelper(mock(RotationTouchHelper::class.java)) + .bindRecentsState(mock(RecentsAnimationDeviceState::class.java)) ) - sandboxContext.putObject( - RotationTouchHelper.INSTANCE, - mock(RotationTouchHelper::class.java), - ) - sandboxContext.putObject( - RecentsAnimationDeviceState.INSTANCE, - mock(RecentsAnimationDeviceState::class.java), - ) - gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0)) underTest = @@ -117,20 +111,14 @@ class LauncherSwipeHandlerV2Test { gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER) underTest.onGestureEnded(flingSpeed, PointF()) verify(systemUiProxy) - .updateContextualEduStats( - /* isTrackpadGesture= */ eq(true), - eq(GestureType.HOME), - ) + .updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME)) } @Test fun goHomeFromAppByTouch_updateEduStats() { underTest.onGestureEnded(flingSpeed, PointF()) verify(systemUiProxy) - .updateContextualEduStats( - /* isTrackpadGesture= */ eq(false), - eq(GestureType.HOME), - ) + .updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME)) } } @@ -141,6 +129,10 @@ interface TestComponent : LauncherAppComponent { interface Builder : LauncherAppComponent.Builder { @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder + @BindsInstance fun bindRotationHelper(helper: RotationTouchHelper): Builder + + @BindsInstance fun bindRecentsState(state: RecentsAnimationDeviceState): Builder + override fun build(): TestComponent } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt index 0245908b43..b652ee8df4 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt @@ -1,9 +1,8 @@ package com.android.quickstep -import android.content.Context import androidx.test.annotation.UiThreadTest -import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest +import com.android.launcher3.dagger.LauncherComponentProvider import com.android.launcher3.util.DisplayController.CHANGE_DENSITY import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE import com.android.launcher3.util.DisplayController.CHANGE_ROTATION @@ -12,6 +11,7 @@ import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.NavigationMode +import com.android.launcher3.util.SandboxApplication import com.android.quickstep.util.GestureExclusionManager import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING @@ -27,6 +27,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -43,21 +44,32 @@ import org.mockito.kotlin.whenever @RunWith(LauncherMultivalentJUnit::class) class RecentsAnimationDeviceStateTest { + @get:Rule val context = SandboxApplication() + @Mock private lateinit var exclusionManager: GestureExclusionManager @Mock private lateinit var info: Info - private val context = ApplicationProvider.getApplicationContext() as Context private lateinit var underTest: RecentsAnimationDeviceState @Before fun setup() { MockitoAnnotations.initMocks(this) - underTest = RecentsAnimationDeviceState(context, exclusionManager) + + val component = LauncherComponentProvider.get(context) + underTest = + RecentsAnimationDeviceState( + context, + exclusionManager, + component.displayController, + component.contextualSearchStateManager, + component.rotationTouchHelper, + component.settingsCache, + component.daggerSingletonTracker, + ) } @After fun tearDown() { - underTest.close() UI_HELPER_EXECUTOR.submit {}.get() MAIN_EXECUTOR.submit {}.get() } diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java index 3c5e1e8345..e2ca91a9c4 100644 --- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java +++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java @@ -16,8 +16,6 @@ package com.android.quickstep; -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; - import static com.android.quickstep.InputConsumerUtils.newBaseConsumer; import static com.android.quickstep.InputConsumerUtils.newConsumer; @@ -40,6 +38,9 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatedFloat; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppModule; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.taskbar.bubbles.BubbleBarController; @@ -54,7 +55,7 @@ import com.android.launcher3.taskbar.bubbles.BubblePinController; import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController; import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; import com.android.launcher3.util.LockedUserState; -import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.BubbleBarInputConsumer; @@ -75,6 +76,9 @@ import com.android.quickstep.views.RecentsViewContainer; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; +import dagger.BindsInstance; +import dagger.Component; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -93,8 +97,8 @@ import javax.inject.Provider; @RunWith(AndroidJUnit4.class) public class InputConsumerUtilsTest { - @NonNull private final MainThreadInitializedObject.SandboxContext mContext = - new MainThreadInitializedObject.SandboxContext(getApplicationContext()); + @Rule public final SandboxApplication mContext = new SandboxApplication(); + @NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0); private TaskAnimationManager mTaskAnimationManager; @@ -125,10 +129,12 @@ public class InputConsumerUtilsTest { } @Before - public void setupMainThreadInitializedObjects() { - mContext.putObject(LockedUserState.INSTANCE, mLockedUserState); - mContext.putObject(RotationTouchHelper.INSTANCE, mock(RotationTouchHelper.class)); - mContext.putObject(RecentsAnimationDeviceState.INSTANCE, mDeviceState); + public void setupDaggerGraphOverrides() { + mContext.initDaggerComponent(DaggerInputConsumerUtilsTest_TestComponent + .builder() + .bindLockedState(mLockedUserState) + .bindRotationHelper(mock(RotationTouchHelper.class)) + .bindRecentsState(mDeviceState)); } @Before @@ -595,4 +601,18 @@ public class InputConsumerUtilsTest { return bubbleControllers; } + + @LauncherAppSingleton + @Component(modules = {LauncherAppModule.class}) + interface TestComponent extends LauncherAppComponent { + @Component.Builder + interface Builder extends LauncherAppComponent.Builder { + @BindsInstance Builder bindLockedState(LockedUserState state); + @BindsInstance Builder bindRotationHelper(RotationTouchHelper helper); + @BindsInstance Builder bindRecentsState(RecentsAnimationDeviceState state); + + @Override + TestComponent build(); + } + } } diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java index ef136d07b9..c58a414304 100644 --- a/src/com/android/launcher3/dagger/LauncherAppModule.java +++ b/src/com/android/launcher3/dagger/LauncherAppModule.java @@ -21,7 +21,9 @@ import dagger.Module; @Module(includes = { WindowManagerProxyModule.class, ApiWrapperModule.class, - PluginManagerWrapperModule.class + PluginManagerWrapperModule.class, + StaticObjectModule.class, + AppModule.class }) public class LauncherAppModule { } diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java index c3e3992cd0..0b7b20f92f 100644 --- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java +++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java @@ -27,6 +27,7 @@ import com.android.launcher3.util.ApiWrapper; import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DynamicResource; +import com.android.launcher3.util.LockedUserState; import com.android.launcher3.util.MSDLPlayerWrapper; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PluginManagerWrapper; @@ -68,6 +69,7 @@ public interface LauncherBaseAppComponent { ThemeManager getThemeManager(); DisplayController getDisplayController(); WallpaperColorHints getWallpaperColorHints(); + LockedUserState getLockedUserState(); /** Builder for LauncherBaseAppComponent. */ interface Builder { diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt index c8d86d4244..a6a6cebb5d 100644 --- a/src/com/android/launcher3/util/LockedUserState.kt +++ b/src/com/android/launcher3/util/LockedUserState.kt @@ -20,10 +20,17 @@ import android.content.Intent import android.os.Process import android.os.UserManager import androidx.annotation.VisibleForTesting +import com.android.launcher3.dagger.ApplicationContext +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR +import javax.inject.Inject -class LockedUserState(private val mContext: Context) : SafeCloseable { +@LauncherAppSingleton +class LockedUserState +@Inject +constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerSingletonTracker) { val isUserUnlockedAtLauncherStartup: Boolean var isUserUnlocked = false private set(value) { @@ -36,7 +43,7 @@ class LockedUserState(private val mContext: Context) : SafeCloseable { private val mUserUnlockedActions: RunnableList = RunnableList() @VisibleForTesting - val mUserUnlockedReceiver = + val userUnlockedReceiver = SimpleBroadcastReceiver(UI_HELPER_EXECUTOR) { if (Intent.ACTION_USER_UNLOCKED == it.action) { isUserUnlocked = true @@ -53,8 +60,8 @@ class LockedUserState(private val mContext: Context) : SafeCloseable { isUserUnlocked = checkIsUserUnlocked() isUserUnlockedAtLauncherStartup = isUserUnlocked if (!isUserUnlocked) { - mUserUnlockedReceiver.register( - mContext, + userUnlockedReceiver.register( + context, { // If user is unlocked while registering broadcast receiver, we should update // [isUserUnlocked], which will call [notifyUserUnlocked] in setter @@ -62,22 +69,18 @@ class LockedUserState(private val mContext: Context) : SafeCloseable { MAIN_EXECUTOR.execute { isUserUnlocked = true } } }, - Intent.ACTION_USER_UNLOCKED + Intent.ACTION_USER_UNLOCKED, ) } + lifeCycle.addCloseable { userUnlockedReceiver.unregisterReceiverSafely(context) } } private fun checkIsUserUnlocked() = - mContext.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle()) + context.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle()) private fun notifyUserUnlocked() { mUserUnlockedActions.executeAllAndDestroy() - mUserUnlockedReceiver.unregisterReceiverSafely(mContext) - } - - /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */ - override fun close() { - mUserUnlockedReceiver.unregisterReceiverSafely(mContext) + userUnlockedReceiver.unregisterReceiverSafely(context) } /** @@ -88,9 +91,7 @@ class LockedUserState(private val mContext: Context) : SafeCloseable { mUserUnlockedActions.add(action) } - /** - * Removes a previously queued `Runnable` to be run when the user is unlocked. - */ + /** Removes a previously queued `Runnable` to be run when the user is unlocked. */ fun removeOnUserUnlockedRunnable(action: Runnable) { mUserUnlockedActions.remove(action) } @@ -98,7 +99,7 @@ class LockedUserState(private val mContext: Context) : SafeCloseable { companion object { @VisibleForTesting @JvmField - val INSTANCE = MainThreadInitializedObject { LockedUserState(it) } + val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLockedUserState) @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context) } diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java index 8fe6e93268..fa183c8d6a 100644 --- a/src/com/android/launcher3/util/SettingsCache.java +++ b/src/com/android/launcher3/util/SettingsCache.java @@ -34,11 +34,11 @@ import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.dagger.LauncherBaseAppComponent; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; import javax.inject.Inject; @@ -57,7 +57,7 @@ import javax.inject.Inject; * Cache will also be updated if a key queried is missing (even if it has no listeners registered). */ @LauncherAppSingleton -public class SettingsCache extends ContentObserver implements SafeCloseable { +public class SettingsCache extends ContentObserver { /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ public static final Uri NOTIFICATION_BADGING_URI = @@ -79,11 +79,17 @@ public class SettingsCache extends ContentObserver implements SafeCloseable { private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString(); private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString(); + private final Function<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMapper = uri -> { + registerUriAsync(uri); + return new CopyOnWriteArrayList<>(); + }; + /** * Caches the last seen value for registered keys. */ - private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>(); - private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>(); + private final Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>(); + private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = + new ConcurrentHashMap<>(); protected final ContentResolver mResolver; /** @@ -96,12 +102,8 @@ public class SettingsCache extends ContentObserver implements SafeCloseable { SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) { super(new Handler(Looper.getMainLooper())); mResolver = context.getContentResolver(); - tracker.addCloseable(this); - } - - @Override - public void close() { - UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this)); + tracker.addCloseable(() -> + UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this))); } @Override @@ -109,11 +111,12 @@ public class SettingsCache extends ContentObserver implements SafeCloseable { // We use default of 1, but if we're getting an onChange call, can assume a non-default // value will exist boolean newVal = updateValue(uri, 1 /* Effectively Unused */); - if (!mListenerMap.containsKey(uri)) { + List<OnChangeListener> listeners = mListenerMap.get(uri); + if (listeners == null) { return; } - for (OnChangeListener listener : mListenerMap.get(uri)) { + for (OnChangeListener listener : listeners) { listener.onSettingsChanged(newVal); } } @@ -138,22 +141,17 @@ public class SettingsCache extends ContentObserver implements SafeCloseable { } } + private void registerUriAsync(Uri uri) { + UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this)); + } + /** * Does not de-dupe if you add same listeners for the same key multiple times. * Unregister once complete using {@link #unregister(Uri, OnChangeListener)} */ @UiThread public void register(Uri uri, OnChangeListener changeListener) { - Preconditions.assertUIThread(); - if (mListenerMap.containsKey(uri)) { - mListenerMap.get(uri).add(changeListener); - } else { - CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>(); - l.add(changeListener); - mListenerMap.put(uri, l); - UI_HELPER_EXECUTOR.execute( - () -> mResolver.registerContentObserver(uri, false, this)); - } + mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener); } private boolean updateValue(Uri keyUri, int defaultValue) { diff --git a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt index dab33a04ee..c3bf7c532b 100644 --- a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt +++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt @@ -25,3 +25,8 @@ private object Modules {} @Module abstract class ApiWrapperModule {} @Module abstract class PluginManagerWrapperModule {} + +@Module object StaticObjectModule {} + +// Module containing bindings for the final derivative app +@Module abstract class AppModule {} diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt index 68da9ff4d6..b66a9d3330 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt @@ -19,6 +19,8 @@ package com.android.launcher3.util import com.android.launcher3.FakeLauncherPrefs import com.android.launcher3.LauncherPrefs import com.android.launcher3.dagger.ApiWrapperModule +import com.android.launcher3.dagger.AppModule +import com.android.launcher3.dagger.StaticObjectModule import com.android.launcher3.dagger.WindowManagerProxyModule import dagger.Binds import dagger.Module @@ -31,11 +33,21 @@ abstract class FakePrefsModule { } /** All modules. We also exclude the plugin module from tests */ -@Module(includes = [ApiWrapperModule::class, WindowManagerProxyModule::class]) +@Module( + includes = + [ + ApiWrapperModule::class, + WindowManagerProxyModule::class, + StaticObjectModule::class, + AppModule::class, + ] +) class AllModulesForTest /** All modules except the WMProxy */ -@Module(includes = [ApiWrapperModule::class]) class AllModulesMinusWMProxy +@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class]) +class AllModulesMinusWMProxy /** All modules except the ApiWrapper */ -@Module(includes = [WindowManagerProxyModule::class]) class AllModulesMinusApiWrapper +@Module(includes = [WindowManagerProxyModule::class, StaticObjectModule::class, AppModule::class]) +class AllModulesMinusApiWrapper diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt index 2711d7a66d..74aeadb359 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt @@ -38,6 +38,7 @@ class LockedUserStateTest { private val userManager: UserManager = mock() private val context: Context = mock() + private val lifeCycle: DaggerSingletonTracker = mock() @Before fun setup() { @@ -48,7 +49,7 @@ class LockedUserStateTest { fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) val action: Runnable = mock() - LockedUserState(context).runOnUserUnlocked(action) + LockedUserState(context, lifeCycle).runOnUserUnlocked(action) verify(action).run() } @@ -56,23 +57,23 @@ class LockedUserStateTest { fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) val action: Runnable = mock() - val state = LockedUserState(context) + val state = LockedUserState(context, lifeCycle) state.runOnUserUnlocked(action) // b/343530737 verifyNoMoreInteractions(action) - state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED)) + state.userUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED)) verify(action).run() } @Test fun isUserUnlocked_returns_true_when_user_is_unlocked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) - assertThat(LockedUserState(context).isUserUnlocked).isTrue() + assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isTrue() } @Test fun isUserUnlocked_returns_false_when_user_is_locked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) - assertThat(LockedUserState(context).isUserUnlocked).isFalse() + assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isFalse() } } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt index efe763739c..0da8891e47 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt @@ -48,6 +48,7 @@ import org.junit.runners.model.Statement class SandboxApplication private constructor(private val base: SandboxApplicationWrapper) : SandboxModelContext(base), TestRule { + @JvmOverloads constructor( base: Context = ApplicationProvider.getApplicationContext() ) : this(SandboxApplicationWrapper(base)) |