Add MTE Settings.
MTE is a technology to help prevent exploitation of some security bugs.
We want to offer users that want to trade off a slight reduction in
performance for higher security the option to do so from the settings
menu.
Test: make RunSettingsRoboTests
check UI manually
Bug: 245624194
Change-Id: Ifbb76e124142ae843ce90bd604ae8417d65fcc7b
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e2acc38..f61fe77 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6709,6 +6709,7 @@
<!-- Help URL, Top level privacy settings [DO NOT TRANSLATE] -->
<string name="help_url_privacy_dashboard" translatable="false"></string>
+ <string name="help_url_memtag" translatable="false"></string>
<string name="help_url_network_dashboard" translatable="false"></string>
<string name="help_url_connected_devices" translatable="false"></string>
<string name="help_url_apps_and_notifications" translatable="false"></string>
@@ -8628,6 +8629,38 @@
<!-- [CHAR LIMIT=NONE] eSim deletion confirmation description -->
<string name="confirm_sim_deletion_description">Verify it\u0027s you before erasing a downloaded SIM</string>
+ <!-- TODO(b/258550150): Finalize all strings in this section and remove translatable="false" -->
+ <!-- [CHAR LIMIT=32] Name of Advanced memory protection page in "More Security Settings" and heading of page. -->
+ <string name="memtag_title" translatable="false">Advanced memory protection</string>
+ <!-- [CHAR LIMIT=52] Label for button to turn on / off Advanced memory protection.-->
+ <string name="memtag_toggle" translatable="false">Try Advanced memory protection</string>
+ <!-- [CHAR LIMIT=NONE] Subtitle of Advanced memory protection page. -->
+ <string name="memtag_intro" translatable="false">This beta feature helps you protect your device from bugs that may put your security at risk.</string>
+ <!-- [CHAR LIMIT=NONE] Status label indicating that Advanced memory protection is on. -->
+ <string name="memtag_on" translatable="false">On</string>
+ <!-- [CHAR LIMIT=37] Status label indicating that Advanced memory protection is off. -->
+ <string name="memtag_off" translatable="false">Off</string>
+ <!-- [CHAR LIMIT=37] Status label indicating that system needs to be rebooted for Advanced memory protection to be on. -->
+ <string name="memtag_on_pending" translatable="false">On after restart</string>
+ <!-- [CHAR LIMIT=37] Status label indicating that system needs to be rebooted for Advanced memory protection to be off. -->
+ <string name="memtag_off_pending" translatable="false">Off after restart</string>
+ <!-- [CHAR LIMIT=37] Status label indicating that Advanced memory protection was forced off via remote device configuration. -->
+ <string name="memtag_force_off" translatable="false">Currently unavailable for your device.</string>
+ <!-- [CHAR LIMIT=NONE] Subtext on page to control Advanced memory protection settings. -->
+ <string name="memtag_footer" translatable="false">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.</string>
+ <!-- [CHAR LIMIT=31] Header of dialog asking user to reboot device. -->
+ <string name="memtag_reboot_title" translatable="false">Restart device?</string>
+ <!-- [CHAR LIMIT=NONE] Message shown in dialog prompting user to reboot device to turn on Advanced memory protection.-->
+ <string name="memtag_reboot_message_on" translatable="false">You\u0027ll need to restart your device to turn on Advanced memory protection.</string>
+ <!-- [CHAR LIMIT=NONE] Message shown in dialog prompting user to reboot device to turn off Advanced memory protection.-->
+ <string name="memtag_reboot_message_off" translatable="false">You\u0027ll need to restart your device to turn off Advanced memory protection.</string>
+ <!-- [CHAR LIMIT=17] Button label in dialog prompting user to reboot device.-->
+ <string name="memtag_reboot_yes" translatable="false">Restart</string>
+ <!-- [CHAR LIMIT=17] Button label in dialog prompting user to reboot device.-->
+ <string name="memtag_reboot_no" translatable="false">Not now</string>
+ <!-- [CHAR LIMIT=NONE] Label for Learn More link. -->
+ <string name="memtag_learn_more" translatable="false">Learn more about Advanced memory protection.</string>
+
<!-- Opening string on the dialog that prompts the user to confirm that they really want to delete their existing work profile. The administration app icon and name appear after the final colon. [CHAR LIMIT=NONE] -->
<string name="opening_paragraph_delete_profile_unknown_company">This work profile is managed by:</string>
<!-- Summary for work profile accounts group. [CHAR LIMIT=25] -->
diff --git a/res/xml/memtag_page.xml b/res/xml/memtag_page.xml
new file mode 100644
index 0000000..6255b43
--- /dev/null
+++ b/res/xml/memtag_page.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/memtag_title">
+
+ <com.android.settingslib.widget.TopIntroPreference
+ android:title="@string/memtag_intro"
+ settings:searchable="false"/>
+
+ <SwitchPreference
+ android:id="@+id/memtag_page_switch"
+ android:key="memtag"
+ android:title="@string/memtag_toggle"
+ settings:controller="com.android.settings.security.MemtagPreferenceController" />
+
+ <com.android.settingslib.widget.FooterPreference
+ android:title="@string/memtag_footer"
+ android:key="memtag_footer"
+ settings:searchable="false"
+ settings:controller="com.android.settings.security.MemtagFooterPreferenceController" />
+</PreferenceScreen>
diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml
index b36fc73..c8ed6db 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" />
+ <Preference
+ android:order="100"
+ android:id="@+id/memtag_page"
+ android:key="memtag_page"
+ android:title="@string/memtag_title"
+ android:fragment="com.android.settings.security.MemtagPage"
+ settings:controller="com.android.settings.security.MemtagPagePreferenceController" />
+
<!-- work profile security section -->
<PreferenceCategory
- android:order="100"
+ android:order="110"
android:key="security_category_profile"
android:title="@string/lock_settings_profile_title">
diff --git a/src/com/android/settings/security/MemtagFooterPreferenceController.java b/src/com/android/settings/security/MemtagFooterPreferenceController.java
new file mode 100644
index 0000000..bcdabb0
--- /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 0000000..ecd6ea6
--- /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 0000000..f1ffcb1
--- /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 0000000..90e765b
--- /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 0000000..290e40c
--- /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 0000000..735de8f
--- /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 7119100..26c4d19 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -80,6 +80,7 @@
"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 0000000..4ffbf5b
--- /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 0000000..a4fd21b
--- /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 0000000..2f8f658
--- /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<TestActivity> 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 0000000..23b30fa
--- /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 11834b1..fce0498 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.Implements;
import org.robolectric.annotation.Resetter;
+import java.util.ArrayList;
+import java.util.List;
+
@Implements(StorageManager.class)
public class ShadowStorageManager {
@@ -40,6 +44,10 @@
return sIsForgetCalled;
}
+ public @NonNull List<VolumeInfo> getVolumes() {
+ return new ArrayList<VolumeInfo>();
+ }
+
@Resetter
public static void reset() {
sIsUnmountCalled = false;