Migrating SettingsLib tests to /external/robolectic.

FragmentController was a hack added to Android's /external/robolectric-shadows.
Moving that debt to /framework/base sub repos.
Updating testutils to new robolectric restrictions.
Using the OverpowerReflectionHelpers where required.

(Cherrypicked from ag/23845147)

Bug: 261728063
Test: atest SettingsLibRoboTests
Change-Id: I8e6fcfdd70375a76e94d904adcbb27904938175c
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 5c55a43..c037c40 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -42,7 +42,10 @@
     name: "SettingsLibRoboTests",
     srcs: ["src/**/*.java"],
     static_libs: [
+        "Settings_robolectric_meta_service_file",
+        "Robolectric_shadows_androidx_fragment_upstream",
         "SettingsLib-robo-testutils",
+        "androidx.fragment_fragment",
         "androidx.test.core",
         "androidx.core_core",
         "testng", // TODO: remove once JUnit on Android provides assertThrows
@@ -53,6 +56,20 @@
     test_options: {
         timeout: 36000,
     },
+    upstream: true,
+}
+
+java_genrule {
+    name: "Settings_robolectric_meta_service_file",
+    out: ["robolectric_meta_service_file.jar"],
+    tools: ["soong_zip"],
+    cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
+        "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
 }
 
 java_library {
@@ -60,9 +77,23 @@
     srcs: [
         "testutils/com/android/settingslib/testutils/**/*.java",
     ],
-
+    javacflags: [
+        "-Aorg.robolectric.annotation.processing.shadowPackage=com.android.settingslib.testutils.shadow",
+        "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+        // Uncomment the below to debug annotation processors not firing.
+        //"-verbose",
+        //"-XprintRounds",
+        //"-XprintProcessorInfo",
+        //"-Xlint",
+        //"-J-verbose",
+    ],
+    plugins: [
+        "auto_value_plugin_1.9",
+        "auto_value_builder_plugin_1.9",
+        "Robolectric_processor_upstream",
+    ],
     libs: [
-        "Robolectric_all-target",
+        "Robolectric_all-target_upstream",
         "mockito-robolectric-prebuilt",
         "truth-prebuilt",
     ],
diff --git a/packages/SettingsLib/tests/robotests/config/robolectric.properties b/packages/SettingsLib/tests/robotests/config/robolectric.properties
index fab7251..2a9e50d 100644
--- a/packages/SettingsLib/tests/robotests/config/robolectric.properties
+++ b/packages/SettingsLib/tests/robotests/config/robolectric.properties
@@ -1 +1,2 @@
 sdk=NEWEST_SDK
+instrumentedPackages=androidx.preference
diff --git a/packages/SettingsLib/tests/robotests/fragment/Android.bp b/packages/SettingsLib/tests/robotests/fragment/Android.bp
new file mode 100644
index 0000000..3e67156
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/Android.bp
@@ -0,0 +1,40 @@
+//#############################################
+// Compile Robolectric shadows framework misapplied to androidx
+//#############################################
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+    name: "Robolectric_shadows_androidx_fragment_upstream",
+    srcs: [
+        "src/main/java/**/*.java",
+        "src/main/java/**/*.kt",
+    ],
+    javacflags: [
+        "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+        "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+        // Uncomment the below to debug annotation processors not firing.
+        //"-verbose",
+        //"-XprintRounds",
+        //"-XprintProcessorInfo",
+        //"-Xlint",
+        //"-J-verbose",
+    ],
+    libs: [
+        "Robolectric_all-target_upstream",
+        "androidx.fragment_fragment",
+    ],
+    plugins: [
+        "auto_value_plugin_1.9",
+        "auto_value_builder_plugin_1.9",
+        "Robolectric_processor_upstream",
+    ],
+
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/BUILD b/packages/SettingsLib/tests/robotests/fragment/BUILD
new file mode 100644
index 0000000..393a02e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/BUILD
@@ -0,0 +1,69 @@
+load("//third_party/java/android/android_sdk_linux/extras/android/compatibility/jetify:jetify.bzl", "jetify_android_library", "jetify_android_local_test")
+
+package(
+    default_applicable_licenses = ["//third_party/java_src/robolectric:license"],
+    default_visibility = ["//third_party/java_src/robolectric:__subpackages__"],
+)
+
+licenses(["notice"])
+
+#==============================================================================
+# Test resources library
+#==============================================================================
+jetify_android_library(
+    name = "test_resources",
+    custom_package = "org.robolectric.shadows.androidx.fragment",
+    manifest = "src/test/AndroidManifest.xml",
+    resource_files = glob(
+        ["src/test/resources/**/*"],
+    ),
+)
+
+#==============================================================================
+# AndroidX fragment module library
+#==============================================================================
+jetify_android_library(
+    name = "androidx_fragment",
+    testonly = 1,
+    srcs = glob(
+        ["src/main/java/**"],
+    ),
+    custom_package = "org.robolectric.shadows.androidx.fragment",
+    javacopts = [
+        "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+    ],
+    jetify_sources = True,
+    plugins = [
+        "//java/com/google/thirdparty/robolectric/processor",
+    ],
+    deps = [
+        "//third_party/java/androidx/core",
+        "//third_party/java/androidx/fragment",
+        "//third_party/java/androidx/lifecycle",
+        "//third_party/java_src/robolectric/shadowapi",
+        "//third_party/java_src/robolectric/shadows/framework",
+    ],
+)
+
+[
+    jetify_android_local_test(
+        name = "test_" + src.rstrip(".java"),
+        size = "small",
+        srcs = glob(
+            ["src/test/java/**/*.java"],
+        ),
+        jetify_sources = True,
+        deps = [
+            ":androidx_fragment",
+            ":test_resources",
+            "//third_party/java/androidx/fragment",
+            "//third_party/java/androidx/loader",
+            "//third_party/java/mockito",
+            "//third_party/java/robolectric",
+            "//third_party/java/truth",
+        ],
+    )
+    for src in glob(
+        ["src/test/java/**/*Test.java"],
+    )
+]
diff --git a/packages/SettingsLib/tests/robotests/fragment/build.gradle b/packages/SettingsLib/tests/robotests/fragment/build.gradle
new file mode 100644
index 0000000..d9dcd84
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/build.gradle
@@ -0,0 +1,48 @@
+plugins {
+    id "net.ltgt.errorprone" version "0.0.13"
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+
+    android {
+        sourceSets {
+            main {
+                res.srcDirs = ['src/test/resources/res']
+            }
+        }
+        testOptions {
+            unitTests {
+                includeAndroidResources = true
+            }
+        }
+    }
+}
+
+dependencies {
+    // Project dependencies
+    compileOnly project(":robolectric")
+
+    // Compile dependencies
+    compileOnly AndroidSdk.MAX_SDK.coordinates
+    compileOnly "androidx.core:core:1.0.0-rc02"
+    compileOnly 'androidx.fragment:fragment:1.0.0-rc02'
+    compileOnly "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+    compileOnly "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+
+    // Testing dependencies
+    testImplementation "com.google.truth:truth:0.44"
+    testImplementation "org.mockito:mockito-core:2.5.4"
+    testImplementation "androidx.arch.core:core-common:2.0.0-beta01"
+    testImplementation "androidx.arch.core:core-runtime:2.0.0-rc01"
+    testImplementation "androidx.collection:collection:1.0.0-rc01"
+    testImplementation "androidx.core:core:1.0.0-rc02"
+    testImplementation 'androidx.fragment:fragment:1.0.0-rc02'
+    testImplementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+    testImplementation "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+    testImplementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01"
+    testImplementation "androidx.lifecycle:lifecycle-livedata-core:2.0.0-rc01"
+    testImplementation "androidx.loader:loader:1.0.0-rc02"
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
new file mode 100644
index 0000000..c688683
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.android.controller.ComponentController;
+import org.robolectric.util.ReflectionHelpers;
+
+/** A Controller that can be used to drive the lifecycle of a {@link Fragment} */
+public class FragmentController<F extends Fragment>
+        extends ComponentController<FragmentController<F>, F> {
+
+    private final F mFragment;
+    private final ActivityController<? extends FragmentActivity> mActivityController;
+
+    private FragmentController(F fragment, Class<? extends FragmentActivity> activityClass) {
+        this(fragment, activityClass, null /*intent*/, null /*arguments*/);
+    }
+
+    private FragmentController(
+            F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+        this(fragment, activityClass, intent, null /*arguments*/);
+    }
+
+    private FragmentController(
+            F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+        this(fragment, activityClass, null /*intent*/, arguments);
+    }
+
+    private FragmentController(
+            F fragment,
+            Class<? extends FragmentActivity> activityClass,
+            Intent intent,
+            Bundle arguments) {
+        super(fragment, intent);
+        this.mFragment = fragment;
+        if (arguments != null) {
+            this.mFragment.setArguments(arguments);
+        }
+        this.mActivityController =
+                ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment.
+     *
+     * @param fragment the fragment which you'd like to drive lifecycle
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(F fragment) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment and intent.
+     *
+     * @param fragment the fragment which you'd like to drive lifecycle
+     * @param intent   the intent which will be retained by activity
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(F fragment, Intent intent) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class, intent);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment and arguments.
+     *
+     * @param fragment  the fragment which you'd like to drive lifecycle
+     * @param arguments the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(F fragment, Bundle arguments) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class, arguments);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment and activity class.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Class<? extends FragmentActivity> activityClass) {
+        return new FragmentController<>(fragment, activityClass);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, intent and arguments.
+     *
+     * @param fragment  the fragment which you'd like to drive lifecycle
+     * @param intent    the intent which will be retained by activity
+     * @param arguments the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Intent intent, Bundle arguments) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class, intent,
+                arguments);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, activity class and intent.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @param intent        the intent which will be retained by activity
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+        return new FragmentController<>(fragment, activityClass, intent);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, activity class and arguments.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @param arguments     the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+        return new FragmentController<>(fragment, activityClass, arguments);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, activity class, intent and
+     * arguments.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @param intent        the intent which will be retained by activity
+     * @param arguments     the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment,
+            Class<? extends FragmentActivity> activityClass,
+            Intent intent,
+            Bundle arguments) {
+        return new FragmentController<>(fragment, activityClass, intent, arguments);
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+     * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+     * added
+     * to the view with ID 1.
+     */
+    public static <F extends Fragment> F setupFragment(F fragment) {
+        return FragmentController.of(fragment).create().start().resume().visible().get();
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+     * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+     * added
+     * to the view with ID 1.
+     */
+    public static <F extends Fragment> F setupFragment(
+            F fragment, Class<? extends FragmentActivity> fragmentActivityClass) {
+        return FragmentController.of(fragment, fragmentActivityClass)
+                .create()
+                .start()
+                .resume()
+                .visible()
+                .get();
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity created with the given bundle,
+     * calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note
+     * that the fragment will be added to the view with ID 1.
+     */
+    public static <F extends Fragment> F setupFragment(
+            F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) {
+        return FragmentController.of(fragment, fragmentActivityClass)
+                .create(bundle)
+                .start()
+                .resume()
+                .visible()
+                .get();
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity created with the given bundle and
+     * container id, calling its onCreate() through onResume() lifecycle methods, and then making it
+     * visible.
+     */
+    public static <F extends Fragment> F setupFragment(
+            F fragment,
+            Class<? extends FragmentActivity> fragmentActivityClass,
+            int containerViewId,
+            Bundle bundle) {
+        return FragmentController.of(fragment, fragmentActivityClass)
+                .create(containerViewId, bundle)
+                .start()
+                .resume()
+                .visible()
+                .get();
+    }
+
+    /**
+     * Creates the activity with {@link Bundle} and adds the fragment to the view with ID {@code
+     * contentViewId}.
+     */
+    public FragmentController<F> create(final int contentViewId, final Bundle bundle) {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController
+                                .create(bundle)
+                                .get()
+                                .getSupportFragmentManager()
+                                .beginTransaction()
+                                .add(contentViewId, mFragment)
+                                .commit();
+                    }
+                });
+        return this;
+    }
+
+    /**
+     * Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment
+     * will be added to the view with ID 1.
+     */
+    public FragmentController<F> create(final Bundle bundle) {
+        return create(1, bundle);
+    }
+
+    /**
+     * Creates the {@link Fragment} in a newly initialized state and hence will receive a null
+     * savedInstanceState {@link Bundle parameter}
+     */
+    @Override
+    public FragmentController<F> create() {
+        return create(null);
+    }
+
+    /** Drive lifecycle of activity to Start lifetime */
+    public FragmentController<F> start() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.start();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Resume lifetime */
+    public FragmentController<F> resume() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.resume();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Pause lifetime */
+    public FragmentController<F> pause() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.pause();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Stop lifetime */
+    public FragmentController<F> stop() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.stop();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Destroy lifetime */
+    @Override
+    public FragmentController<F> destroy() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.destroy();
+                    }
+                });
+        return this;
+    }
+
+    /** Let activity can be visible lifetime */
+    public FragmentController<F> visible() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.visible();
+                    }
+                });
+        return this;
+    }
+
+    private static class FragmentControllerActivity extends FragmentActivity {
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LinearLayout view = new LinearLayout(this);
+            view.setId(1);
+
+            setContentView(view);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
new file mode 100644
index 0000000..dd89441
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+/**
+ * Testing infrastructure for androidx.fragment library.
+ *
+ * <p>To use this in your project, add the artifact {@code
+ * org.robolectric:shadows-androidx-fragment} to your project.
+ */
+package org.robolectric.shadows.androidx.fragment;
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..8493c02
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.robolectric.shadows.androidx.fragment">
+
+    <uses-sdk android:targetSdkVersion="28"/>
+</manifest>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
new file mode 100644
index 0000000..ef63058
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link FragmentController} */
+@RunWith(RobolectricTestRunner.class)
+public class FragmentControllerTest {
+
+    @After
+    public void tearDown() {
+        TranscriptFragment.clearLifecycleEvents();
+    }
+
+    @Test
+    public void initialNotAttached() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment());
+
+        assertThat(controller.get().getView()).isNull();
+        assertThat(controller.get().getActivity()).isNull();
+        assertThat(controller.get().isAdded()).isFalse();
+    }
+
+    @Test
+    public void initialNotAttached_customActivity() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        assertThat(controller.get().getView()).isNull();
+        assertThat(controller.get().getActivity()).isNull();
+        assertThat(controller.get().isAdded()).isFalse();
+    }
+
+    @Test
+    public void attachedAfterCreate() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment());
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+    }
+
+    @Test
+    public void attachedAfterCreate_customActivity() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().getActivity()).isInstanceOf(TestActivity.class);
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+    }
+
+    @Test
+    public void attachedAfterCreate_customizedViewId() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), CustomizedViewIdTestActivity.class);
+
+        controller.create(R.id.custom_activity_view, null).start();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+        assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+    }
+
+    @Test
+    public void hasViewAfterStart() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment());
+
+        controller.create().start();
+
+        assertThat(controller.get().getView()).isNotNull();
+    }
+
+    @Test
+    public void isResumed() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isTrue();
+        assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+    }
+
+    @Test
+    public void isPaused() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume().pause();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+        assertThat(controller.get().getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume", "onPause")
+                .inOrder();
+    }
+
+    @Test
+    public void isStopped() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume().pause().stop();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+        assertThat(controller.get().getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume", "onPause", "onStop")
+                .inOrder();
+    }
+
+    @Test
+    public void withIntent() {
+        final Intent intent = generateTestIntent();
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class, intent);
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+        final Intent intentInFragment = controller.get().getActivity().getIntent();
+
+        assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+        assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+    }
+
+    @Test
+    public void withArguments() {
+        final Bundle bundle = generateTestBundle();
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class, bundle);
+
+        controller.create();
+        final Bundle args = controller.get().getArguments();
+
+        assertThat(args.getString("test_key")).isEqualTo("test_value");
+    }
+
+    @Test
+    public void withIntentAndArguments() {
+        final Bundle bundle = generateTestBundle();
+        final Intent intent = generateTestIntent();
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class, intent, bundle);
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+        final Intent intentInFragment = controller.get().getActivity().getIntent();
+        final Bundle args = controller.get().getArguments();
+
+        assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+        assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+        assertThat(args.getString("test_key")).isEqualTo("test_value");
+    }
+
+    @Test
+    public void visible() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume();
+
+        assertThat(controller.get().isVisible()).isFalse();
+
+        controller.visible();
+
+        assertThat(controller.get().isVisible()).isTrue();
+    }
+
+    @Test
+    public void setupFragmentWithFragment_fragmentHasCorrectLifecycle() {
+        TranscriptFragment fragment = FragmentController.setupFragment(new TranscriptFragment());
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    @Test
+    public void setupFragmentWithFragmentAndActivity_fragmentHasCorrectLifecycle() {
+        TranscriptFragment fragment =
+                FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class);
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    @Test
+    public void setupFragmentWithFragmentAndActivityAndBundle_HasCorrectLifecycle() {
+        Bundle testBundle = generateTestBundle();
+        TranscriptFragment fragment =
+                FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class,
+                        testBundle);
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    @Test
+    public void
+            setupFragmentWithFragment_Activity_ContainViewIdAndBundle_HasCorrectLifecycle() {
+        Bundle testBundle = generateTestBundle();
+        TranscriptFragment fragment =
+                FragmentController.setupFragment(
+                        new TranscriptFragment(),
+                        CustomizedViewIdTestActivity.class,
+                        R.id.custom_activity_view,
+                        testBundle);
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    private Intent generateTestIntent() {
+        final Intent testIntent = new Intent("test_action").putExtra("test_key", "test_value");
+        return testIntent;
+    }
+
+    private Bundle generateTestBundle() {
+        final Bundle testBundle = new Bundle();
+        testBundle.putString("test_key", "test_value");
+
+        return testBundle;
+    }
+
+    /** A Fragment which can record lifecycle status for test. */
+    public static class TranscriptFragment extends Fragment {
+
+        public static final List<String> sLifecycleEvents = new ArrayList<>();
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            sLifecycleEvents.add("onCreate");
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            sLifecycleEvents.add("onStart");
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+            sLifecycleEvents.add("onResume");
+        }
+
+        @Override
+        public void onPause() {
+            super.onPause();
+            sLifecycleEvents.add("onPause");
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+            sLifecycleEvents.add("onStop");
+        }
+
+        @Override
+        public View onCreateView(
+                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            return inflater.inflate(R.layout.fragment_contents, container, false);
+        }
+
+        public List<String> getLifecycleEvents() {
+            return sLifecycleEvents;
+        }
+
+        public static void clearLifecycleEvents() {
+            sLifecycleEvents.clear();
+        }
+    }
+
+    /** A Activity which set a default view for test. */
+    public static class TestActivity extends FragmentActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LinearLayout view = new LinearLayout(this);
+            view.setId(1);
+
+            setContentView(view);
+        }
+    }
+
+    /** A Activity which has a custom view for test. */
+    public static class CustomizedViewIdTestActivity extends FragmentActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.custom_activity_view);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
new file mode 100644
index 0000000..c074f30
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/custom_activity_view"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
new file mode 100644
index 0000000..425b2bb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+  <TextView
+      android:id="@+id/tacos"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="TACOS"/>
+
+  <TextView
+      android:id="@+id/burritos"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="BURRITOS"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 4a913c8..bb72375 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -58,12 +57,10 @@
 import org.robolectric.shadows.ShadowSettings;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {UtilsTest.ShadowSecure.class, UtilsTest.ShadowLocationManager.class})
