diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2019-03-22 14:13:36 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2019-04-10 12:06:17 -0700 |
commit | c0f03d96656a3c70a26d3ec97e9e782fbdbe64b9 (patch) | |
tree | 8696c1cb8b375fc1efa50e8ae4fed2a5c51d1010 /tests | |
parent | e09e1f2253a9f20f1c91532956053a11b43ad355 (diff) | |
download | android_packages_apps_Trebuchet-c0f03d96656a3c70a26d3ec97e9e782fbdbe64b9.tar.gz android_packages_apps_Trebuchet-c0f03d96656a3c70a26d3ec97e9e782fbdbe64b9.tar.bz2 android_packages_apps_Trebuchet-c0f03d96656a3c70a26d3ec97e9e782fbdbe64b9.zip |
Adding support for loading the default layout from a content provider
The autority of the provider should be set in secure settings:
launcher3.layout.provider
Bug: 127987071
Change-Id: Iccf2960aa6c0a5a8ff9621b13d8963d9daecb993
Diffstat (limited to 'tests')
3 files changed, 333 insertions, 0 deletions
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java index 0edb3d61b..fa23b8d5b 100644 --- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java +++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java @@ -18,6 +18,7 @@ package com.android.launcher3.testcomponent; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.PackageManager.DONT_KILL_APP; +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import android.app.Activity; import android.app.ActivityManager; @@ -28,6 +29,13 @@ import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.util.Base64; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import androidx.test.InstrumentationRegistry; @@ -104,4 +112,19 @@ public class TestCommandReceiver extends ContentProvider { Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands"); return inst.getTargetContext().getContentResolver().call(uri, command, arg, null); } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + String path = Base64.encodeToString(uri.getPath().getBytes(), + Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP); + File file = new File(getContext().getCacheDir(), path); + if (!file.exists()) { + // Create an empty file so that we can pass its descriptor + try { + file.createNewFile(); + } catch (IOException e) { } + } + + return ParcelFileDescriptor.open(file, MODE_READ_WRITE); + } } diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java new file mode 100644 index 000000000..1efdee8ef --- /dev/null +++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.launcher3.ui; + +import static org.junit.Assert.assertTrue; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; + +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.testcomponent.TestCommandReceiver; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.widget.LauncherAppWidgetHostView; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.OutputStreamWriter; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.UiSelector; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { + + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + + private static final String SETTINGS_APP = "com.android.settings"; + + private Context mContext; + private String mAuthority; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mContext = InstrumentationRegistry.getContext(); + + PackageManager pm = mTargetContext.getPackageManager(); + ProviderInfo pi = pm.getProviderInfo(new ComponentName(mContext, + TestCommandReceiver.class), 0); + mAuthority = pi.authority; + } + + @Test + public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { + writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP)); + + // Launch the home activity + mActivityMonitor.startLauncher(); + waitForModelLoaded(); + + // Verify widget present + UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) + .description(getSettingsApp().getLabel().toString()); + assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + } + + @Test + public void testCustomProfileLoaded_with_widget() throws Exception { + // A non-restored widget with no config screen gets restored automatically. + LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); + + writeLayout(new LauncherLayoutBuilder().atWorkspace(0, 1, 0) + .putWidget(info.getComponent().getPackageName(), + info.getComponent().getClassName(), 2, 2)); + + // Launch the home activity + mActivityMonitor.startLauncher(); + waitForModelLoaded(); + + // Verify widget present + UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) + .className(LauncherAppWidgetHostView.class).description(info.label); + assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + } + + @Test + public void testCustomProfileLoaded_with_folder() throws Exception { + writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) + .addApp(SETTINGS_APP, SETTINGS_APP) + .addApp(SETTINGS_APP, SETTINGS_APP) + .addApp(SETTINGS_APP, SETTINGS_APP) + .build()); + + // Launch the home activity + mActivityMonitor.startLauncher(); + waitForModelLoaded(); + + // Verify widget present + UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) + .descriptionContains(mTargetContext.getString(android.R.string.copy)); + assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + } + + @After + public void cleanup() throws Exception { + mDevice.executeShellCommand("settings delete secure launcher3.layout.provider"); + } + + private void writeLayout(LauncherLayoutBuilder builder) throws Exception { + mDevice.executeShellCommand("settings put secure launcher3.layout.provider " + mAuthority); + ParcelFileDescriptor pfd = mTargetContext.getContentResolver().openFileDescriptor( + Uri.parse("content://" + mAuthority + "/launcher_layout"), "w"); + + try (OutputStreamWriter writer = new OutputStreamWriter(new AutoCloseOutputStream(pfd))) { + builder.build(writer); + } + clearLauncherData(); + } +} diff --git a/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java new file mode 100644 index 000000000..d3659ebdc --- /dev/null +++ b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java @@ -0,0 +1,172 @@ +/** + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.launcher3.util; + + +import android.text.TextUtils; +import android.util.Pair; +import android.util.Xml; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Helper class to build xml for Launcher Layout + */ +public class LauncherLayoutBuilder { + + // Object Tags + private static final String TAG_WORKSPACE = "workspace"; + private static final String TAG_AUTO_INSTALL = "autoinstall"; + private static final String TAG_FOLDER = "folder"; + private static final String TAG_APPWIDGET = "appwidget"; + private static final String TAG_EXTRA = "extra"; + + private static final String ATTR_CONTAINER = "container"; + private static final String ATTR_RANK = "rank"; + + private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_CLASS_NAME = "className"; + private static final String ATTR_TITLE = "title"; + private static final String ATTR_SCREEN = "screen"; + + // x and y can be specified as negative integers, in which case -1 represents the + // last row / column, -2 represents the second last, and so on. + private static final String ATTR_X = "x"; + private static final String ATTR_Y = "y"; + private static final String ATTR_SPAN_X = "spanX"; + private static final String ATTR_SPAN_Y = "spanY"; + + private static final String ATTR_CHILDREN = "children"; + + + // Style attrs -- "Extra" + private static final String ATTR_KEY = "key"; + private static final String ATTR_VALUE = "value"; + + private static final String CONTAINER_DESKTOP = "desktop"; + private static final String CONTAINER_HOTSEAT = "hotseat"; + + private final ArrayList<Pair<String, HashMap<String, Object>>> mNodes = new ArrayList<>(); + + public Location atHotseat(int rank) { + Location l = new Location(); + l.items.put(ATTR_CONTAINER, CONTAINER_HOTSEAT); + l.items.put(ATTR_RANK, Integer.toString(rank)); + return l; + } + + public Location atWorkspace(int x, int y, int screen) { + Location l = new Location(); + l.items.put(ATTR_CONTAINER, CONTAINER_DESKTOP); + l.items.put(ATTR_X, Integer.toString(x)); + l.items.put(ATTR_Y, Integer.toString(y)); + l.items.put(ATTR_SCREEN, Integer.toString(screen)); + return l; + } + + public String build() throws IOException { + StringWriter writer = new StringWriter(); + build(writer); + return writer.toString(); + } + + public void build(Writer writer) throws IOException { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(writer); + + serializer.startDocument("UTF-8", true); + serializer.startTag(null, TAG_WORKSPACE); + writeNodes(serializer, mNodes); + serializer.endTag(null, TAG_WORKSPACE); + serializer.endDocument(); + serializer.flush(); + } + + private static void writeNodes(XmlSerializer serializer, + ArrayList<Pair<String, HashMap<String, Object>>> nodes) throws IOException { + for (Pair<String, HashMap<String, Object>> node : nodes) { + ArrayList<Pair<String, HashMap<String, Object>>> children = null; + + serializer.startTag(null, node.first); + for (Map.Entry<String, Object> attr : node.second.entrySet()) { + if (ATTR_CHILDREN.equals(attr.getKey())) { + children = (ArrayList<Pair<String, HashMap<String, Object>>>) attr.getValue(); + } else { + serializer.attribute(null, attr.getKey(), (String) attr.getValue()); + } + } + + if (children != null) { + writeNodes(serializer, children); + } + serializer.endTag(null, node.first); + } + } + + public class Location { + + final HashMap<String, Object> items = new HashMap<>(); + + public LauncherLayoutBuilder putApp(String packageName, String className) { + items.put(ATTR_PACKAGE_NAME, packageName); + items.put(ATTR_CLASS_NAME, TextUtils.isEmpty(className) ? packageName : className); + mNodes.add(Pair.create(TAG_AUTO_INSTALL, items)); + return LauncherLayoutBuilder.this; + } + + public LauncherLayoutBuilder putWidget(String packageName, String className, + int spanX, int spanY) { + items.put(ATTR_PACKAGE_NAME, packageName); + items.put(ATTR_CLASS_NAME, className); + items.put(ATTR_SPAN_X, Integer.toString(spanX)); + items.put(ATTR_SPAN_Y, Integer.toString(spanY)); + mNodes.add(Pair.create(TAG_APPWIDGET, items)); + return LauncherLayoutBuilder.this; + } + + public FolderBuilder putFolder(int titleResId) { + FolderBuilder folderBuilder = new FolderBuilder(); + items.put(ATTR_TITLE, Integer.toString(titleResId)); + items.put(ATTR_CHILDREN, folderBuilder.mChildren); + mNodes.add(Pair.create(TAG_FOLDER, items)); + return folderBuilder; + } + } + + public class FolderBuilder { + + final ArrayList<Pair<String, HashMap<String, Object>>> mChildren = new ArrayList<>(); + + public FolderBuilder addApp(String packageName, String className) { + HashMap<String, Object> items = new HashMap<>(); + items.put(ATTR_PACKAGE_NAME, packageName); + items.put(ATTR_CLASS_NAME, TextUtils.isEmpty(className) ? packageName : className); + mChildren.add(Pair.create(TAG_AUTO_INSTALL, items)); + return this; + } + + public LauncherLayoutBuilder build() { + return LauncherLayoutBuilder.this; + } + } +} |