[DO NOT MERGE] FRP bypass defense in the settings app

Over the last few years, there have been a number of
Factory Reset Protection bypass bugs in the SUW flow.
It's unlikely to defense all points from individual apps.

Therefore, we decide to block some critical pages when
user doesn't complete the SUW flow.

Test: Can't open the certain pages in the suw flow.
Bug: 258422561
Fix: 200746457
Bug: 202975040
Fix: 213091525
Fix: 213090835
Fix: 201561699
Fix: 213090827
Fix: 213090875
Change-Id: Ia18f367109df5af7da0a5acad7702898a459d32e
Merged-In: Ia18f367109df5af7da0a5acad7702898a459d32e
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index 659ada4..92df759 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -55,6 +55,8 @@
 import com.android.settingslib.search.Indexable;
 import com.android.settingslib.widget.LayoutPreference;
 
+import com.google.android.setupcompat.util.WizardManagerHelper;
+
 import java.util.UUID;
 
 /**
@@ -63,7 +65,7 @@
 public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
         implements DialogCreatable, HelpResourceProvider, Indexable {
 
-    private static final String TAG = "SettingsPreference";
+    private static final String TAG = "SettingsPreferenceFragment";
 
     private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
 
@@ -124,6 +126,15 @@
     public boolean mPreferenceHighlighted = false;
 
     @Override
+    public void onAttach(Context context) {
+        if (shouldSkipForInitialSUW() && !WizardManagerHelper.isDeviceProvisioned(getContext())) {
+            Log.w(TAG, "Skip " + getClass().getSimpleName() + " before SUW completed.");
+            finish();
+        }
+        super.onAttach(context);
+    }
+
+    @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         SearchMenuController.init(this /* host */);
@@ -270,6 +281,16 @@
                 || (mAdapter.getPreferenceAdapterPosition(preference) != RecyclerView.NO_POSITION));
     }
 
+    /**
+     * Whether UI should be skipped in the initial SUW flow.
+     *
+     * @return {@code true} when UI should be skipped in the initial SUW flow.
+     * {@code false} when UI should not be skipped in the initial SUW flow.
+     */
+    protected boolean shouldSkipForInitialSUW() {
+        return false;
+    }
+
     protected void onDataSetChanged() {
         highlightPreferenceIfNeeded();
         updateEmptyView();
diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java
index 7b50b46..c66b6c7 100644
--- a/src/com/android/settings/accounts/AccountDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountDashboardFragment.java
@@ -72,6 +72,11 @@
         return buildPreferenceControllers(context, this /* parent */, authorities);
     }
 
+    @Override
+    protected boolean shouldSkipForInitialSUW() {
+        return true;
+    }
+
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
             SettingsPreferenceFragment parent, String[] authorities) {
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 7aad245..69ddd58 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -491,6 +491,11 @@
         return true;
     }
 
+    @Override
+    protected boolean shouldSkipForInitialSUW() {
+        return true;
+    }
+
     private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
         stopListeningToPackageRemove();
         // Create new intent to launch Uninstaller activity
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 945970e..9029249 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -205,6 +205,11 @@
     }
 
     @Override
+    protected boolean shouldSkipForInitialSUW() {
+        return true;
+    }
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         registerReceivers();
diff --git a/src/com/android/settings/system/ResetDashboardFragment.java b/src/com/android/settings/system/ResetDashboardFragment.java
index 024db14..8c376c6 100644
--- a/src/com/android/settings/system/ResetDashboardFragment.java
+++ b/src/com/android/settings/system/ResetDashboardFragment.java
@@ -57,6 +57,11 @@
         return buildPreferenceControllers(context, getSettingsLifecycle());
     }
 