+@Config(shadows = {UtilsTest.ShadowLocationManager.class})
 public class UtilsTest {
     private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
     private static final String TAG = "UtilsTest";
@@ -94,7 +91,7 @@
         mContext = spy(RuntimeEnvironment.application);
         when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
         when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
-        ShadowSecure.reset();
+        ShadowSettings.ShadowSecure.reset();
         mAudioManager = mContext.getSystemService(AudioManager.class);
     }
 
@@ -111,15 +108,16 @@
                 Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
 
         assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN))
-                .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+                Settings.Secure.LOCATION_CHANGER,
+                Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
+                Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
     }
 
     @Test
     public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
-        final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1,
-                PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50,
-                PERCENTAGE_100};
+        final String[] expectedPercentages =
+                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
+                        PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -129,9 +127,9 @@
 
     @Test
     public void testFormatPercentage_RoundFalse_NoRound() {
-        final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0,
-                PERCENTAGE_0, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50,
-                PERCENTAGE_100};
+        final String[] expectedPercentages =
+                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
+                        PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -143,12 +141,7 @@
     public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
         Resources resources = mock(Resources.class);
         when(resources.getInteger(
-                eq(
-                        com.android
-                                .internal
-                                .R
-                                .integer
-                                .config_storageManagerDaystoRetainDefault)))
+                eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
                 .thenReturn(60);
         assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
     }
