Using a runtime generated layout for tests instead of defining xml
This allows support for easily setting up default layouts
Bug: 277345535
Test: Presubmit
Flag: N/A
Change-Id: I1c089d60ac3f8add8d7e1060d343e04d30afe094
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 197aa5a..ede7e2f 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -27,6 +27,8 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.XmlResourceParser;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
@@ -38,7 +40,9 @@
import android.util.Xml;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import androidx.annotation.WorkerThread;
+import androidx.annotation.XmlRes;
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -161,7 +165,7 @@
protected final LayoutParserCallback mCallback;
protected final PackageManager mPackageManager;
- protected final Resources mSourceRes;
+ protected final SourceResources mSourceRes;
protected final Supplier<XmlPullParser> mInitialLayoutSupplier;
private final InvariantDeviceProfile mIdp;
@@ -178,11 +182,12 @@
public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
LayoutParserCallback callback, Resources res,
int layoutId, String rootTag) {
- this(context, appWidgetHolder, callback, res, () -> res.getXml(layoutId), rootTag);
+ this(context, appWidgetHolder, callback, SourceResources.wrap(res),
+ () -> res.getXml(layoutId), rootTag);
}
public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
- LayoutParserCallback callback, Resources res,
+ LayoutParserCallback callback, SourceResources res,
Supplier<XmlPullParser> initialLayoutSupplier, String rootTag) {
mContext = context;
mAppWidgetHolder = appWidgetHolder;
@@ -703,4 +708,42 @@
to.put(key, from.getAsInteger(key));
}
+ /**
+ * Wrapper over resources for easier abstraction
+ */
+ public interface SourceResources {
+
+ /**
+ * Refer {@link Resources#getXml(int)}
+ */
+ default XmlResourceParser getXml(@XmlRes int id) throws NotFoundException {
+ throw new NotFoundException();
+ }
+
+ /**
+ * Refer {@link Resources#getString(int)}
+ */
+ default String getString(@StringRes int id) throws NotFoundException {
+ throw new NotFoundException();
+ }
+
+ /**
+ * Returns a {@link SourceResources} corresponding to the provided resources
+ */
+ static SourceResources wrap(Resources res) {
+ return new SourceResources() {
+ @Override
+ public XmlResourceParser getXml(int id) {
+ return res.getXml(id);
+ }
+
+ @Override
+ public String getString(int id) {
+ return res.getString(id);
+ }
+ };
+ }
+ }
+
+
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index dee3205..d30d23c 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -244,14 +244,6 @@
mModelDbController.createEmptyDB();
return null;
}
- case LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG: {
- mModelDbController.setUseTestWorkspaceLayout(arg);
- return null;
- }
- case LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG: {
- mModelDbController.setUseTestWorkspaceLayout(null);
- return null;
- }
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
mModelDbController.loadDefaultFavoritesIfNecessary();
return null;
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 1bbb09a..1ca3747 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -374,15 +374,6 @@
public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
- public static final String METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG =
- "set_use_test_workspace_layout_flag";
- public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST = "default_test_workspace";
- public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2 = "default_test2_workspace";
- public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL = "default_tapl_workspace";
-
- public static final String METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG =
- "clear_use_test_workspace_layout_flag";
-
public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
@@ -399,6 +390,10 @@
public static final String EXTRA_DB_NAME = "db_name";
+ public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob";
+ public static final String LAYOUT_DIGEST_LABEL = "launcher-layout";
+ public static final String LAYOUT_DIGEST_TAG = "ignore";
+
public static Bundle call(ContentResolver cr, String method) {
return call(cr, method, null /* arg */);
}
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 7452bcd..9b54ce1 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -15,38 +15,49 @@
*/
package com.android.launcher3.model;
+import static android.util.Base64.NO_PADDING;
+import static android.util.Base64.NO_WRAP;
+
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
import static com.android.launcher3.model.DatabaseHelper.EMPTY_DATABASE_CREATED;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import android.app.blob.BlobHandle;
+import android.app.blob.BlobStoreManager;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Base64;
import android.util.Log;
import android.util.Xml;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.AutoInstallsLayout.SourceResources;
import com.android.launcher3.DefaultLayoutParser;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -69,14 +80,7 @@
public class ModelDbController {
private static final String TAG = "LauncherProvider";
- private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
- private static final int TEST2_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test2_workspace;
- private static final int TAPL_WORKSPACE_LAYOUT_RES_XML = R.xml.default_tapl_test_workspace;
-
protected DatabaseHelper mOpenHelper;
- protected String mProviderAuthority;
-
- private int mDefaultWorkspaceLayoutOverride = 0;
private final Context mContext;
@@ -224,21 +228,6 @@
}
/**
- * Overrides the default xml to be used for setting up workspace
- */
- public void setUseTestWorkspaceLayout(@Nullable String layout) {
- if (LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST.equals(layout)) {
- mDefaultWorkspaceLayoutOverride = TEST_WORKSPACE_LAYOUT_RES_XML;
- } else if (LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2.equals(layout)) {
- mDefaultWorkspaceLayoutOverride = TEST2_WORKSPACE_LAYOUT_RES_XML;
- } else if (LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL.equals(layout)) {
- mDefaultWorkspaceLayoutOverride = TAPL_WORKSPACE_LAYOUT_RES_XML;
- } else {
- mDefaultWorkspaceLayoutOverride = 0;
- }
- }
-
- /**
* Removes any widget which are present in the framework, but not in out internal DB
*/
public void removeGhostWidgets() {
@@ -403,39 +392,55 @@
*/
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(
LauncherWidgetHolder widgetHolder) {
- final String authority;
- if (!TextUtils.isEmpty(mProviderAuthority)) {
- authority = mProviderAuthority;
- } else {
- authority = Settings.Secure.getString(mContext.getContentResolver(),
- "launcher3.layout.provider");
+ ContentResolver cr = mContext.getContentResolver();
+ String blobHandlerDigest = Settings.Secure.getString(cr, LAYOUT_DIGEST_KEY);
+ if (Utilities.ATLEAST_R && !TextUtils.isEmpty(blobHandlerDigest)) {
+ BlobStoreManager blobManager = mContext.getSystemService(BlobStoreManager.class);
+ try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
+ blobManager.openBlob(BlobHandle.createWithSha256(
+ Base64.decode(blobHandlerDigest, NO_WRAP | NO_PADDING),
+ LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG)))) {
+ return getAutoInstallsLayoutFromIS(in, widgetHolder, new SourceResources() { });
+ } catch (Exception e) {
+ Log.e(TAG, "Error getting layout from blob handle" , e);
+ return null;
+ }
}
+
+ String authority = Settings.Secure.getString(cr, "launcher3.layout.provider");
if (TextUtils.isEmpty(authority)) {
return null;
}
- ProviderInfo pi = mContext.getPackageManager().resolveContentProvider(authority, 0);
+ PackageManager pm = mContext.getPackageManager();
+ ProviderInfo pi = pm.resolveContentProvider(authority, 0);
if (pi == null) {
Log.e(TAG, "No provider found for authority " + authority);
return null;
}
Uri uri = getLayoutUri(authority, mContext);
- try (InputStream in = mContext.getContentResolver().openInputStream(uri)) {
- // Read the full xml so that we fail early in case of any IO error.
- String layout = new String(IOUtils.toByteArray(in));
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new StringReader(layout));
-
+ try (InputStream in = cr.openInputStream(uri)) {
Log.d(TAG, "Loading layout from " + authority);
- return new AutoInstallsLayout(mContext, widgetHolder, mOpenHelper,
- mContext.getPackageManager().getResourcesForApplication(pi.applicationInfo),
- () -> parser, AutoInstallsLayout.TAG_WORKSPACE);
+
+ Resources res = pm.getResourcesForApplication(pi.applicationInfo);
+ return getAutoInstallsLayoutFromIS(in, widgetHolder, SourceResources.wrap(res));
} catch (Exception e) {
Log.e(TAG, "Error getting layout stream from: " + authority , e);
return null;
}
}
+ private AutoInstallsLayout getAutoInstallsLayoutFromIS(InputStream in,
+ LauncherWidgetHolder widgetHolder, SourceResources res) throws Exception {
+ // Read the full xml so that we fail early in case of any IO error.
+ String layout = new String(IOUtils.toByteArray(in));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new StringReader(layout));
+
+ return new AutoInstallsLayout(mContext, widgetHolder, mOpenHelper, res,
+ () -> parser, AutoInstallsLayout.TAG_WORKSPACE);
+ }
+
private static Uri getLayoutUri(String authority, Context ctx) {
InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
@@ -448,13 +453,9 @@
private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
- int defaultLayout = mDefaultWorkspaceLayoutOverride > 0
- ? mDefaultWorkspaceLayoutOverride : idp.defaultLayoutId;
-
- if (mContext.getSystemService(UserManager.class).isDemoUser()
- && idp.demoModeLayoutId != 0) {
- defaultLayout = idp.demoModeLayoutId;
- }
+ int defaultLayout = idp.demoModeLayoutId != 0
+ && mContext.getSystemService(UserManager.class).isDemoUser()
+ ? idp.demoModeLayoutId : idp.defaultLayoutId;
return new DefaultLayoutParser(mContext, widgetHolder,
mOpenHelper, mContext.getResources(), defaultLayout);