Merge "Converting LauncherPrefs to dagger" into main

This commit is contained in:
Sunny Goyal
2025-01-03 14:18:19 -08:00
committed by Android (Google) Code Review
16 changed files with 287 additions and 407 deletions
@@ -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<T extends Context & ActivityContext>
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();
@@ -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
+3 -10
View File
@@ -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);
@@ -474,13 +472,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
+3 -11
View File
@@ -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);
@@ -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. */
+1 -10
View File
@@ -415,7 +415,6 @@ public class Launcher extends StatefulActivity<LauncherState>
private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
private boolean mIsColdStartupAfterReboot;
private boolean mForceConfigUpdate;
private boolean mIsNaturalScrollingEnabled;
@@ -761,7 +760,7 @@ public class Launcher extends StatefulActivity<LauncherState>
protected void onHandleConfigurationChanged() {
Trace.beginSection("Launcher#onHandleconfigurationChanged");
try {
if (!initDeviceProfile(mDeviceProfile.inv) && !mForceConfigUpdate) {
if (!initDeviceProfile(mDeviceProfile.inv)) {
return;
}
dispatchDeviceProfileChanged();
@@ -774,7 +773,6 @@ public class Launcher extends StatefulActivity<LauncherState>
mModel.rebindCallbacks();
updateDisallowBack();
} finally {
mForceConfigUpdate = false;
Trace.endSection();
}
}
@@ -3159,13 +3157,6 @@ public class Launcher extends StatefulActivity<LauncherState>
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;
@@ -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));
@@ -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()
+176 -231
View File
@@ -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 <T> get(item: ContextualItem<T>): T
fun <T> get(item: ContextualItem<T>): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Returns the value with type [T] for [item]. */
abstract fun <T> get(item: ConstantItem<T>): T
fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
/** Stores the values for each item in preferences. */
abstract fun put(vararg itemsToValues: Pair<Item, Any>)
/**
* 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<String>`.
*/
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
private fun <T> getInner(item: Item, default: T): T {
val sp = item.sharedPrefs
/** Stores the [value] with type [T] for [item] in preferences. */
abstract fun <T : Any> 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<String>)
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<Item, Any>)
/**
* 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<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.apply() }
/** Registers [listener] for [items]. */
abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
/** See referenced `put` method above. */
fun <T : Any> 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<Item, Any>): 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<out Pair<Item, Any>>
): List<SharedPreferences.Editor> {
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<<Item>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<String>)
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<out Item>): List<SharedPreferences.Editor> {
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<LauncherPrefs> { 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 <T> get(item: ContextualItem<T>): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Wrapper around `getInner` for an `Item` */
override fun <T> get(item: ConstantItem<T>): 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<String>`.
*/
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
private fun <T> 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<String>)
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<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.apply() }
/** See referenced `put` method above. */
override fun <T : Any> 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<Item, Any>): 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<out Pair<Item, Any>>
): List<SharedPreferences.Editor> {
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<Item, Any> ->
putValue(itemToValue.first, itemToValue.second)
}
}
}
}
/**
* Handles adding values to `SharedPreferences` regardless of type. This method is especially
* helpful for updating `SharedPreferences` values for `List<<Item>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<String>)
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<out Item>): List<SharedPreferences.Editor> {
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
@@ -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<T extends Context & ActivityContext> 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);
@@ -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 {
@@ -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
}
}
@@ -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<String, Any>()
private val listeners = mutableSetOf<LauncherPrefChangeListener>()
@LauncherAppSingleton
class FakeLauncherPrefs @Inject constructor(@ApplicationContext context: Context) :
LauncherPrefs(context) {
@Suppress("UNCHECKED_CAST")
override fun <T> get(item: ContextualItem<T>): 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 <T> get(item: ConstantItem<T>): T {
return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValue) as T
}
override fun put(vararg itemsToValues: Pair<Item, Any>) = putSync(*itemsToValues)
override fun <T : Any> put(item: Item, value: T) = putSync(item to value)
override fun putSync(vararg itemsToValues: Pair<Item, Any>) {
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
}
@@ -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()
}
@@ -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);
@@ -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
}
}