@@ -163,31 +156,6 @@
         return intent -> TextUtils.equals(expected, intent.getAction());
     }
 
-    @Implements(value = Settings.Secure.class)
-    public static class ShadowSecure extends ShadowSettings.ShadowSecure {
-        private static Map<String, Integer> map = new HashMap<>();
-
-        @Implementation
-        public static boolean putIntForUser(ContentResolver cr, String name, int value,
-                int userHandle) {
-            map.put(name, value);
-            return true;
-        }
-
-        @Implementation
-        public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
-            if (map.containsKey(name)) {
-                return map.get(name);
-            } else {
-                return def;
-            }
-        }
-
-        public static void reset() {
-            map.clear();
-        }
-    }
-
     @Implements(value = LocationManager.class)
     public static class ShadowLocationManager {
 
@@ -337,9 +305,8 @@
 
     @Test
     public void getBatteryStatus_statusIsFull_returnFullString() {
-        final Intent intent = new Intent()
-                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
-                .putExtra(BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+                BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -348,9 +315,8 @@
 
     @Test
     public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
-        final Intent intent = new Intent()
-                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
-                .putExtra(BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+                BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
@@ -516,7 +482,6 @@
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings())
-                .thenReturn(new int[]{complianceWarningType});
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
index 44fdaec..3de8446 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
@@ -23,13 +23,17 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 
+import com.android.settingslib.testutils.shadow.ShadowSecure;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSecure.class})
 public class AccessibilityUtilsTest {
 
     private Context mContext;
@@ -46,7 +50,7 @@
 
     @Test
     public void getEnabledServicesFromSettings_badFormat_emptyResult() {
-        Settings.Secure.putStringForUser(
+        ShadowSecure.putStringForUser(
                 mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 ":",
                 UserHandle.myUserId());
@@ -57,7 +61,7 @@
     @Test
     public void getEnabledServicesFromSettings_1Service_1result() {
         final ComponentName cn = new ComponentName("pkg", "serv");
-        Settings.Secure.putStringForUser(
+        ShadowSecure.putStringForUser(
                 mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 cn.flattenToString() + ":",
                 UserHandle.myUserId());
@@ -70,7 +74,7 @@
     public void getEnabledServicesFromSettings_2Services_2results() {
         final ComponentName cn1 = new ComponentName("pkg", "serv");
         final ComponentName cn2 = new ComponentName("pkg", "serv2");
-        Settings.Secure.putStringForUser(
+        ShadowSecure.putStringForUser(
                 mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 cn1.flattenToString() + ":" + cn2.flattenToString(),
                 UserHandle.myUserId());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
index cb62a73..f9505dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
@@ -37,6 +37,8 @@
 import android.os.UserManager;
 import android.util.LongSparseArray;
 
+import com.android.settingslib.testutils.shadow.ShadowPermissionChecker;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +47,6 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowPermissionChecker;
 
 import java.time.Clock;
 import java.util.ArrayList;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index dd8d54a..a2e8c59 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -38,6 +38,7 @@
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
@@ -167,6 +168,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void getAttribution_notSet_shouldReturnUnknown() {
         final Activity activity = Robolectric.setupActivity(Activity.class);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index d67d44b..25833b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.CujType;
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
 import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
 
 import org.junit.Before;
@@ -51,7 +52,6 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
-import org.robolectric.util.ReflectionHelpers;
 
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -83,8 +83,10 @@
     public void setUp() {
         ShadowInteractionJankMonitor.reset();
         when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true);
-        ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService",
-                mScheduledExecutorService);
+        OverpoweredReflectionHelper
+                .setStaticField(SettingsJankMonitor.class,
+                        "scheduledExecutorService",
+                        mScheduledExecutorService);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index cf702b53..471dac0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -37,8 +37,10 @@
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.LooperMode;
 
 @RunWith(RobolectricTestRunner.class)
+@LooperMode(LooperMode.Mode.PAUSED)
 public class HideNonSystemOverlayMixinTest {
 
     private ActivityController<TestActivity> mActivityController;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
index 3475ff7..b009abd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
@@ -22,13 +22,14 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import com.android.settingslib.testutils.shadow.ShadowUserManager;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowUserManager;
 
 @RunWith(RobolectricTestRunner.class)
 public class DevelopmentSettingsEnablerTest {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 8e33ca3..0cabab2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -21,6 +21,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
@@ -269,6 +270,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void testGenerateHtmlWithCustomHeading() throws Exception {
         List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
@@ -292,6 +294,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void testGenerateNewHtmlWithCustomHeading() throws Exception {
         List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
new file mode 100644
index 0000000..9e9725f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@LooperMode(LooperMode.Mode.LEGACY)
+package com.android.settingslib;
+
+import org.robolectric.annotation.LooperMode;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
index d41d511..faec02f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
@@ -27,6 +27,7 @@
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
 
 @RunWith(RobolectricTestRunner.class)
 public class AnimatedImageViewTest {
@@ -40,6 +41,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void testAnimation_ViewVisible_AnimationRunning() {
         mAnimatedImageView.setVisibility(View.VISIBLE);
         mAnimatedImageView.setAnimating(true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index 0a48f19..0d88913 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -41,6 +41,8 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.R;
 
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -502,14 +504,18 @@
     private void assumeAndroidR() {
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30);
         ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R");
-        ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
+        OverpoweredReflectionHelper
+                .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
         // Reset view holder to use correct layout.
     }
 
+
+
     private void assumeAndroidS() {
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31);
         ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S");
-        ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
+        OverpoweredReflectionHelper
+                .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
         // Re-inflate view to update layout.
         setUpViewHolder();
     }
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
new file mode 100644
index 0000000..4fcc5a1
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils;
+
+import org.robolectric.util.ReflectionHelpers;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class OverpoweredReflectionHelper extends ReflectionHelpers {
+
+    /**
+     * Robolectric upstream does not rely on or encourage this behaviour.
+     *
+     * @param field
+     */
+    private static void makeFieldVeryAccessible(Field field) {
+        field.setAccessible(true);
+        // remove 'final' modifier if present
+        if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+            Field modifiersField = getModifiersField();
+            modifiersField.setAccessible(true);
+            try {
+                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+            } catch (IllegalAccessException e) {
+
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    private static Field getModifiersField() {
+        try {
+            return Field.class.getDeclaredField("modifiers");
+        } catch (NoSuchFieldException e) {
+            try {
+                Method getFieldsMethod =
+                        Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
+                getFieldsMethod.setAccessible(true);
+                Field[] fields = (Field[]) getFieldsMethod.invoke(Field.class, false);
+                for (Field modifiersField : fields) {
+                    if ("modifiers".equals(modifiersField.getName())) {
+                        return modifiersField;
+                    }
+                }
+            } catch (ReflectiveOperationException innerE) {
+                throw new AssertionError(innerE);
+            }
+        }
+        throw new AssertionError();
+    }
+
+    /**
+     * Reflectively set the value of a static field.
+     *
+     * @param field Field object.
+     * @param fieldNewValue The new value.
+     */
+    public static void setStaticField(Field field, Object fieldNewValue) {
+        try {
+            makeFieldVeryAccessible(field);
+            field.setAccessible(true);
+            field.set(null, fieldNewValue);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Reflectively set the value of a static field.
+     *
+     * @param clazz Target class.
+     * @param fieldName The field name.
+     * @param fieldNewValue The new value.
+     */
+    public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
+        try {
+            setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
index 924eb04..0b9ba8d 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -16,23 +16,27 @@
 
 package com.android.settingslib.testutils.shadow;
 
+import static android.os.Build.VERSION_CODES.O;
+
 import android.app.ActivityManager;
+import android.app.IActivityManager;
 
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
 import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
 
 @Implements(ActivityManager.class)
 public class ShadowActivityManager {
     private static int sCurrentUserId = 0;
-    private int mUserSwitchedTo = -1;
+    private static int sUserSwitchedTo = -1;
 
     @Resetter
-    public void reset() {
+    public static void reset() {
         sCurrentUserId = 0;
-        mUserSwitchedTo = 0;
+        sUserSwitchedTo = 0;
     }
 
     @Implementation
@@ -42,16 +46,21 @@
 
     @Implementation
     protected boolean switchUser(int userId) {
-        mUserSwitchedTo = userId;
+        sUserSwitchedTo = userId;
         return true;
     }
 
+    @Implementation(minSdk = O)
+    protected static IActivityManager getService() {
+        return ReflectionHelpers.createNullProxy(IActivityManager.class);
+    }
+
     public boolean getSwitchUserCalled() {
-        return mUserSwitchedTo != -1;
+        return sUserSwitchedTo != -1;
     }
 
     public int getUserSwitchedTo() {
-        return mUserSwitchedTo;
+        return sUserSwitchedTo;
     }
 
     public static void setCurrentUser(int userId) {
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index 2c0792f..bbfdb7f 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -29,7 +29,7 @@
     private static String sDefaultDialer;
 
     @Resetter
-    public void reset() {
+    public static void reset() {
         sDefaultDialer = null;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
new file mode 100644
index 0000000..fae3aea
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils.shadow;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.HashMap;
+import java.util.Map;
+/** Shadow class of {@link PermissionChecker}. */
+@Implements(PermissionChecker.class)
+public class ShadowPermissionChecker {
+    private static final Map<String, Map<String, Integer>> RESULTS = new HashMap<>();
+    /** Set the result of permission check for a specific permission. */
+    public static void setResult(String packageName, String permission, int result) {
+        if (!RESULTS.containsKey(packageName)) {
+            RESULTS.put(packageName, new HashMap<>());
+        }
+        RESULTS.get(packageName).put(permission, result);
+    }
+    /** Check the permission of calling package. */
+    @Implementation
+    public static int checkCallingPermissionForDataDelivery(
+            Context context,
+            String permission,
+            String packageName,
+            String attributionTag,
+            String message) {
+        return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+                ? RESULTS.get(packageName).get(permission)
+                : PermissionChecker.checkCallingPermissionForDataDelivery(
+                        context, permission, packageName, attributionTag, message);
+    }
+    /** Check general permission. */
+    @Implementation
+    public static int checkPermissionForDataDelivery(
+            Context context,
+            String permission,
+            int pid,
+            int uid,
+            String packageName,
+            String attributionTag,
+            String message) {
+        return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+                ? RESULTS.get(packageName).get(permission)
+                : PermissionChecker.checkPermissionForDataDelivery(
+                        context, permission, pid, uid, packageName, attributionTag, message);
+    }
+    /** Check general permission. */
+    @Implementation
+    public static int checkPermissionForPreflight(@NonNull Context context,
+            @NonNull String permission, int pid, int uid, @Nullable String packageName) {
+        return checkPermissionForPreflight(context, permission, new AttributionSource(
+                uid, packageName, null /*attributionTag*/));
+    }
+    /** Check general permission. */
+    @Implementation
+    public static int checkPermissionForPreflight(@NonNull Context context,
+            @NonNull String permission, @NonNull AttributionSource attributionSource) {
+        final String packageName = attributionSource.getPackageName();
+        return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+                ? RESULTS.get(packageName).get(permission)
+                : PermissionChecker.checkPermissionForPreflight(
+                        context, permission, attributionSource);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
new file mode 100644
index 0000000..70ebc67
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils.shadow;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+@Implements(value = Settings.Secure.class)
+public class ShadowSecure extends ShadowSettings.ShadowSecure {
+    @Implementation(minSdk = JELLY_BEAN_MR1)
+    public static boolean putStringForUser(ContentResolver cr, String name, String value,
+            int userHandle) {
+        return putString(cr, name, value);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
index 381d072..5ac0a87 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
@@ -31,7 +31,7 @@
     private static ComponentName sDefaultSmsApplication;
 
     @Resetter
-    public void reset() {
+    public static void reset() {
         sDefaultSmsApplication = null;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
index ca1eefc..60d7721 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -16,20 +16,28 @@
 
 package com.android.settingslib.testutils.shadow;
 
+import static android.os.Build.VERSION_CODES.N_MR1;
+
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBuild;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Implements(value = UserManager.class)
 public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
     private List<UserInfo> mUserInfos = addProfile(0, "Owner");
+    private final Map<Integer, UserProperties> mUserPropertiesMap = new HashMap<>();
 
     @Implementation
     protected static UserManager get(Context context) {
@@ -62,4 +70,37 @@
     protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
         return getProfiles();
     }
+
+    /**
+     * @return {@code false} by default, or the value specified via {@link #setIsAdminUser(boolean)}
+     */
+    @Implementation(minSdk = N_MR1)
+    public boolean isAdminUser() {
+        return getUserInfo(UserHandle.myUserId()).isAdmin();
+    }
+
+    /**
+     * Sets that the current user is an admin user; controls the return value of
+     * {@link UserManager#isAdminUser}.
+     */
+    public void setIsAdminUser(boolean isAdminUser) {
+        UserInfo userInfo = getUserInfo(UserHandle.myUserId());
+        if (isAdminUser) {
+            userInfo.flags |= UserInfo.FLAG_ADMIN;
+        } else {
+            userInfo.flags &= ~UserInfo.FLAG_ADMIN;
+        }
+    }
+
+    public void setupUserProperty(int userId, int showInSettings) {
+        UserProperties userProperties = new UserProperties(new UserProperties.Builder()
+                .setShowInSettings(showInSettings).build());
+        mUserPropertiesMap.putIfAbsent(userId, userProperties);
+    }
+
+    @Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE)
+    protected UserProperties getUserProperties(UserHandle user) {
+        return mUserPropertiesMap.getOrDefault(user.getIdentifier(),
+            new UserProperties(new UserProperties.Builder().build()));
+    }
 }