From a44e75d6ed31e65fe9d8d1fe371d9395f04b7353 Mon Sep 17 00:00:00 2001 From: Wentao Wang Date: Thu, 7 Sep 2023 02:57:28 +0000 Subject: [PATCH] Separate the demo user factory reset option with admin user factory reset option. A security vulnerability was discovered by Android security. b/292548775 Within a short period of time after the device reboot, the user could enter the settings page and factory reset the device. Android Enterprise suggests to add DISALLOW_FACTORY_RESET user restriction to the device. However, DISALLOW_FACTORY_RESET will be enabled on all Android users, including both the admin user and the demo user. The existing behavior in Android settings is that once the user restriction is set, factory reset button will be greyed out, which makes the factory reset functionality in demo user go away. In demo user, the factory reset command will be forwarded to the current active device owner. So in this change, we separate the button for admin user and the button for demo user. In demo user, the button is still visible when the restriction is set. And in admin user, the button will be greyed out as expected. Once this change is in, then Pixel Retail Demo could set the user restriction properly and rely on its internal logic to do factory reset. If other applications are trying to do the factory reset, it will be denied by OS. BUG: 292548775 Change-Id: I9d2d47bb29bc2c1e05058b246908768cd2f95990 --- res/xml/reset_dashboard_fragment.xml | 8 ++ src/com/android/settings/MainClear.java | 2 +- ...toryResetDemoUserPreferenceController.java | 32 +++++++ .../FactoryResetPreferenceController.java | 25 ++--- .../system/ResetDashboardFragment.java | 1 - .../system/ResetPreferenceController.java | 2 - ...ResetDemoUserPreferenceControllerTest.java | 96 +++++++++++++++++++ .../FactoryResetPreferenceControllerTest.java | 4 +- 8 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java diff --git a/res/xml/reset_dashboard_fragment.xml b/res/xml/reset_dashboard_fragment.xml index 3bd7a136760..08852c92c86 100644 --- a/res/xml/reset_dashboard_fragment.xml +++ b/res/xml/reset_dashboard_fragment.xml @@ -57,5 +57,13 @@ settings:keywords="@string/keywords_factory_data_reset" settings:userRestriction="no_factory_reset" settings:useAdminDisabledSummary="true" + settings:controller="com.android.settings.system.FactoryResetPreferenceController" + android:fragment="com.android.settings.MainClear" /> + + diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java index f706c785401..8a441e2c2ce 100644 --- a/src/com/android/settings/MainClear.java +++ b/src/com/android/settings/MainClear.java @@ -569,7 +569,7 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis UserHandle.myUserId()); if (disallow && !Utils.isDemoUser(context)) { return inflater.inflate(R.layout.main_clear_disallowed_screen, null); - } else if (admin != null) { + } else if (admin != null && !Utils.isDemoUser(context)) { new ActionDisabledByAdminDialogHelper(getActivity()) .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin) .setOnDismissListener(__ -> getActivity().finish()) diff --git a/src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java b/src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java new file mode 100644 index 00000000000..f6a9b3198a3 --- /dev/null +++ b/src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 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 android.content.Context; +import com.android.settings.Utils; + +public class FactoryResetDemoUserPreferenceController extends FactoryResetPreferenceController { + + public FactoryResetDemoUserPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** Hide demo user specific "Factory reset" settings for non demo users. */ + @Override + public int getAvailabilityStatus() { + return Utils.isDemoUser(mContext) ? AVAILABLE : DISABLED_FOR_USER; + } +} diff --git a/src/com/android/settings/system/FactoryResetPreferenceController.java b/src/com/android/settings/system/FactoryResetPreferenceController.java index a307171d122..6e010c1fbc2 100644 --- a/src/com/android/settings/system/FactoryResetPreferenceController.java +++ b/src/com/android/settings/system/FactoryResetPreferenceController.java @@ -24,35 +24,26 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.Utils; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settings.core.BasePreferenceController; -public class FactoryResetPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin { - /** Key of the "Factory reset" preference in {@link R.xml.reset_dashboard_fragment}. */ - private static final String KEY_FACTORY_RESET = "factory_reset"; +public class FactoryResetPreferenceController extends BasePreferenceController { private final UserManager mUm; - public FactoryResetPreferenceController(Context context) { - super(context); + public FactoryResetPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); } - /** Hide "Factory reset" settings for secondary users, except demo users. */ + /** Hide "Factory reset" settings for secondary users. */ @Override - public boolean isAvailable() { - return mUm.isAdminUser() || Utils.isDemoUser(mContext); - } - - @Override - public String getPreferenceKey() { - return KEY_FACTORY_RESET; + public int getAvailabilityStatus() { + return mUm.isAdminUser() ? AVAILABLE : DISABLED_FOR_USER; } @Override public boolean handlePreferenceTreeClick(Preference preference) { - if (KEY_FACTORY_RESET.equals(preference.getKey())) { + if (mPreferenceKey.equals(preference.getKey())) { final Intent intent = new Intent(mContext, Settings.FactoryResetActivity.class); mContext.startActivity(intent); return true; diff --git a/src/com/android/settings/system/ResetDashboardFragment.java b/src/com/android/settings/system/ResetDashboardFragment.java index aea92aafc90..662edc53b20 100644 --- a/src/com/android/settings/system/ResetDashboardFragment.java +++ b/src/com/android/settings/system/ResetDashboardFragment.java @@ -78,7 +78,6 @@ public class ResetDashboardFragment extends DashboardFragment { if (SubscriptionUtil.isSimHardwareVisible(context)) { controllers.add(new NetworkResetPreferenceController(context)); } - controllers.add(new FactoryResetPreferenceController(context)); controllers.add(new ResetAppPrefPreferenceController(context, lifecycle)); return controllers; } diff --git a/src/com/android/settings/system/ResetPreferenceController.java b/src/com/android/settings/system/ResetPreferenceController.java index 0740ac9dae0..35f1ff7a9f2 100644 --- a/src/com/android/settings/system/ResetPreferenceController.java +++ b/src/com/android/settings/system/ResetPreferenceController.java @@ -26,13 +26,11 @@ public class ResetPreferenceController extends BasePreferenceController { private final UserManager mUm; private final NetworkResetPreferenceController mNetworkReset; - private final FactoryResetPreferenceController mFactpruReset; public ResetPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); mNetworkReset = new NetworkResetPreferenceController(context); - mFactpruReset = new FactoryResetPreferenceController(context); } @Override diff --git a/tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java new file mode 100644 index 00000000000..0c92b057f92 --- /dev/null +++ b/tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 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 android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settings.testutils.shadow.ShadowUtils; + +import org.junit.After; +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 = ShadowUserManager.class) +public class FactoryResetDemoUserPreferenceControllerTest { + + private static final String FACTORY_RESET_DEMO_USER_KEY = "factory_reset_demo_user"; + + private ShadowUserManager mShadowUserManager; + + private Context mContext; + private FactoryResetDemoUserPreferenceController mController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mShadowUserManager = ShadowUserManager.getShadow(); + + mController = new FactoryResetDemoUserPreferenceController( + mContext, FACTORY_RESET_DEMO_USER_KEY); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + mShadowUserManager.setIsAdminUser(false); + mShadowUserManager.setIsDemoUser(false); + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0); + } + + @Test + public void isAvailable_systemUser() { + mShadowUserManager.setIsAdminUser(true); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_nonSystemUser() { + mShadowUserManager.setIsAdminUser(false); + mShadowUserManager.setIsDemoUser(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_demoUser() { + mShadowUserManager.setIsAdminUser(false); + + // Place the device in demo mode. + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1); + + // Indicate the user is a demo user. + mShadowUserManager.addUser(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void getPreferenceKey() { + assertThat(mController.getPreferenceKey()).isEqualTo(FACTORY_RESET_DEMO_USER_KEY); + } +} diff --git a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java index f2a932ee0fe..6e6fad83a27 100644 --- a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java @@ -49,7 +49,7 @@ public class FactoryResetPreferenceControllerTest { mContext = RuntimeEnvironment.application; mShadowUserManager = ShadowUserManager.getShadow(); - mController = new FactoryResetPreferenceController(mContext); + mController = new FactoryResetPreferenceController(mContext, FACTORY_RESET_KEY); } @After @@ -85,7 +85,7 @@ public class FactoryResetPreferenceControllerTest { // Indicate the user is a demo user. mShadowUserManager.addUser(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO); - assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.isAvailable()).isFalse(); } @Test