diff --git a/res/values/strings.xml b/res/values/strings.xml index e2acc38ef29..f61fe77b50b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6709,6 +6709,7 @@ + @@ -8628,6 +8629,38 @@ Verify it\u0027s you before erasing a downloaded SIM + + + Advanced memory protection + + Try Advanced memory protection + + This beta feature helps you protect your device from bugs that may put your security at risk. + + On + + Off + + On after restart + + Off after restart + + Currently unavailable for your device. + + You\u0027ll have to restart your device to turn Advanced memory protection on or off. When it\u0027s on, you may notice slower device performance. + + Restart device? + + You\u0027ll need to restart your device to turn on Advanced memory protection. + + You\u0027ll need to restart your device to turn off Advanced memory protection. + + Restart + + Not now + + Learn more about Advanced memory protection. + This work profile is managed by: diff --git a/res/xml/memtag_page.xml b/res/xml/memtag_page.xml new file mode 100644 index 00000000000..6255b43385a --- /dev/null +++ b/res/xml/memtag_page.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml index b36fc73fdd2..c8ed6db0c15 100644 --- a/res/xml/security_advanced_settings.xml +++ b/res/xml/security_advanced_settings.xml @@ -107,9 +107,17 @@ settings:isPreferenceVisible="@bool/config_show_sim_info" settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" /> + + diff --git a/src/com/android/settings/security/MemtagFooterPreferenceController.java b/src/com/android/settings/security/MemtagFooterPreferenceController.java new file mode 100644 index 00000000000..bcdabb055be --- /dev/null +++ b/src/com/android/settings/security/MemtagFooterPreferenceController.java @@ -0,0 +1,56 @@ +/* + * 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.security; + +import android.content.Context; +import android.text.TextUtils; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.HelpUtils; +import com.android.settingslib.widget.FooterPreference; + +/** Footer for face settings showing the help text and help link. */ +public class MemtagFooterPreferenceController extends BasePreferenceController { + + public MemtagFooterPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + // Set up learn more link. + FooterPreference prefFooter = screen.findPreference(getPreferenceKey()); + String helpUrl = mContext.getString(R.string.help_url_memtag); + if (prefFooter != null && !TextUtils.isEmpty(helpUrl)) { + prefFooter.setLearnMoreAction( + v -> + mContext.startActivity( + HelpUtils.getHelpIntent( + mContext, helpUrl, /* backupContext= */ ""))); + prefFooter.setLearnMoreText(mContext.getString(R.string.memtag_learn_more)); + } + } +} diff --git a/src/com/android/settings/security/MemtagHelper.java b/src/com/android/settings/security/MemtagHelper.java new file mode 100644 index 00000000000..ecd6ea63cdf --- /dev/null +++ b/src/com/android/settings/security/MemtagHelper.java @@ -0,0 +1,79 @@ +/* + * 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.security; + +import android.os.SystemProperties; + +import com.android.internal.os.Zygote; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import java.util.Arrays; + +public class MemtagHelper { + private static boolean isForcedOff() { + return "force_off" + .equals( + SystemProperties.get( + "persist.device_config.memory_safety_native.bootloader_override")); + } + + public static boolean isChecked() { + String modes[] = SystemProperties.get("arm64.memtag.bootctl", "").split(","); + return Arrays.asList(modes).contains("memtag"); + } + + public static void setChecked(boolean isChecked) { + String newString = isChecked ? "memtag" : "none"; + SystemProperties.set("arm64.memtag.bootctl", newString); + } + + public static int getAvailabilityStatus() { + if (MemtagHelper.isForcedOff()) { + return BasePreferenceController.DISABLED_DEPENDENT_SETTING; + } + return SystemProperties.getBoolean("ro.arm64.memtag.bootctl_supported", false) + ? BasePreferenceController.AVAILABLE + : BasePreferenceController.UNSUPPORTED_ON_DEVICE; + } + + /** + * Returns whether MTE is currently active on this device. We use this to determine whether we + * need to reboot the device to apply the user choice. + * + * @return boolean whether MTE is currently active + */ + public static boolean isOn() { + return Zygote.nativeSupportsMemoryTagging(); + } + + public static int getSummary() { + if (isForcedOff()) { + return R.string.memtag_force_off; + } + if (isOn()) { + if (isChecked()) { + return R.string.memtag_on; + } + return R.string.memtag_off_pending; + } + if (isChecked()) { + return R.string.memtag_on_pending; + } + return R.string.memtag_off; + } +} diff --git a/src/com/android/settings/security/MemtagPage.java b/src/com/android/settings/security/MemtagPage.java new file mode 100644 index 00000000000..f1ffcb1b1f3 --- /dev/null +++ b/src/com/android/settings/security/MemtagPage.java @@ -0,0 +1,56 @@ +/* + * 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.security; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +@SearchIndexable +public class MemtagPage extends DashboardFragment { + + private static final String TAG = "MemtagPage"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.SETTINGS_MEMTAG_CATEGORY; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + use(MemtagPreferenceController.class).setFragment(this); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.memtag_page; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.memtag_page); +} diff --git a/src/com/android/settings/security/MemtagPagePreferenceController.java b/src/com/android/settings/security/MemtagPagePreferenceController.java new file mode 100644 index 00000000000..90e765bb3d4 --- /dev/null +++ b/src/com/android/settings/security/MemtagPagePreferenceController.java @@ -0,0 +1,49 @@ +/* + * 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.security; + +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; + +public class MemtagPagePreferenceController extends BasePreferenceController { + static final String KEY_MEMTAG = "memtag_page"; + + public MemtagPagePreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return MemtagHelper.getAvailabilityStatus(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + Preference preference = screen.findPreference(getPreferenceKey()); + refreshSummary(preference); + } + + @Override + public CharSequence getSummary() { + return mContext.getResources().getString(MemtagHelper.getSummary()); + } +} diff --git a/src/com/android/settings/security/MemtagPreferenceController.java b/src/com/android/settings/security/MemtagPreferenceController.java new file mode 100644 index 00000000000..290e40c0d48 --- /dev/null +++ b/src/com/android/settings/security/MemtagPreferenceController.java @@ -0,0 +1,84 @@ +/* + * 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.security; + +import android.content.Context; + +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +public class MemtagPreferenceController extends TogglePreferenceController { + private Preference mPreference; + private Fragment mFragment; + + public MemtagPreferenceController(Context context, String key) { + super(context, key); + } + + public void setFragment(Fragment fragment) { + mFragment = fragment; + } + + @Override + public int getAvailabilityStatus() { + return MemtagHelper.getAvailabilityStatus(); + } + + @Override + public boolean isChecked() { + return MemtagHelper.isChecked(); + } + + @Override + public boolean setChecked(boolean isChecked) { + MemtagHelper.setChecked(isChecked); + if (mPreference != null) { + refreshSummary(mPreference); + } + if (isChecked != MemtagHelper.isOn()) { + MemtagRebootDialog.show(mContext, mFragment, isChecked); + } + return true; + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_security; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + refreshSummary(mPreference); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + refreshSummary(preference); + } + + @Override + public CharSequence getSummary() { + return mContext.getResources().getString(MemtagHelper.getSummary()); + } +} diff --git a/src/com/android/settings/security/MemtagRebootDialog.java b/src/com/android/settings/security/MemtagRebootDialog.java new file mode 100644 index 00000000000..735de8f8058 --- /dev/null +++ b/src/com/android/settings/security/MemtagRebootDialog.java @@ -0,0 +1,73 @@ +/* + * 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.security; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.PowerManager; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +public class MemtagRebootDialog extends InstrumentedDialogFragment + implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener { + + public static final String TAG = "MemtagRebootDialog"; + private boolean mIsChecked; + + public MemtagRebootDialog(Context context, boolean isChecked) { + mIsChecked = isChecked; + } + + public static void show(Context context, Fragment host, boolean isChecked) { + final FragmentManager manager = host.getActivity().getSupportFragmentManager(); + if (manager.findFragmentByTag(TAG) == null) { + final MemtagRebootDialog dialog = new MemtagRebootDialog(context, isChecked); + dialog.show(manager, TAG); + } + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.REBOOT_WITH_MTE; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + int msg = + mIsChecked ? R.string.memtag_reboot_message_on : R.string.memtag_reboot_message_off; + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.memtag_reboot_title) + .setMessage(msg) + .setPositiveButton(R.string.memtag_reboot_yes, this /* onClickListener */) + .setNegativeButton(R.string.memtag_reboot_no, null /* onClickListener */) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + PowerManager pm = getContext().getSystemService(PowerManager.class); + pm.reboot(/* reason */ null); + } +} diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp index 711910091e3..26c4d19dbe8 100644 --- a/tests/robotests/Android.bp +++ b/tests/robotests/Android.bp @@ -80,6 +80,7 @@ android_robolectric_test { "SettingsLib-robo-testutils", "android-support-annotations", "androidx.test.core", + "androidx.test.rules", "androidx.test.runner", "androidx.test.ext.junit", "androidx.test.espresso.core", diff --git a/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java b/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java new file mode 100644 index 00000000000..4ffbf5b66ab --- /dev/null +++ b/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java @@ -0,0 +1,162 @@ +/* + * 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.security; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.SystemProperties; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowSystemProperties; + +@RunWith(RobolectricTestRunner.class) +public class MemtagHelperTest { + private final String mMemtagProperty = "arm64.memtag.bootctl"; + private final String mMemtagSupportedProperty = "ro.arm64.memtag.bootctl_supported"; + private final String mDeviceConfigOverride = + "persist.device_config.memory_safety_native.bootloader_override"; + + @Test + public void isChecked_empty_isFalse() { + ShadowSystemProperties.override(mMemtagProperty, ""); + assertThat(MemtagHelper.isChecked()).isFalse(); + } + + @Test + public void isChecked_memtag_isTrue() { + ShadowSystemProperties.override(mMemtagProperty, "memtag"); + assertThat(MemtagHelper.isChecked()).isTrue(); + } + + @Test + public void isChecked_memtagAndKernel_isTrue() { + ShadowSystemProperties.override(mMemtagProperty, "memtag,memtag-kernel"); + assertThat(MemtagHelper.isChecked()).isTrue(); + } + + @Test + public void isChecked_kernel_isFalse() { + ShadowSystemProperties.override(mMemtagProperty, "memtag-kernel"); + assertThat(MemtagHelper.isChecked()).isFalse(); + } + + @Test + public void isChecked_kernelAndMemtag_isTrue() { + ShadowSystemProperties.override(mMemtagProperty, "memtag-kernel,memtag"); + assertThat(MemtagHelper.isChecked()).isTrue(); + } + + @Test + public void SetChecked_true_isMemtag() { + MemtagHelper.setChecked(true); + assertThat(SystemProperties.get(mMemtagProperty)).isEqualTo("memtag"); + } + + @Test + public void SetChecked_false_isNone() { + MemtagHelper.setChecked(false); + assertThat(SystemProperties.get(mMemtagProperty)).isEqualTo("none"); + } + + @Test + public void getAvailabilityStatus_isForcedOff_isDISABLED_DEPENDENT_SETTING() { + ShadowSystemProperties.override(mDeviceConfigOverride, "force_off"); + ShadowSystemProperties.override(mMemtagSupportedProperty, "true"); + assertThat(MemtagHelper.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING); + } + + @Test + public void getAvailabilityStatus_isUnsupported_isUNSUPPORTED_ON_DEVICE() { + ShadowSystemProperties.override(mDeviceConfigOverride, ""); + ShadowSystemProperties.override(mMemtagSupportedProperty, "false"); + assertThat(MemtagHelper.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_isSupported_isAVAILABLE() { + ShadowSystemProperties.override(mMemtagSupportedProperty, "true"); + assertThat(MemtagHelper.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void IsOn_zygoteSupportsMemoryTagging_isTrue() { + ZygoteShadow.setSupportsMemoryTagging(true); + assertThat(MemtagHelper.isOn()).isTrue(); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void IsOn_noZygoteSupportsMemoryTagging_isFalse() { + ZygoteShadow.setSupportsMemoryTagging(false); + assertThat(MemtagHelper.isOn()).isFalse(); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void getSummary_memtagAndZygoteSupportsMemoryTagging_memtag_on() { + ZygoteShadow.setSupportsMemoryTagging(true); + ShadowSystemProperties.override(mDeviceConfigOverride, ""); + ShadowSystemProperties.override(mMemtagProperty, "memtag"); + assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_on); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void getSummary_noMemtagAndZygoteSupportsMemoryTagging_memtag_off_pending() { + ZygoteShadow.setSupportsMemoryTagging(true); + ShadowSystemProperties.override(mDeviceConfigOverride, ""); + ShadowSystemProperties.override(mMemtagProperty, ""); + assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_off_pending); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void getSummary_noMemtagAndNoZygoteSupportsMemoryTagging_memtag_off() { + ZygoteShadow.setSupportsMemoryTagging(false); + ShadowSystemProperties.override(mDeviceConfigOverride, ""); + ShadowSystemProperties.override(mMemtagProperty, ""); + assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_off); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void getSummary_memtagAndNoZygoteSupportsMemoryTagging_memtag_on_pending() { + ZygoteShadow.setSupportsMemoryTagging(false); + ShadowSystemProperties.override(mDeviceConfigOverride, ""); + ShadowSystemProperties.override(mMemtagProperty, "memtag"); + assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_on_pending); + } + + @Test + @Config(shadows = {ZygoteShadow.class}) + public void getSummary_forceOffOverride_memtag_force_off() { + ZygoteShadow.setSupportsMemoryTagging(false); + ShadowSystemProperties.override(mDeviceConfigOverride, "force_off"); + ShadowSystemProperties.override(mMemtagProperty, "memtag"); + assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_force_off); + } +} diff --git a/tests/robotests/src/com/android/settings/security/MemtagPageTest.java b/tests/robotests/src/com/android/settings/security/MemtagPageTest.java new file mode 100644 index 00000000000..a4fd21b46b5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/security/MemtagPageTest.java @@ -0,0 +1,60 @@ +/* + * 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.security; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class MemtagPageTest { + + private MemtagPage mMemtagPage; + private Context mContext; + + @Before + public void setUp() { + mMemtagPage = new MemtagPage(); + mContext = RuntimeEnvironment.application; + } + + @Test + public void getMetricsCategory_isSETTINGS_MEMTAG_CATEGORY() { + assertThat(mMemtagPage.getMetricsCategory()) + .isEqualTo(SettingsEnums.SETTINGS_MEMTAG_CATEGORY); + } + + @Test + public void getPreferenceScreenResId_isMemtag_page() { + assertThat(mMemtagPage.getPreferenceScreenResId()).isEqualTo(R.xml.memtag_page); + } + + @Test + public void SEARCH_INDEX_DATA_PROVIDERgetPreferenceControllers_isNotEmpty() { + assertThat(MemtagPage.SEARCH_INDEX_DATA_PROVIDER.getPreferenceControllers(mContext)) + .isNotEmpty(); + } +} diff --git a/tests/robotests/src/com/android/settings/security/MemtagPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/MemtagPreferenceControllerTest.java new file mode 100644 index 00000000000..2f8f658681f --- /dev/null +++ b/tests/robotests/src/com/android/settings/security/MemtagPreferenceControllerTest.java @@ -0,0 +1,161 @@ +/* + * 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.security; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Bundle; + +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentContainerView; +import androidx.test.rule.ActivityTestRule; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowDeviceConfig; +import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowSystemProperties; + +@RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + ZygoteShadow.class, + ShadowDeviceConfig.class, + ShadowInteractionJankMonitor.class + }) +public class MemtagPreferenceControllerTest { + private final String mMemtagSupportedProperty = "ro.arm64.memtag.bootctl_supported"; + + @Rule + public ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(TestActivity.class); + + private MemtagPage mMemtagPage; + private MemtagPreferenceController mController; + private Context mContext; + private TestActivity mActivity; + + private static final String FRAGMENT_TAG = "memtag_page"; + + @Before + public void setUp() { + ShadowSystemProperties.override(mMemtagSupportedProperty, "true"); + + mContext = RuntimeEnvironment.application; + mMemtagPage = new MemtagPage(); + mActivity = mActivityTestRule.getActivity(); + mActivity + .getSupportFragmentManager() + .beginTransaction() + .add(TestActivity.CONTAINER_VIEW_ID, mMemtagPage) + .commit(); + mController = new MemtagPreferenceController(mContext, FRAGMENT_TAG); + mController.setFragment(mMemtagPage); + } + + @Test + public void getSliceHighlightMenuRes_isMenu_key_security() { + assertThat(mController.getSliceHighlightMenuRes()).isEqualTo(R.string.menu_key_security); + } + + @Test + public void setChecked_isChecked_updatesSummary() { + ZygoteShadow.setSupportsMemoryTagging(true); + mController.setChecked(true); + assertThat(mController.getSummary()) + .isEqualTo(mContext.getResources().getString(R.string.memtag_on)); + } + + @Test + public void setChecked_isUnchecked_updatesSummary() { + ZygoteShadow.setSupportsMemoryTagging(false); + mController.setChecked(false); + assertThat(mController.getSummary()) + .isEqualTo(mContext.getResources().getString(R.string.memtag_off)); + } + + @Test + public void setChecked_isCheckedPending_updatesSummary() { + ZygoteShadow.setSupportsMemoryTagging(false); + mController.setChecked(true); + assertThat(mController.getSummary()) + .isEqualTo(mContext.getResources().getString(R.string.memtag_on_pending)); + } + + @Test + public void setChecked_isUncheckedPending_updatesSummary() { + ZygoteShadow.setSupportsMemoryTagging(true); + mController.setChecked(false); + assertThat(mController.getSummary()) + .isEqualTo(mContext.getResources().getString(R.string.memtag_off_pending)); + } + + @Test + public void setChecked_isCheckedPending_showsDialog() { + ZygoteShadow.setSupportsMemoryTagging(false); + mController.setChecked(true); + onView(withText(R.string.memtag_reboot_title)).inRoot(isDialog()); + } + + @Test + public void setChecked_isUncheckedPending_showsDialog() { + ZygoteShadow.setSupportsMemoryTagging(true); + mController.setChecked(false); + onView(withText(R.string.memtag_reboot_title)).inRoot(isDialog()); + } + + @Test + public void setChecked_isChecked_doesNotShowDialog() { + ZygoteShadow.setSupportsMemoryTagging(false); + mController.setChecked(false); + onView(withText(R.string.memtag_reboot_title)).inRoot(isDialog()).check(doesNotExist()); + } + + @Test + public void setChecked_isUnchecked_doesNotShowDialog() { + ZygoteShadow.setSupportsMemoryTagging(true); + mController.setChecked(true); + onView(withText(R.string.memtag_reboot_title)).inRoot(isDialog()).check(doesNotExist()); + } + + private static final class TestActivity extends FragmentActivity { + + private static final int CONTAINER_VIEW_ID = 1234; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + + FragmentContainerView contentView = new FragmentContainerView(this); + contentView.setId(CONTAINER_VIEW_ID); + setContentView(contentView); + } + } +} diff --git a/tests/robotests/src/com/android/settings/security/ZygoteShadow.java b/tests/robotests/src/com/android/settings/security/ZygoteShadow.java new file mode 100644 index 00000000000..23b30faf045 --- /dev/null +++ b/tests/robotests/src/com/android/settings/security/ZygoteShadow.java @@ -0,0 +1,36 @@ +/* + * 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.security; + +import com.android.internal.os.Zygote; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(Zygote.class) +public class ZygoteShadow { + private static boolean sSupportsMemoryTagging; + + static void setSupportsMemoryTagging(boolean value) { + sSupportsMemoryTagging = value; + } + + @Implementation + public static boolean nativeSupportsMemoryTagging() { + return sSupportsMemoryTagging; + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java index 11834b1dc7e..fce049822eb 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java @@ -16,6 +16,7 @@ package com.android.settings.testutils.shadow; +import android.annotation.NonNull; import android.os.storage.DiskInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; @@ -25,6 +26,9 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; +import java.util.ArrayList; +import java.util.List; + @Implements(StorageManager.class) public class ShadowStorageManager { @@ -40,6 +44,10 @@ public class ShadowStorageManager { return sIsForgetCalled; } + public @NonNull List getVolumes() { + return new ArrayList(); + } + @Resetter public static void reset() { sIsUnmountCalled = false;