From e79d453bc8e56257d55b3d2024b6bcaa689e707a Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 31 Dec 2024 00:10:20 -0800 Subject: [PATCH] Converting LauncherPrefs to dagger Fixing ENABLE_TWOLINE_ALLAPPS_TOGGLE not properly tied to IDP: http://recall/-/ep7WJ8pKwCEklUN5J1mAkM Bug: 361850561 Flag: EXEMPT dagger-migration Test: atest LauncherPrefsTest FakeLauncherPrefsTest Change-Id: Iba63d060f4a8c2e31033fca2a4638c559c161338 --- .../appprediction/PredictionRowView.java | 4 +- .../rules/TaskbarWindowSandboxContext.kt | 6 +- src/com/android/launcher3/BubbleTextView.java | 13 +- src/com/android/launcher3/DeviceProfile.java | 14 +- .../launcher3/InvariantDeviceProfile.java | 70 +-- src/com/android/launcher3/Launcher.java | 11 +- .../android/launcher3/LauncherAppState.java | 18 +- src/com/android/launcher3/LauncherModel.kt | 1 - src/com/android/launcher3/LauncherPrefs.kt | 407 ++++++++---------- .../launcher3/allapps/BaseAllAppsAdapter.java | 6 +- .../dagger/LauncherBaseAppComponent.java | 2 + .../launcher3/AbstractDeviceProfileTest.kt | 9 +- .../android/launcher3/FakeLauncherPrefs.kt | 69 +-- .../launcher3/FakeLauncherPrefsTest.kt | 2 +- .../launcher3/ui/BubbleTextViewTest.java | 53 +-- .../launcher3/util/DisplayControllerTest.kt | 9 +- 16 files changed, 287 insertions(+), 407 deletions(-) diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java index 22e491ffec..b329156cf3 100644 --- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -35,7 +35,6 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Flags; -import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.FloatingHeaderRow; @@ -150,8 +149,7 @@ public class PredictionRowView int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2; // Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line // is not enabled. Otherwise, the extra height will increase by just the textHeight. - int extraHeight = (Flags.enableTwolineToggle() && - LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext())) + int extraHeight = deviceProfile.inv.enableTwoLinesInAllApps ? (textHeight + mTopRowExtraHeight) : mTopRowExtraHeight; totalHeight += extraHeight; return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom(); diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt index 0b94dfd92e..31c5a4c950 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt @@ -23,7 +23,6 @@ import android.hardware.display.VirtualDisplay import android.view.Display.DEFAULT_DISPLAY import androidx.test.core.app.ApplicationProvider import com.android.launcher3.FakeLauncherPrefs -import com.android.launcher3.LauncherPrefs import com.android.launcher3.dagger.LauncherAppComponent import com.android.launcher3.dagger.LauncherAppModule import com.android.launcher3.dagger.LauncherAppSingleton @@ -74,8 +73,6 @@ private constructor( .bindSettingsCache(settingsCacheSandbox.cache) componentBinder?.invoke(context, builder) base.initDaggerComponent(builder) - - putObject(LauncherPrefs.INSTANCE, FakeLauncherPrefs(context)) } } @@ -119,6 +116,9 @@ private constructor( @LauncherAppSingleton @Component(modules = [LauncherAppModule::class]) interface TaskbarSandboxComponent : LauncherAppComponent { + + override fun getLauncherPrefs(): FakeLauncherPrefs + @Component.Builder interface Builder : LauncherAppComponent.Builder { @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index c78666ec6f..61297ce089 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -161,7 +161,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, }; private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); - private final ActivityContext mActivity; + protected final ActivityContext mActivity; private FastBitmapDrawable mIcon; private DeviceProfile mDeviceProfile; private boolean mCenterVertically; @@ -190,7 +190,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @ViewDebug.ExportedProperty(category = "launcher") private DotInfo mDotInfo; private DotRenderer mDotRenderer; - private String mCurrentLanguage; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) protected DotRenderer.DrawParams mDotParams; private Animator mDotScaleAnim; @@ -302,7 +301,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mDotParams = new DotRenderer.DrawParams(); - mCurrentLanguage = context.getResources().getConfiguration().locale.getLanguage(); setEllipsize(TruncateAt.END); setAccessibilityDelegate(mActivity.getAccessibilityDelegate()); setTextAlpha(1f); @@ -492,13 +490,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * Only if actual text can be displayed in two line, the {@code true} value will be effective. */ protected boolean shouldUseTwoLine() { - return isCurrentLanguageEnglish() && (mDisplay == DISPLAY_ALL_APPS - || mDisplay == DISPLAY_PREDICTION_ROW) && (Flags.enableTwolineToggle() - && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext())); - } - - protected boolean isCurrentLanguageEnglish() { - return mCurrentLanguage.equals(Locale.ENGLISH.getLanguage()); + return mDeviceProfile.inv.enableTwoLinesInAllApps + && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW); } @UiThread diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 5387815cd7..9ebae9f6d9 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -24,7 +24,6 @@ import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE; import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE; import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT; import static com.android.launcher3.Utilities.dpiFromPx; -import static com.android.launcher3.Utilities.isEnglishLanguage; import static com.android.launcher3.Utilities.pxFromSp; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR; import static com.android.launcher3.icons.GraphicsUtils.getShapePath; @@ -1362,16 +1361,9 @@ public class DeviceProfile { if (isVerticalLayout && !mIsResponsiveGrid) { hideWorkspaceLabelsIfNotEnoughSpace(); } - if ((Flags.enableTwolineToggle() - && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context))) { - if (!isEnglishLanguage(context)) { - // Set toggle preference value to false if not english here as it's possible the - // preference is stale after language change. - LauncherPrefs.get(context).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, false); - } else { - // Add extra textHeight to the existing allAppsCellHeight. - allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx); - } + if (inv.enableTwoLinesInAllApps) { + // Add extra textHeight to the existing allAppsCellHeight. + allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx); } updateHotseatSizes(iconSizePx); diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 28293d13b5..5cca990b3e 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.LauncherPrefs.DB_FILE; +import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE; import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE; import static com.android.launcher3.LauncherPrefs.GRID_NAME; import static com.android.launcher3.Utilities.dpiFromPx; @@ -30,6 +31,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.annotation.TargetApi; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -57,14 +59,15 @@ import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.DeviceGridState; import com.android.launcher3.provider.RestoreDbTask; -import com.android.launcher3.settings.SettingsActivity; import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Partner; import com.android.launcher3.util.ResourceHelper; +import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SafeCloseable; +import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.util.window.CachedDisplayInfo; import com.android.launcher3.util.window.WindowManagerProxy; @@ -123,6 +126,8 @@ public class InvariantDeviceProfile implements SafeCloseable { private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns"; private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp"; + private final RunnableList mCloseActions = new RunnableList(); + /** * Number of icons per row and column in the workspace. */ @@ -218,12 +223,12 @@ public class InvariantDeviceProfile implements SafeCloseable { @XmlRes public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - + private String mLocale = ""; + public boolean enableTwoLinesInAllApps = false; /** * Fixed landscape mode is the landscape on the phones. */ public boolean isFixedLandscape = false; - private LauncherPrefChangeListener mLandscapeModePreferenceListener; public String dbFile; public int defaultLayoutId; @@ -247,7 +252,9 @@ public class InvariantDeviceProfile implements SafeCloseable { private InvariantDeviceProfile(Context context) { String gridName = getCurrentGridName(context); initGrid(context, gridName); - DisplayController.INSTANCE.get(context).setPriorityListener( + + DisplayController dc = DisplayController.INSTANCE.get(context); + dc.setPriorityListener( (displayContext, info, flags) -> { if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING @@ -255,25 +262,28 @@ public class InvariantDeviceProfile implements SafeCloseable { onConfigChanged(displayContext); } }); - if (Flags.oneGridSpecs()) { - mLandscapeModePreferenceListener = (String preference_name) -> { - // Here we need both conditions even though they might seem redundant but because - // the update happens in the executable there can be race conditions and this avoids - // it. - if (isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context) - && SettingsActivity.FIXED_LANDSCAPE_MODE.equals(preference_name)) { - MAIN_EXECUTOR.execute(() -> { - Trace.beginSection("InvariantDeviceProfile#setFixedLandscape"); - onConfigChanged(context.getApplicationContext()); - Trace.endSection(); - }); - } - }; - LauncherPrefs.INSTANCE.get(context).addListener( - mLandscapeModePreferenceListener, - FIXED_LANDSCAPE_MODE - ); - } + mCloseActions.add(() -> dc.setPriorityListener(null)); + + LauncherPrefChangeListener prefListener = key -> { + if (FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(key) + && isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)) { + Trace.beginSection("InvariantDeviceProfile#setFixedLandscape"); + onConfigChanged(context); + Trace.endSection(); + } else if (ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key) + && enableTwoLinesInAllApps != ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context)) { + onConfigChanged(context); + } + }; + LauncherPrefs prefs = LauncherPrefs.INSTANCE.get(context); + prefs.addListener(prefListener, FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE); + mCloseActions.add(() -> prefs.removeListener(prefListener, + FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE)); + + SimpleBroadcastReceiver localeReceiver = new SimpleBroadcastReceiver( + MAIN_EXECUTOR, i -> onConfigChanged(context)); + localeReceiver.register(context, Intent.ACTION_LOCALE_CHANGED); + mCloseActions.add(() -> localeReceiver.unregisterReceiverSafely(context)); } /** @@ -341,12 +351,7 @@ public class InvariantDeviceProfile implements SafeCloseable { @Override public void close() { - DisplayController.INSTANCE.executeIfCreated(dc -> dc.setPriorityListener(null)); - if (mLandscapeModePreferenceListener != null) { - LauncherPrefs.INSTANCE.executeIfCreated( - lp -> lp.removeListener(mLandscapeModePreferenceListener, FIXED_LANDSCAPE_MODE) - ); - } + mCloseActions.executeAllAndDestroy(); } public static String getCurrentGridName(Context context) { @@ -413,6 +418,11 @@ public class InvariantDeviceProfile implements SafeCloseable { } private void initGrid(Context context, Info displayInfo, DisplayOption displayOption) { + enableTwoLinesInAllApps = Flags.enableTwolineToggle() + && Utilities.isEnglishLanguage(context) + && ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context); + mLocale = context.getResources().getConfiguration().locale.toString(); + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); GridOption closestProfile = displayOption.grid; numRows = closestProfile.numRows; @@ -564,7 +574,7 @@ public class InvariantDeviceProfile implements SafeCloseable { private Object[] toModelState() { return new Object[]{ numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons, - iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile}; + iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile, mLocale}; } /** Updates IDP using the provided context. Notifies listeners of change. */ diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b38efc26b4..3fbf888273 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -417,7 +417,6 @@ public class Launcher extends StatefulActivity private final List mBackPressedHandlers = new ArrayList<>(); private boolean mIsColdStartupAfterReboot; - private boolean mForceConfigUpdate; private boolean mIsNaturalScrollingEnabled; @@ -763,7 +762,7 @@ public class Launcher extends StatefulActivity protected void onHandleConfigurationChanged() { Trace.beginSection("Launcher#onHandleconfigurationChanged"); try { - if (!initDeviceProfile(mDeviceProfile.inv) && !mForceConfigUpdate) { + if (!initDeviceProfile(mDeviceProfile.inv)) { return; } dispatchDeviceProfileChanged(); @@ -776,7 +775,6 @@ public class Launcher extends StatefulActivity mModel.rebindCallbacks(); updateDisallowBack(); } finally { - mForceConfigUpdate = false; Trace.endSection(); } } @@ -3181,13 +3179,6 @@ public class Launcher extends StatefulActivity return mAnimationCoordinator; } - /** - * Set to force config update when set to true next time onHandleConfigurationChanged is called. - */ - public void setForceConfigUpdate(boolean forceConfigUpdate) { - mForceConfigUpdate = forceConfigUpdate; - } - @Override public View.OnLongClickListener getAllAppsItemLongClickListener() { return ItemLongClickListener.INSTANCE_ALL_APPS; diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index a53238da3a..5989e4c6a2 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -34,8 +34,6 @@ import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_L import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.LauncherApps; @@ -70,9 +68,6 @@ import com.android.launcher3.util.Themes; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.widget.custom.CustomWidgetManager; -import java.util.Locale; -import java.util.Objects; - public class LauncherAppState implements SafeCloseable { public static final String TAG = "LauncherAppState"; @@ -128,22 +123,11 @@ public class LauncherAppState implements SafeCloseable { SimpleBroadcastReceiver modelChangeReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, mModel::onBroadcastIntent); - final Locale oldLocale = mContext.getResources().getConfiguration().locale; modelChangeReceiver.register( mContext, - () -> { - // if local has changed before receiver is registered on bg thread, - // mModel needs to reload. - Locale newLocale = mContext.getResources().getConfiguration().locale; - if (!Objects.equals(oldLocale, newLocale)) { - mModel.forceReload(); - } - }, - Intent.ACTION_LOCALE_CHANGED, ACTION_DEVICE_POLICY_RESOURCE_UPDATED); if (BuildConfig.IS_STUDIO_BUILD) { - mContext.registerReceiver(modelChangeReceiver, new IntentFilter(ACTION_FORCE_ROLOAD), - RECEIVER_EXPORTED); + modelChangeReceiver.register(mContext, RECEIVER_EXPORTED, ACTION_FORCE_ROLOAD); } mOnTerminateCallback.add(() -> modelChangeReceiver.unregisterReceiverSafely(mContext)); diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt index b56df4624c..185629b30e 100644 --- a/src/com/android/launcher3/LauncherModel.kt +++ b/src/com/android/launcher3/LauncherModel.kt @@ -176,7 +176,6 @@ class LauncherModel( fun onBroadcastIntent(intent: Intent) { if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=$intent") when (intent.action) { - Intent.ACTION_LOCALE_CHANGED, LauncherAppState.ACTION_FORCE_ROLOAD -> // If we have changed locale we need to clear out the labels in all apps/workspace. forceReload() diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt index 5b9c2fa3a4..ad592d8dea 100644 --- a/src/com/android/launcher3/LauncherPrefs.kt +++ b/src/com/android/launcher3/LauncherPrefs.kt @@ -23,59 +23,212 @@ import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY +import com.android.launcher3.dagger.ApplicationContext +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.model.DeviceGridState import com.android.launcher3.pm.InstallSessionHelper import com.android.launcher3.provider.RestoreDbTask import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY import com.android.launcher3.settings.SettingsActivity import com.android.launcher3.states.RotationHelper +import com.android.launcher3.util.DaggerSingletonObject import com.android.launcher3.util.DisplayController -import com.android.launcher3.util.MainThreadInitializedObject -import com.android.launcher3.util.SafeCloseable import com.android.launcher3.util.Themes +import javax.inject.Inject /** * Manages Launcher [SharedPreferences] through [Item] instances. * * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. */ -abstract class LauncherPrefs : SafeCloseable { +@LauncherAppSingleton +open class LauncherPrefs +@Inject +constructor(@ApplicationContext private val encryptedContext: Context) { + + private val deviceProtectedSharedPrefs: SharedPreferences by lazy { + encryptedContext + .createDeviceProtectedStorageContext() + .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE) + } + + open val Item.sharedPrefs: SharedPreferences + get() = + if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs + else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) /** Returns the value with type [T] for [item]. */ - abstract fun get(item: ContextualItem): T + fun get(item: ContextualItem): T = + getInner(item, item.defaultValueFromContext(encryptedContext)) /** Returns the value with type [T] for [item]. */ - abstract fun get(item: ConstantItem): T + fun get(item: ConstantItem): T = getInner(item, item.defaultValue) - /** Stores the values for each item in preferences. */ - abstract fun put(vararg itemsToValues: Pair) + /** + * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the + * default value type, and will throw an error if the type of the item provided is not a + * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set`. + */ + @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") + private fun getInner(item: Item, default: T): T { + val sp = item.sharedPrefs - /** Stores the [value] with type [T] for [item] in preferences. */ - abstract fun put(item: Item, value: T) + return when (item.type) { + String::class.java -> sp.getString(item.sharedPrefKey, default as? String) + Boolean::class.java, + java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean) + Int::class.java, + java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int) + Float::class.java, + java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float) + Long::class.java, + java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long) + Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set) + else -> + throw IllegalArgumentException( + "item type: ${item.type}" + " is not compatible with sharedPref methods" + ) + } + as T + } - /** Synchronous version of [put]. */ - abstract fun putSync(vararg itemsToValues: Pair) + /** + * Stores each of the values provided in `SharedPreferences` according to the configuration + * contained within the associated items provided. Internally, it uses apply, so the caller + * cannot assume that the values that have been put are immediately available for use. + * + * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from + * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the + * provided item configurations. + */ + fun put(vararg itemsToValues: Pair): Unit = + prepareToPutValues(itemsToValues).forEach { it.apply() } - /** Registers [listener] for [items]. */ - abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) + /** See referenced `put` method above. */ + fun put(item: Item, value: T): Unit = put(item.to(value)) - /** Unregisters [listener] for [items]. */ - abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) + /** + * Synchronously stores all the values provided according to their associated Item + * configuration. + */ + fun putSync(vararg itemsToValues: Pair): Unit = + prepareToPutValues(itemsToValues).forEach { it.commit() } - /** Returns `true` iff all [items] have a value. */ - abstract fun has(vararg items: Item): Boolean + /** + * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If + * the item is boot aware, this method updates both the boot aware and the encrypted files. This + * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs + * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which + * already points to encrypted storage. + * + * Returns a list of editors with all transactions added so that the caller can determine to use + * .apply() or .commit() + */ + private fun prepareToPutValues( + updates: Array> + ): List { + val updatesPerPrefFile = updates.groupBy { it.first.sharedPrefs }.toMap() - /** Removes the value for each item in [items]. */ - abstract fun remove(vararg items: Item) + return updatesPerPrefFile.map { (sharedPref, itemList) -> + sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } } + } + } - /** Synchronous version of [remove]. */ - abstract fun removeSync(vararg items: Item) + /** + * Handles adding values to `SharedPreferences` regardless of type. This method is especially + * helpful for updating `SharedPreferences` values for `List<Any>` that have multiple + * types of Item values. + */ + @Suppress("UNCHECKED_CAST") + private fun SharedPreferences.Editor.putValue( + item: Item, + value: Any?, + ): SharedPreferences.Editor = + when (item.type) { + String::class.java -> putString(item.sharedPrefKey, value as? String) + Boolean::class.java, + java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean) + Int::class.java, + java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int) + Float::class.java, + java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float) + Long::class.java, + java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long) + Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set) + else -> + throw IllegalArgumentException( + "item type: ${item.type} is not compatible with sharedPref methods" + ) + } + + /** + * After calling this method, the listener will be notified of any future updates to the + * `SharedPreferences` files associated with the provided list of items. The listener will need + * to filter update notifications so they don't activate for non-relevant updates. + */ + fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) { + items + .map { it.sharedPrefs } + .distinct() + .forEach { it.registerOnSharedPreferenceChangeListener(listener) } + } + + /** + * Stops the listener from getting notified of any more updates to any of the + * `SharedPreferences` files associated with any of the provided list of [Item]. + */ + fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) { + // If a listener is not registered to a SharedPreference, unregistering it does nothing + items + .map { it.sharedPrefs } + .distinct() + .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) } + } + + /** + * Checks if all the provided [Item] have values stored in their corresponding + * `SharedPreferences` files. + */ + fun has(vararg items: Item): Boolean { + items + .groupBy { it.sharedPrefs } + .forEach { (prefs, itemsSublist) -> + if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false + } + return true + } + + /** + * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. + */ + fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() } + + /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */ + fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() } + + /** + * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the + * item is boot aware, this method removes the data from both the boot aware and encrypted + * files. + * + * @return a list of editors with all transactions added so that the caller can determine to use + * .apply() or .commit() + */ + private fun prepareToRemove(items: Array): List { + val itemsPerFile = items.groupBy { it.sharedPrefs }.toMap() + + return itemsPerFile.map { (prefs, items) -> + prefs.edit().also { editor -> + items.forEach { item -> editor.remove(item.sharedPrefKey) } + } + } + } companion object { @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" - @JvmField - var INSTANCE = MainThreadInitializedObject { LauncherPrefsImpl(it) } + @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs) @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context) @@ -212,214 +365,6 @@ abstract class LauncherPrefs : SafeCloseable { } } -private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() { - private val deviceProtectedStorageContext = - encryptedContext.createDeviceProtectedStorageContext() - - private val bootAwarePrefs - get() = - deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE) - - private val Item.encryptedPrefs - get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) - - private fun chooseSharedPreferences(item: Item): SharedPreferences = - if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs - else item.encryptedPrefs - - /** Wrapper around `getInner` for a `ContextualItem` */ - override fun get(item: ContextualItem): T = - getInner(item, item.defaultValueFromContext(encryptedContext)) - - /** Wrapper around `getInner` for an `Item` */ - override fun get(item: ConstantItem): T = getInner(item, item.defaultValue) - - /** - * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the - * default value type, and will throw an error if the type of the item provided is not a - * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set`. - */ - @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") - private fun getInner(item: Item, default: T): T { - val sp = chooseSharedPreferences(item) - - return when (item.type) { - String::class.java -> sp.getString(item.sharedPrefKey, default as? String) - Boolean::class.java, - java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean) - Int::class.java, - java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int) - Float::class.java, - java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float) - Long::class.java, - java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long) - Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set) - else -> - throw IllegalArgumentException( - "item type: ${item.type}" + " is not compatible with sharedPref methods" - ) - } - as T - } - - /** - * Stores each of the values provided in `SharedPreferences` according to the configuration - * contained within the associated items provided. Internally, it uses apply, so the caller - * cannot assume that the values that have been put are immediately available for use. - * - * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from - * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the - * provided item configurations. - */ - override fun put(vararg itemsToValues: Pair): Unit = - prepareToPutValues(itemsToValues).forEach { it.apply() } - - /** See referenced `put` method above. */ - override fun put(item: Item, value: T): Unit = put(item.to(value)) - - /** - * Synchronously stores all the values provided according to their associated Item - * configuration. - */ - override fun putSync(vararg itemsToValues: Pair): Unit = - prepareToPutValues(itemsToValues).forEach { it.commit() } - - /** - * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If - * the item is boot aware, this method updates both the boot aware and the encrypted files. This - * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs - * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which - * already points to encrypted storage. - * - * Returns a list of editors with all transactions added so that the caller can determine to use - * .apply() or .commit() - */ - private fun prepareToPutValues( - updates: Array> - ): List { - val updatesPerPrefFile = - updates - .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED } - .groupBy { it.first.encryptedPrefs } - .toMutableMap() - - val bootAwareUpdates = - updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } - if (bootAwareUpdates.isNotEmpty()) { - updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates - } - - return updatesPerPrefFile.map { prefToItemValueList -> - prefToItemValueList.key.edit().apply { - prefToItemValueList.value.forEach { itemToValue: Pair -> - putValue(itemToValue.first, itemToValue.second) - } - } - } - } - - /** - * Handles adding values to `SharedPreferences` regardless of type. This method is especially - * helpful for updating `SharedPreferences` values for `List<Any>` that have multiple - * types of Item values. - */ - @Suppress("UNCHECKED_CAST") - private fun SharedPreferences.Editor.putValue( - item: Item, - value: Any?, - ): SharedPreferences.Editor = - when (item.type) { - String::class.java -> putString(item.sharedPrefKey, value as? String) - Boolean::class.java, - java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean) - Int::class.java, - java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int) - Float::class.java, - java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float) - Long::class.java, - java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long) - Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set) - else -> - throw IllegalArgumentException( - "item type: ${item.type} is not compatible with sharedPref methods" - ) - } - - /** - * After calling this method, the listener will be notified of any future updates to the - * `SharedPreferences` files associated with the provided list of items. The listener will need - * to filter update notifications so they don't activate for non-relevant updates. - */ - override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) { - items - .map { chooseSharedPreferences(it) } - .distinct() - .forEach { it.registerOnSharedPreferenceChangeListener(listener) } - } - - /** - * Stops the listener from getting notified of any more updates to any of the - * `SharedPreferences` files associated with any of the provided list of [Item]. - */ - override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) { - // If a listener is not registered to a SharedPreference, unregistering it does nothing - items - .map { chooseSharedPreferences(it) } - .distinct() - .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) } - } - - /** - * Checks if all the provided [Item] have values stored in their corresponding - * `SharedPreferences` files. - */ - override fun has(vararg items: Item): Boolean { - items - .groupBy { chooseSharedPreferences(it) } - .forEach { (prefs, itemsSublist) -> - if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false - } - return true - } - - /** - * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. - */ - override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() } - - /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */ - override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() } - - /** - * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the - * item is boot aware, this method removes the data from both the boot aware and encrypted - * files. - * - * @return a list of editors with all transactions added so that the caller can determine to use - * .apply() or .commit() - */ - private fun prepareToRemove(items: Array): List { - val itemsPerFile = - items - .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED } - .groupBy { it.encryptedPrefs } - .toMutableMap() - - val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } - if (bootAwareUpdates.isNotEmpty()) { - itemsPerFile[bootAwarePrefs] = bootAwareUpdates - } - - return itemsPerFile.map { (prefs, items) -> - prefs.edit().also { editor -> - items.forEach { item -> editor.remove(item.sharedPrefKey) } - } - } - } - - override fun close() {} -} - abstract class Item { abstract val sharedPrefKey: String abstract val isBackedUp: Boolean diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java index 60bf3eacc6..e8b7247240 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java @@ -40,8 +40,6 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BubbleTextView; -import com.android.launcher3.Flags; -import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.allapps.search.SearchAdapterProvider; import com.android.launcher3.model.data.AppInfo; @@ -219,9 +217,7 @@ public abstract class BaseAllAppsAdapter ex public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case VIEW_TYPE_ICON: - int layout = (Flags.enableTwolineToggle() - && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get( - mActivityContext.getApplicationContext())) + int layout = mActivityContext.getDeviceProfile().inv.enableTwoLinesInAllApps ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon; BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( layout, parent, false); diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java index 340fb02d87..72a97a841d 100644 --- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java +++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java @@ -18,6 +18,7 @@ package com.android.launcher3.dagger; import android.content.Context; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.contextualeducation.ContextualEduStatsManager; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.model.ItemInstallQueue; @@ -62,6 +63,7 @@ public interface LauncherBaseAppComponent { VibratorWrapper getVibratorWrapper(); MSDLPlayerWrapper getMSDLPlayerWrapper(); WindowManagerProxy getWmProxy(); + LauncherPrefs getLauncherPrefs(); /** Builder for LauncherBaseAppComponent. */ interface Builder { diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt index 8598917a06..6676766632 100644 --- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt @@ -284,7 +284,6 @@ abstract class AbstractDeviceProfileTest { isFixedLandscape: Boolean = false, ) { setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_TWOLINE_TOGGLE) - LauncherPrefs.get(testContext).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, true) val windowsBounds = perDisplayBoundsCache[displayInfo]!! val realBounds = windowsBounds[rotation] whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo) @@ -310,10 +309,11 @@ abstract class AbstractDeviceProfileTest { val configurationContext = runningContext.createConfigurationContext(config) context = SandboxContext(configurationContext) context.initDaggerComponent( - DaggerAbsDPTestSandboxComponent.builder().bindWMProxy(windowManagerProxy) + DaggerAbsDPTestSandboxComponent.builder() + .bindWMProxy(windowManagerProxy) + .bindLauncherPrefs(launcherPrefs) ) context.putObject(DisplayController.INSTANCE, displayController) - context.putObject(LauncherPrefs.INSTANCE, launcherPrefs) whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false) whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true) @@ -322,6 +322,7 @@ abstract class AbstractDeviceProfileTest { whenever(launcherPrefs.get(LauncherPrefs.DEVICE_TYPE)).thenReturn(-1) whenever(launcherPrefs.get(LauncherPrefs.WORKSPACE_SIZE)).thenReturn("") whenever(launcherPrefs.get(LauncherPrefs.DB_FILE)).thenReturn("") + whenever(launcherPrefs.get(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE)).thenReturn(true) val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache)) whenever(displayController.info).thenReturn(info) whenever(info.isTransientTaskbar).thenReturn(isGestureMode) @@ -375,6 +376,8 @@ interface AbsDPTestSandboxComponent : LauncherAppComponent { interface Builder : LauncherAppComponent.Builder { @BindsInstance fun bindWMProxy(proxy: MyWmProxy): Builder + @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder + override fun build(): AbsDPTestSandboxComponent } } diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt index 946bbc55ca..7573d2f937 100644 --- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt +++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt @@ -17,61 +17,24 @@ package com.android.launcher3 import android.content.Context -import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import android.content.Context.MODE_PRIVATE +import android.content.SharedPreferences +import com.android.launcher3.dagger.ApplicationContext +import com.android.launcher3.dagger.LauncherAppSingleton +import java.io.File +import javax.inject.Inject /** Emulates Launcher preferences for a test environment. */ -class FakeLauncherPrefs(private val context: Context) : LauncherPrefs() { - private val prefsMap = mutableMapOf() - private val listeners = mutableSetOf() +@LauncherAppSingleton +class FakeLauncherPrefs @Inject constructor(@ApplicationContext context: Context) : + LauncherPrefs(context) { - @Suppress("UNCHECKED_CAST") - override fun get(item: ContextualItem): T { - return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValueFromContext(context)) as T - } + private val backingPrefs = + context.getSharedPreferences( + File.createTempFile("fake-pref", ".xml", context.filesDir), + MODE_PRIVATE, + ) - @Suppress("UNCHECKED_CAST") - override fun get(item: ConstantItem): T { - return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValue) as T - } - - override fun put(vararg itemsToValues: Pair) = putSync(*itemsToValues) - - override fun put(item: Item, value: T) = putSync(item to value) - - override fun putSync(vararg itemsToValues: Pair) { - itemsToValues - .map { (i, v) -> i.sharedPrefKey to v } - .forEach { (k, v) -> - prefsMap[k] = v - notifyChange(k) - } - } - - override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) { - listeners.add(listener) - } - - override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) { - listeners.remove(listener) - } - - override fun has(vararg items: Item) = items.all { it.sharedPrefKey in prefsMap } - - override fun remove(vararg items: Item) = removeSync(*items) - - override fun removeSync(vararg items: Item) { - items - .filter { it.sharedPrefKey in prefsMap } - .forEach { - prefsMap.remove(it.sharedPrefKey) - notifyChange(it.sharedPrefKey) - } - } - - override fun close() = Unit - - private fun notifyChange(key: String) { - // Mimics SharedPreferencesImpl#notifyListeners main thread dispatching. - MAIN_EXECUTOR.execute { listeners.forEach { it.onPrefChanged(key) } } - } + override val Item.sharedPrefs: SharedPreferences + get() = backingPrefs } diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt index 2463c93d83..c57c86f685 100644 --- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt @@ -128,7 +128,7 @@ class FakeLauncherPrefsTest { val listener = LauncherPrefChangeListener { changedKey = it } launcherPrefs.addListener(listener, TEST_CONSTANT_ITEM) - launcherPrefs.removeListener(listener) + launcherPrefs.removeListener(listener, TEST_CONSTANT_ITEM) getInstrumentation().runOnMainSync { launcherPrefs.put(TEST_CONSTANT_ITEM, true) } assertThat(changedKey).isNull() } diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java index b933ed2cdf..f51871b647 100644 --- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java @@ -20,8 +20,6 @@ import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER; -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; - import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS; import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW; import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT; @@ -44,6 +42,7 @@ import android.content.Intent; import android.graphics.Typeface; import android.os.Build; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.text.SpannedString; @@ -64,8 +63,10 @@ import com.android.launcher3.search.StringMatcherUtility; import com.android.launcher3.util.ActivityContextWrapper; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext; import com.android.launcher3.views.BaseDragLayer; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -111,19 +112,22 @@ public class BubbleTextViewTest { private static final float SPACE_MULTIPLIER = 1; private static final float SPACE_EXTRA = 0; + private SandboxModelContext mModelContext; + private BubbleTextView mBubbleTextView; private ItemInfoWithIcon mItemInfoWithIcon; private Context mContext; private int mLimitedWidth; private AppInfo mGmailAppInfo; - private LauncherPrefs mLauncherPrefs; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Utilities.enableRunningInTestHarnessForTests(); - mContext = new ActivityContextWrapper(getApplicationContext()); - mLauncherPrefs = LauncherPrefs.get(mContext); + mModelContext = new SandboxModelContext(); + LauncherPrefs.get(mModelContext).put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); + + mContext = new ActivityContextWrapper(mModelContext); mBubbleTextView = new BubbleTextView(mContext); mBubbleTextView.reset(); @@ -149,10 +153,14 @@ public class BubbleTextViewTest { mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent()); } + @After + public void tearDown() { + mModelContext.onDestroy(); + } + @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testEmptyString_flagOn() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); mItemInfoWithIcon.title = EMPTY_STRING; mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -165,8 +173,8 @@ public class BubbleTextViewTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testEmptyString_flagOff() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); mItemInfoWithIcon.title = EMPTY_STRING; mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -179,9 +187,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testStringWithSpaceLongerThanCharLimit_flagOn() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "Battery Stats" mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -195,8 +202,8 @@ public class BubbleTextViewTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testStringWithSpaceLongerThanCharLimit_flagOff() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "Battery Stats" mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -210,9 +217,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringNoSpaceLongerThanCharLimit_flagOn() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "flutterappflorafy" mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -226,8 +232,8 @@ public class BubbleTextViewTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringNoSpaceLongerThanCharLimit_flagOff() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "flutterappflorafy" mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -241,9 +247,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringWithSpaceLongerThanCharLimit_flagOn() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "System UWB Field Test" mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -257,8 +262,8 @@ public class BubbleTextViewTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringWithSpaceLongerThanCharLimit_flagOff() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "System UWB Field Test" mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -272,9 +277,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringSymbolLongerThanCharLimit_flagOn() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -288,8 +292,8 @@ public class BubbleTextViewTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringSymbolLongerThanCharLimit_flagOff() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -359,9 +363,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testEnsurePredictionRowIsTwoLine() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "Battery Stats" mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW); @@ -375,9 +378,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -390,9 +392,8 @@ public class BubbleTextViewTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); - mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt index d0cf6107e5..e6e156fb1e 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt @@ -43,7 +43,6 @@ import dagger.BindsInstance import dagger.Component import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue -import kotlin.math.min import org.junit.After import org.junit.Before import org.junit.Test @@ -58,6 +57,7 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.stubbing.Answer +import kotlin.math.min /** Unit tests for {@link DisplayController} */ @SmallTest @@ -97,9 +97,10 @@ class DisplayControllerTest { @Before fun setUp() { context.initDaggerComponent( - DaggerDisplayControllerTestComponent.builder().bindWMProxy(windowManagerProxy) + DaggerDisplayControllerTestComponent.builder() + .bindWMProxy(windowManagerProxy) + .bindLauncherPrefs(launcherPrefs) ) - context.putObject(LauncherPrefs.INSTANCE, launcherPrefs) displayManager = context.spyService(DisplayManager::class.java) whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) @@ -243,6 +244,8 @@ interface DisplayControllerTestComponent : LauncherAppComponent { interface Builder : LauncherAppComponent.Builder { @BindsInstance fun bindWMProxy(proxy: MyWmProxy): Builder + @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder + override fun build(): DisplayControllerTestComponent } }