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) {
+    }
+}