Merge "Makes it possible to robo-test Settings app fragments."
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index c1b8cf5..0c539d8 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -87,4 +87,5 @@
com.android.settings.notification.NotificationAccessSettings
com.android.settings.notification.ZenModeSettings
com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment
-com.android.settings.applications.ConvertToFbe
\ No newline at end of file
+com.android.settings.applications.ConvertToFbe
+com.android.settings.localepicker.LocaleListEditor
\ No newline at end of file
diff --git a/tests/robotests/src/android/print/PrintServicesLoader.java b/tests/robotests/src/android/print/PrintServicesLoader.java
new file mode 100644
index 0000000..e4975ed
--- /dev/null
+++ b/tests/robotests/src/android/print/PrintServicesLoader.java
@@ -0,0 +1,19 @@
+package android.print;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Loader;
+import android.printservice.PrintServiceInfo;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
+ */
+public class PrintServicesLoader extends Loader<List<PrintServiceInfo>> {
+ public PrintServicesLoader(@NonNull PrintManager printManager, @NonNull Context context,
+ int selectionFlags) {
+ super(Preconditions.checkNotNull(context));
+ }
+}
diff --git a/tests/robotests/src/com/android/internal/app/LocalePickerWithRegion.java b/tests/robotests/src/com/android/internal/app/LocalePickerWithRegion.java
new file mode 100644
index 0000000..9edda45
--- /dev/null
+++ b/tests/robotests/src/com/android/internal/app/LocalePickerWithRegion.java
@@ -0,0 +1,11 @@
+package com.android.internal.app;
+
+/**
+ * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
+ */
+public class LocalePickerWithRegion {
+
+ public interface LocaleSelectedListener {
+ void onLocaleSelected(LocaleStore.LocaleInfo locale);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java b/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java
index 9127d5f..4472025 100644
--- a/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java
+++ b/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java
@@ -15,15 +15,23 @@
*/
package com.android.settings;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.Fs;
import org.robolectric.res.ResourcePath;
+import org.robolectric.util.ActivityController;
+import org.robolectric.util.ReflectionHelpers;
import java.util.List;
+import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
+import static org.robolectric.Robolectric.getShadowsAdapter;
+
/**
* Custom test runner for the testing of BluetoothPairingDialogs. This is needed because the
* default behavior for robolectric is just to grab the resource directory in the target package.
@@ -77,4 +85,15 @@
manifest.setPackageName("com.android.settings");
return manifest;
}
-}
\ No newline at end of file
+
+ // A simple utility class to start a Settings fragment with an intent. The code here is almost
+ // the same as FragmentTestUtil.startFragment except that it starts an activity with an intent.
+ public static void startSettingsFragment(
+ Fragment fragment, Class<? extends SettingsActivity> activityClass) {
+ Intent intent = new Intent().putExtra(EXTRA_SHOW_FRAGMENT, fragment.getClass().getName());
+ SettingsActivity activity = ActivityController.of(
+ getShadowsAdapter(), ReflectionHelpers.callConstructor(activityClass), intent)
+ .setup().get();
+ activity.getFragmentManager().beginTransaction().add(fragment, null).commit();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java b/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java
new file mode 100644
index 0000000..e49b8b8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java
@@ -0,0 +1,61 @@
+package com.android.settings.applications;
+
+import android.os.Looper;
+import android.os.UserManager;
+import com.android.settings.Settings;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme;
+import com.android.settingslib.applications.ApplicationsState;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link ManageApplications}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+// TODO: Consider making the shadow class set global using a robolectric.properties file.
+@Config(manifest = TestConfig.MANIFEST_PATH,
+ sdk = TestConfig.SDK_VERSION,
+ shadows = {
+ SettingsShadowResources.class,
+ SettingsShadowTheme.class,
+ ShadowDynamicIndexableContentMonitor.class
+ })
+public class ManageApplicationsTest {
+
+ @Mock private ApplicationsState mState;
+ @Mock private ApplicationsState.Session mSession;
+ @Mock private UserManager mUserManager;
+
+ private Looper mBgLooper;
+
+ private ManageApplications mFragment;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mState);
+ when(mState.newSession(any())).thenReturn(mSession);
+ mBgLooper = Looper.myLooper();
+ when(mState.getBackgroundLooper()).thenReturn(mBgLooper);
+
+ mFragment = new ManageApplications();
+ }
+
+ @Test
+ public void launchFragment() {
+ SettingsRobolectricTestRunner.startSettingsFragment(
+ mFragment, Settings.ManageApplicationsActivity.class);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java
new file mode 100644
index 0000000..51a187b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java
@@ -0,0 +1,126 @@
+package com.android.settings.testutils.shadow;
+
+import android.annotation.DimenRes;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.res.StyleData;
+import org.robolectric.res.StyleResolver;
+import org.robolectric.res.builder.XmlResourceParserImpl;
+import org.robolectric.shadows.ShadowAssetManager;
+import org.robolectric.shadows.ShadowResources;
+import org.robolectric.util.ReflectionHelpers;
+import org.w3c.dom.Node;
+
+import java.util.List;
+import java.util.Map;
+
+import static android.util.TypedValue.TYPE_REFERENCE;
+import static org.robolectric.Shadows.shadowOf;
+import static org.robolectric.internal.Shadow.directlyOn;
+
+/**
+ * Shadow Resources and Theme classes to handle resource references that Robolectric shadows cannot
+ * handle because they are too new or private.
+ */
+@Implements(Resources.class)
+public class SettingsShadowResources extends ShadowResources {
+
+ @RealObject Resources realResources;
+
+ @Implementation
+ public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
+ // Handle requests for private dimension resources,
+ // TODO: Consider making a set of private dimension resource ids if this happens repeatedly.
+ if (id == com.android.internal.R.dimen.preference_fragment_padding_bottom) {
+ return 0;
+ }
+ return directlyOn(realResources, Resources.class).getDimensionPixelSize(id);
+ }
+
+ @Implementation
+ public Drawable loadDrawable(TypedValue value, int id, Theme theme)
+ throws NotFoundException {
+ // The drawable item in switchbar_background.xml refers to a very recent color attribute
+ // that Robolectric isn't yet aware of.
+ // TODO: Remove this once Robolectric is updated.
+ if (id == com.android.settings.R.drawable.switchbar_background) {
+ return new ColorDrawable();
+ }
+ return super.loadDrawable(value, id, theme);
+ }
+
+ @Implements(Theme.class)
+ public static class SettingsShadowTheme extends ShadowTheme {
+
+ @RealObject
+ Theme realTheme;
+
+ @Implementation
+ public TypedArray obtainStyledAttributes(
+ AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
+ // Replace all private string references with a placeholder.
+ if (set != null) {
+ for (int i = 0; i < set.getAttributeCount(); ++i) {
+ if (set.getAttributeValue(i).startsWith("@*android:string")) {
+ Node node = ReflectionHelpers.callInstanceMethod(
+ XmlResourceParserImpl.class, set, "getAttributeAt",
+ ReflectionHelpers.ClassParameter.from(int.class, i));
+ node.setNodeValue("PLACEHOLDER");
+ }
+ }
+ }
+
+ // Track down all styles and remove all inheritance from private styles.
+ ShadowAssetManager assetManager = shadowOf(RuntimeEnvironment.application.getAssets());
+ // The Object's below are actually ShadowAssetManager.OverlayedStyle. We can't use it
+ // here because it's package private.
+ Map<Long, List<Object>> appliedStylesList =
+ ReflectionHelpers.getField(assetManager, "appliedStyles");
+ for (Long idx : appliedStylesList.keySet()) {
+ List<Object> appliedStyles = appliedStylesList.get(idx);
+ int i = 1;
+ for (Object appliedStyle : appliedStyles) {
+ StyleResolver styleResolver = ReflectionHelpers.getField(appliedStyle, "style");
+ List<StyleData> styleDatas =
+ ReflectionHelpers.getField(styleResolver, "styles");
+ for (StyleData styleData : styleDatas) {
+ if (styleData.getParent() != null &&
+ styleData.getParent().startsWith("@*android:style")) {
+ ReflectionHelpers.setField(StyleData.class, styleData, "parent", null);
+ }
+ }
+ }
+
+ }
+ return super.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Implementation
+ public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
+ // The real Resources instance in Robolectric tests somehow fails to find the
+ // preferenceTheme attribute in the layout. Let's do it ourselves.
+ if (getResources().getResourceName(resid)
+ .equals("com.android.settings:attr/preferenceTheme")) {
+ int preferenceThemeResId =
+ getResources().getIdentifier(
+ "PreferenceTheme", "style", "com.android.settings");
+ outValue.type = TYPE_REFERENCE;
+ outValue.data = preferenceThemeResId;
+ outValue.resourceId = preferenceThemeResId;
+ return true;
+ }
+ return directlyOn(realTheme, Theme.class)
+ .resolveAttribute(resid, outValue, resolveRefs);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDynamicIndexableContentMonitor.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDynamicIndexableContentMonitor.java
new file mode 100644
index 0000000..de5d243
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDynamicIndexableContentMonitor.java
@@ -0,0 +1,22 @@
+package com.android.settings.testutils.shadow;
+
+import android.app.Activity;
+import android.os.UserManager;
+import com.android.settings.search.DynamicIndexableContentMonitor;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+/**
+ * A shadow class of {@link DynamicIndexableContentMonitor}. The real implementation of
+ * {@link DynamicIndexableContentMonitor#register} calls {@link UserManager#isUserUnlocked()}, which
+ * Robolectric has not yet been updated to support, so throws a NoSuchMethodError exception.
+ */
+// TODO: Delete this once Robolectric is updated to the latest SDK.
+@Implements(DynamicIndexableContentMonitor.class)
+public class ShadowDynamicIndexableContentMonitor {
+
+ @Implementation
+ public void register(Activity activity, int loaderId) {
+ }
+}