Minimal Phone Mode (part-3)

Introduces a separate database for minimal device mode.

When minimal device mode is enabled/disabled:
1. WellbeingModel receives onChange event from ContentObserver
2. WellbeingModel called DWB's ContentProvider for latest state in
minimal device mode
3. Based on the state, WellbeingModel calls LauncherProvider to put
launcher into normal/minimal mode.
4. When going from normal -> minimal, Launcher switches to a different
database, namely minimal.db, then proceed to database initialization.
5. If the database hasn't been initialized yet, Launcher will call
ContentResolver#openInputStream with following uri:
content://com.google.android.apps.wellbeing.api/launcher_layout
to get the default layout xml.
6. The default layout is then saved in database, and the database is
considered initialized and doesn't need to go through step 5 again in
the future.
7. In case of minimal -> normal, Launcher switches back to its original
database (e.g. launcher.db if the grid size is 5x5), then reload launcher.

Bug: 161462256
Change-Id: I6bafa66440da23281f63454b698ea56b15960022
This commit is contained in:
Pinyao Ting
2020-07-20 11:03:39 -07:00
parent 107fe60f6e
commit 96186aff87
3 changed files with 97 additions and 14 deletions

View File

@@ -43,9 +43,13 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
@@ -74,6 +78,9 @@ public final class WellbeingModel {
private static final int MSG_PACKAGE_REMOVED = 2;
private static final int MSG_FULL_REFRESH = 3;
private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
private static final int IN_MINIMAL_DEVICE = 2;
// Welbeing contract
private static final String PATH_ACTIONS = "actions";
private static final String PATH_MINIMAL_DEVICE = "minimal_device";
@@ -84,6 +91,8 @@ public final class WellbeingModel {
private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
private static final String EXTRA_PACKAGES = "packages";
private static final String EXTRA_SUCCESS = "success";
private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state";
private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db";
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -121,11 +130,12 @@ public final class WellbeingModel {
updateWellbeingData();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
updateLauncherModel();
updateLauncherModel(context);
}
}
};
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, this::updateLauncherModel);
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
updateLauncherModel(context));
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
context.registerReceiver(
@@ -170,7 +180,6 @@ public final class WellbeingModel {
Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
}
updateWellbeingData();
}
@@ -208,10 +217,34 @@ public final class WellbeingModel {
mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
}
private void updateLauncherModel() {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) return;
private void updateLauncherModel(@NonNull final Context context) {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
reloadLauncherInNormalMode(context);
return;
}
runWithMinimalDeviceConfigs((bundle) -> {
if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE)
== IN_MINIMAL_DEVICE) {
reloadLauncherInMinimalMode(context);
} else {
reloadLauncherInNormalMode(context);
}
});
}
// TODO: init Launcher in minimal device / normal mode
private void reloadLauncherInNormalMode(@NonNull final Context context) {
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
InvariantDeviceProfile.INSTANCE.get(context).dbFile);
}
private void reloadLauncherInMinimalMode(@NonNull final Context context) {
final Bundle extras = new Bundle();
extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
mWellbeingProviderPkg + ".api");
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
DB_NAME_MINIMAL_DEVICE, extras);
}
private Uri.Builder apiBuilder() {
@@ -225,6 +258,9 @@ public final class WellbeingModel {
*/
@WorkerThread
private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
return;
}
if (DEBUG || mIsInTest) {
Log.d(TAG, "runWithMinimalDeviceConfigs() called");
}

View File

@@ -100,10 +100,12 @@ public class LauncherProvider extends ContentProvider {
public static final int SCHEMA_VERSION = 28;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
protected DatabaseHelper mOpenHelper;
protected String mProviderAuthority;
private long mLastRestoreTimestamp = 0L;
@@ -367,7 +369,8 @@ public class LauncherProvider extends ContentProvider {
case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
Bundle result = new Bundle();
result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false));
Utilities.getPrefs(getContext()).getBoolean(
mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false));
return result;
}
case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
@@ -437,6 +440,7 @@ public class LauncherProvider extends ContentProvider {
getContext(), true /* forMigration */)));
return result;
}
return null;
}
case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
@@ -450,6 +454,23 @@ public class LauncherProvider extends ContentProvider {
() -> mOpenHelper));
return result;
}
return null;
}
case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
final DatabaseHelper helper = mOpenHelper;
if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
mProviderAuthority = null;
} else {
mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
}
mOpenHelper = DatabaseHelper.createDatabaseHelper(
getContext(), arg, false /* forMigration */);
helper.close();
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) return null;
app.getModel().forceReload();
return null;
}
}
return null;
@@ -492,7 +513,8 @@ public class LauncherProvider extends ContentProvider {
}
private void clearFlagEmptyDbCreated() {
Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
Utilities.getPrefs(getContext()).edit()
.remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
}
/**
@@ -505,7 +527,7 @@ public class LauncherProvider extends ContentProvider {
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
Log.d(TAG, "loading default workspace");
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
@@ -553,8 +575,13 @@ public class LauncherProvider extends ContentProvider {
*/
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
Context ctx = getContext();
String authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
final String authority;
if (!TextUtils.isEmpty(mProviderAuthority)) {
authority = mProviderAuthority;
} else {
authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
}
if (TextUtils.isEmpty(authority)) {
return null;
}
@@ -693,12 +720,26 @@ public class LauncherProvider extends ContentProvider {
}
}
/**
* Re-composite given key in respect to database. If the current db is
* {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
* given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
* string will be "EMPTY_DATABASE_CREATED@minimal.db".
*/
String getKey(final String key) {
if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
return key;
}
return key + "@" + getDatabaseName();
}
/**
* Overriden in tests.
*/
protected void onEmptyDbCreated() {
// Set the flag for empty DB
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
.commit();
}
public long getSerialNumberForUser(UserHandle user) {

View File

@@ -354,14 +354,20 @@ public class LauncherSettings {
public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
public static final String METHOD_SWITCH_DATABASE = "switch_database";
public static final String EXTRA_VALUE = "value";
public static Bundle call(ContentResolver cr, String method) {
return call(cr, method, null);
return call(cr, method, null /* arg */);
}
public static Bundle call(ContentResolver cr, String method, String arg) {
return cr.call(CONTENT_URI, method, arg, null);
return call(cr, method, arg, null /* extras */);
}
public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
return cr.call(CONTENT_URI, method, arg, extras);
}
}
}