Merge "Converting LauncherPrefs to dagger" into main
This commit is contained in:
@@ -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();
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user