+    @Override
+    protected boolean shouldSkipForInitialSUW() {
+        return true;
+    }
+
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
             Lifecycle lifecycle) {
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
diff --git a/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java
index cb53f69..6489311 100644
--- a/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java
@@ -23,11 +23,13 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -41,6 +43,7 @@
 import com.android.settings.testutils.shadow.ShadowFragment;
 import com.android.settings.widget.WorkOnlyCategory;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,7 +67,9 @@
     private PreferenceScreen mPreferenceScreen;
     private Context mContext;
     private TestFragment mFragment;
+    private TestFragment2 mFragment2;
     private View mEmptyView;
+    private int mInitDeviceProvisionedValue;
 
     @Before
     public void setUp() {
@@ -72,13 +77,24 @@
         FakeFeatureFactory.setupForTest();
         mContext = RuntimeEnvironment.application;
         mFragment = spy(new TestFragment());
+        mFragment2 = spy(new TestFragment2());
         doReturn(mActivity).when(mFragment).getActivity();
         when(mFragment.getContext()).thenReturn(mContext);
+        when(mFragment2.getContext()).thenReturn(mContext);
 
         mEmptyView = new View(mContext);
         ReflectionHelpers.setField(mFragment, "mEmptyView", mEmptyView);
 
         doReturn(ITEM_COUNT).when(mPreferenceScreen).getPreferenceCount();
+
+        mInitDeviceProvisionedValue = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0);
+    }
+
+    @After
+    public void tearDown() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, mInitDeviceProvisionedValue);
     }
 
     @Test
@@ -210,9 +226,67 @@
         assertThat(mFragment.mPinnedHeaderFrameLayout.getVisibility()).isEqualTo(View.INVISIBLE);
     }
 
+    @Test
+    public void onAttach_shouldNotSkipForSUWAndDeviceIsProvisioned_notCallFinish() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1);
+
+        mFragment.onAttach(mContext);
+
+        verify(mFragment, never()).finish();
+    }
+
+    @Test
+    public void onAttach_shouldNotSkipForSUWAndDeviceIsNotProvisioned_notCallFinish() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0);
+
+        mFragment.onAttach(mContext);
+
+        verify(mFragment, never()).finish();
+    }
+
+    @Test
+    public void onAttach_shouldSkipForSUWAndDeviceIsDeviceProvisioned_notCallFinish() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1);
+
+        mFragment2.onAttach(mContext);
+
+        verify(mFragment2, never()).finish();
+    }
+
+    @Test
+    public void onAttach_shouldSkipForSUWAndDeviceProvisioned_notCallFinish() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0);
+
+        mFragment2.onAttach(mContext);
+
+        verify(mFragment2, times(1)).finish();
+    }
+
     public static class TestFragment extends SettingsPreferenceFragment {
 
         @Override
+        protected boolean shouldSkipForInitialSUW() {
+            return false;
+        }
+
+        @Override
+        public int getMetricsCategory() {
+            return 0;
+        }
+    }
+
+    public static class TestFragment2 extends SettingsPreferenceFragment {
+
+        @Override
+        protected boolean shouldSkipForInitialSUW() {
+            return true;
+        }
+
+        @Override
         public int getMetricsCategory() {
             return 0;
         }
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java
index fe57090..921587e 100644
--- a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java
@@ -114,4 +114,9 @@
 
         assertThat(indexRaws).isNotEmpty();
     }
+
+    @Test
+    public void shouldSkipForInitialSUW_returnTrue() {
+        assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
index e46cd06..5292c60 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
@@ -384,6 +384,11 @@
                 .isTrue();
     }
 
+    @Test
+    public void shouldSkipForInitialSUW_returnTrue() {
+        assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
+    }
+
     @Implements(AppUtils.class)
     public static class ShadowAppUtils {
 
diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
index 101297b..44a7d49 100644
--- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
@@ -276,6 +276,11 @@
         verify(controller).onDisableLogPersistDialogRejected();
     }
 
+    @Test
+    public void shouldSkipForInitialSUW_returnTrue() {
+        assertThat(mDashboard.shouldSkipForInitialSUW()).isTrue();
+    }
+
     @Implements(EnableDevelopmentSettingWarningDialog.class)
     public static class ShadowEnableDevelopmentSettingWarningDialog {
 
diff --git a/tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java
new file mode 100644
index 0000000..c1d4788
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ResetDashboardFragmentTest {
+
+    private ResetDashboardFragment mFragment;
+
+    @Before
+    public void setup() {
+        mFragment = new ResetDashboardFragment();
+    }
+
+    @Test
+    public void shouldSkipForInitialSUW_returnTrue() {
+        assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
+    }
+}