From ab2f808196dd4701c742ca17d475aebc07be5366 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 11 Apr 2023 11:35:43 -0700 Subject: [PATCH] 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 --- .../testing/DebugTestInformationHandler.java | 39 -------- .../android/quickstep/TaplTestsTaskbar.java | 16 ++- res/xml/default_tapl_test_workspace.xml | 28 ------ res/xml/default_test2_workspace.xml | 57 ----------- res/xml/default_test_workspace.xml | 29 ------ .../android/launcher3/AutoInstallsLayout.java | 49 +++++++++- .../android/launcher3/LauncherProvider.java | 8 -- .../android/launcher3/LauncherSettings.java | 13 +-- .../launcher3/model/ModelDbController.java | 97 ++++++++++--------- tests/Android.bp | 1 + .../testing/shared/TestProtocol.java | 5 - .../launcher3/ui/TaplTestsLauncher3.java | 17 +++- .../ui/workspace/TwoPanelWorkspaceTest.java | 24 ++++- .../com/android/launcher3/util/TestUtil.java | 45 +++++++++ .../tapl/LauncherInstrumentation.java | 30 ------ 15 files changed, 193 insertions(+), 265 deletions(-) delete mode 100644 res/xml/default_tapl_test_workspace.xml delete mode 100644 res/xml/default_test2_workspace.xml delete mode 100644 res/xml/default_test_workspace.xml diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java index 4f9c32ade5..3fddd9d693 100644 --- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java +++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java @@ -218,29 +218,6 @@ public class DebugTestInformationHandler extends TestInformationHandler { } } - case TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT: { - useTestWorkspaceLayout( - LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST); - return response; - } - - case TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT: { - useTestWorkspaceLayout( - LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2); - return response; - } - - case TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT: { - useTestWorkspaceLayout( - LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL); - return response; - } - - case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: { - useTestWorkspaceLayout(null); - return response; - } - case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: { return getLauncherUIProperty(Bundle::putStringArrayList, l -> { ShortcutAndWidgetContainer hotseatIconsContainer = @@ -278,20 +255,4 @@ public class DebugTestInformationHandler extends TestInformationHandler { return super.call(method, arg, extras); } } - - private void useTestWorkspaceLayout(String layout) { - final long identity = Binder.clearCallingIdentity(); - try { - if (layout != null) { - LauncherSettings.Settings.call(mContext.getContentResolver(), - LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG, - layout); - } else { - LauncherSettings.Settings.call(mContext.getContentResolver(), - LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java index 735c5e6fd4..624347131b 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java @@ -29,6 +29,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.tapl.Taskbar; import com.android.launcher3.ui.TaplTestsLauncher3; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; @@ -49,11 +51,17 @@ public class TaplTestsTaskbar extends AbstractQuickStepTest { private static final String CALCULATOR_APP_PACKAGE = resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR); + private AutoCloseable mLauncherLayout; + @Override public void setUp() throws Exception { Assume.assumeTrue(mLauncher.isTablet()); super.setUp(); - mLauncher.useTestWorkspaceLayoutOnReload(); + + LauncherLayoutBuilder layoutBuilder = new LauncherLayoutBuilder().atHotseat(0).putApp( + "com.google.android.apps.nexuslauncher.tests", + "com.android.launcher3.testcomponent.BaseTestingActivity"); + mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder); TaplTestsLauncher3.initialize(this); startAppFast(CALCULATOR_APP_PACKAGE); @@ -62,9 +70,11 @@ public class TaplTestsTaskbar extends AbstractQuickStepTest { } @After - public void tearDown() { - mLauncher.useDefaultWorkspaceLayoutOnReload(); + public void tearDown() throws Exception { mLauncher.enableBlockTimeout(false); + if (mLauncherLayout != null) { + mLauncherLayout.close(); + } } @Test diff --git a/res/xml/default_tapl_test_workspace.xml b/res/xml/default_tapl_test_workspace.xml deleted file mode 100644 index 24d76f3b1a..0000000000 --- a/res/xml/default_tapl_test_workspace.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - diff --git a/res/xml/default_test2_workspace.xml b/res/xml/default_test2_workspace.xml deleted file mode 100644 index c560104b9e..0000000000 --- a/res/xml/default_test2_workspace.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/default_test_workspace.xml b/res/xml/default_test_workspace.xml deleted file mode 100644 index bd718b3b14..0000000000 --- a/res/xml/default_test_workspace.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 197aa5a178..ede7e2f950 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -27,6 +27,8 @@ import android.content.pm.LauncherActivityInfo; 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.Log; 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 @@ public class AutoInstallsLayout { protected final LayoutParserCallback mCallback; protected final PackageManager mPackageManager; - protected final Resources mSourceRes; + protected final SourceResources mSourceRes; protected final Supplier mInitialLayoutSupplier; private final InvariantDeviceProfile mIdp; @@ -178,11 +182,12 @@ public class AutoInstallsLayout { 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 initialLayoutSupplier, String rootTag) { mContext = context; mAppWidgetHolder = appWidgetHolder; @@ -703,4 +708,42 @@ public class AutoInstallsLayout { 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 dee3205c92..d30d23ceda 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -244,14 +244,6 @@ public class LauncherProvider extends ContentProvider { 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 1bbb09afe4..1ca3747eec 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -374,15 +374,6 @@ public class LauncherSettings { 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 class LauncherSettings { 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 7452bcd7fc..9b54ce131d 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 @@ import java.util.function.Supplier; 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; @@ -223,21 +227,6 @@ public class ModelDbController { mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); } - /** - * 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 */ @@ -403,39 +392,55 @@ public class ModelDbController { */ 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 @@ public class ModelDbController { 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); diff --git a/tests/Android.bp b/tests/Android.bp index 81853d15ff..da8d844542 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -45,6 +45,7 @@ filegroup { "src/com/android/launcher3/ui/AbstractLauncherUiTest.java", "src/com/android/launcher3/ui/PortraitLandscapeRunner.java", "src/com/android/launcher3/ui/TaplTestsLauncher3.java", + "src/com/android/launcher3/util/LauncherLayoutBuilder.java", "src/com/android/launcher3/util/TestUtil.java", "src/com/android/launcher3/util/Wait.java", "src/com/android/launcher3/util/WidgetUtils.java", diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java index a37c3cd3c8..dc835e2485 100644 --- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java +++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java @@ -106,11 +106,6 @@ public final class TestProtocol { public static final String REQUEST_GET_HAD_NONTEST_EVENTS = "get-had-nontest-events"; public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging"; public static final String REQUEST_CLEAR_DATA = "clear-data"; - public static final String REQUEST_USE_TEST_WORKSPACE_LAYOUT = "use-test-workspace-layout"; - public static final String REQUEST_USE_TEST2_WORKSPACE_LAYOUT = "use-test2-workspace-layout"; - public static final String REQUEST_USE_TAPL_WORKSPACE_LAYOUT = "use-tapl-workspace-layout"; - public static final String REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT = - "use-default-workspace-layout"; public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names"; public static final String REQUEST_IS_TABLET = "is-tablet"; public static final String REQUEST_IS_TWO_PANELS = "is-two-panel"; diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 217bec3aa1..81f1525ee4 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -54,12 +54,14 @@ import com.android.launcher3.tapl.HomeAppIcon; import com.android.launcher3.tapl.HomeAppIconMenuItem; import com.android.launcher3.tapl.Widgets; import com.android.launcher3.tapl.Workspace; +import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.util.rule.TISBindRule; import com.android.launcher3.widget.picker.WidgetsFullSheet; import com.android.launcher3.widget.picker.WidgetsRecyclerView; +import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -83,6 +85,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @Rule public TISBindRule mTISBindRule = new TISBindRule(); + private AutoCloseable mLauncherLayout; + @Before public void setUp() throws Exception { super.setUp(); @@ -101,6 +105,13 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher); } + @After + public void tearDown() throws Exception { + if (mLauncherLayout != null) { + mLauncherLayout.close(); + } + } + // Please don't add negative test cases for methods that fail only after a long wait. public static void expectFail(String message, Runnable action) { boolean failed = false; @@ -230,8 +241,10 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @Test @ScreenRecord // b/202433017 public void testWorkspace() throws Exception { - // Make sure there is an instance of chrome on the hotseat - mLauncher.useTaplWorkspaceLayoutOnReload(); + // Set workspace that includes the chrome Activity app icon on the hotseat. + LauncherLayoutBuilder builder = new LauncherLayoutBuilder() + .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main"); + mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder); clearLauncherData(); final Workspace workspace = mLauncher.getWorkspace(); diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java index 3abafdfccb..c4b6d43466 100644 --- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java +++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java @@ -30,6 +30,8 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TaplTestsLauncher3; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.TestUtil; import org.junit.After; import org.junit.Before; @@ -49,12 +51,24 @@ import java.util.stream.Collectors; @RunWith(AndroidJUnit4.class) public class TwoPanelWorkspaceTest extends AbstractLauncherUiTest { + private AutoCloseable mLauncherLayout; + @Before public void setUp() throws Exception { super.setUp(); - mLauncher.useTest2WorkspaceLayoutOnReload(); - TaplTestsLauncher3.initialize(this); + // Set layout that includes Maps/Play on workspace, and Messaging/Chrome on hotseat. + LauncherLayoutBuilder builder = new LauncherLayoutBuilder() + .atHotseat(0).putApp( + "com.google.android.apps.messaging", + "com.google.android.apps.messaging.ui.ConversationListActivity") + .atHotseat(1).putApp("com.android.chrome", "com.google.android.apps.chrome.Main") + .atWorkspace(0, -1, 0).putApp( + "com.google.android.apps.maps", "com.google.android.maps.MapsActivity") + .atWorkspace(3, -1, 0).putApp( + "com.android.vending", "com.android.vending.AssetBrowserActivity"); + mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder); + TaplTestsLauncher3.initialize(this); assumeTrue(mLauncher.isTwoPanels()); // Pre verifying the screens @@ -67,9 +81,11 @@ public class TwoPanelWorkspaceTest extends AbstractLauncherUiTest { } @After - public void tearDown() { + public void tearDown() throws Exception { executeOnLauncher(launcher -> launcher.enableHotseatEdu(true)); - mLauncher.useDefaultWorkspaceLayoutOnReload(); + if (mLauncherLayout != null) { + mLauncherLayout.close(); + } } @Test diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java index 433fd31bcc..4981795fe5 100644 --- a/tests/src/com/android/launcher3/util/TestUtil.java +++ b/tests/src/com/android/launcher3/util/TestUtil.java @@ -15,15 +15,29 @@ */ package com.android.launcher3.util; +import static android.util.Base64.NO_PADDING; +import static android.util.Base64.NO_WRAP; + import static androidx.test.InstrumentationRegistry.getContext; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static androidx.test.InstrumentationRegistry.getTargetContext; +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 android.app.blob.BlobHandle; +import android.app.blob.BlobStoreManager; +import android.content.Context; import android.content.pm.LauncherApps; import android.content.res.Resources; +import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; import android.os.UserHandle; +import android.provider.Settings; +import android.util.Base64; import androidx.test.uiautomator.UiDevice; @@ -36,6 +50,8 @@ import org.junit.Assert; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; import java.util.concurrent.CountDownLatch; import java.util.function.Predicate; import java.util.function.ToIntFunction; @@ -109,6 +125,35 @@ public class TestUtil { "pm uninstall " + DUMMY_PACKAGE); } + /** + * Sets the default layout for Launcher and returns an object which can be used to clear + * the data + */ + public static AutoCloseable setLauncherDefaultLayout( + Context context, LauncherLayoutBuilder layoutBuilder) throws Exception { + byte[] data = layoutBuilder.build().getBytes(); + byte[] digest = MessageDigest.getInstance("SHA-256").digest(data); + + BlobHandle handle = BlobHandle.createWithSha256( + digest, LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG); + BlobStoreManager blobManager = context.getSystemService(BlobStoreManager.class); + final long sessionId = blobManager.createSession(handle); + CountDownLatch wait = new CountDownLatch(1); + try (BlobStoreManager.Session session = blobManager.openSession(sessionId)) { + try (OutputStream out = new AutoCloseOutputStream(session.openWrite(0, -1))) { + out.write(data); + } + session.allowPublicAccess(); + session.commit(AsyncTask.THREAD_POOL_EXECUTOR, i -> wait.countDown()); + } + + String key = Base64.encodeToString(digest, NO_WRAP | NO_PADDING); + Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, key); + wait.await(); + return () -> + Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null); + } + private static class PackageInstallCheck extends LauncherApps.Callback implements AutoCloseable { diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 9905603852..ba8f070aec 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -1851,36 +1851,6 @@ public final class LauncherInstrumentation { getTestInfo(TestProtocol.REQUEST_CLEAR_DATA); } - /** - * Reloads the workspace with a test layout that includes the Test Activity app icon on the - * hotseat. - */ - public void useTestWorkspaceLayoutOnReload() { - getTestInfo(TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT); - } - - /** - * Reloads the workspace with a test layout that includes Maps/Play on workspace, and - * Dialer/Messaging/Chrome/Camera on hotseat. - */ - public void useTest2WorkspaceLayoutOnReload() { - getTestInfo(TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT); - } - - - /** - * Reloads the workspace with a test layout that includes the chrome Activity app icon on the - * hotseat. - */ - public void useTaplWorkspaceLayoutOnReload() { - getTestInfo(TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT); - } - - /** Reloads the workspace with the default layout defined by the user's grid size selection. */ - public void useDefaultWorkspaceLayoutOnReload() { - getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT); - } - /** Shows the taskbar if it is hidden, otherwise does nothing. */ public void showTaskbarIfHidden() { getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);