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
This commit is contained in:
Florian Mayer
2022-09-15 14:31:42 -07:00
parent 1cda0a8418
commit 5696dde950
15 changed files with 904 additions and 1 deletions

View File

@@ -6709,6 +6709,7 @@
<!-- Help URL, Top level privacy settings [DO NOT TRANSLATE] --> <!-- Help URL, Top level privacy settings [DO NOT TRANSLATE] -->
<string name="help_url_privacy_dashboard" translatable="false"></string> <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_network_dashboard" translatable="false"></string>
<string name="help_url_connected_devices" translatable="false"></string> <string name="help_url_connected_devices" translatable="false"></string>
<string name="help_url_apps_and_notifications" translatable="false"></string> <string name="help_url_apps_and_notifications" translatable="false"></string>
@@ -8628,6 +8629,38 @@
<!-- [CHAR LIMIT=NONE] eSim deletion confirmation description --> <!-- [CHAR LIMIT=NONE] eSim deletion confirmation description -->
<string name="confirm_sim_deletion_description">Verify it\u0027s you before erasing a downloaded SIM</string> <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] --> <!-- 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> <string name="opening_paragraph_delete_profile_unknown_company">This work profile is managed by:</string>
<!-- Summary for work profile accounts group. [CHAR LIMIT=25] --> <!-- Summary for work profile accounts group. [CHAR LIMIT=25] -->

37
res/xml/memtag_page.xml Normal file
View File

@@ -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>

View File

@@ -107,9 +107,17 @@
settings:isPreferenceVisible="@bool/config_show_sim_info" settings:isPreferenceVisible="@bool/config_show_sim_info"
settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" /> 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 --> <!-- work profile security section -->
<PreferenceCategory <PreferenceCategory
android:order="100" android:order="110"
android:key="security_category_profile" android:key="security_category_profile"
android:title="@string/lock_settings_profile_title"> android:title="@string/lock_settings_profile_title">

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -80,6 +80,7 @@ android_robolectric_test {
"SettingsLib-robo-testutils", "SettingsLib-robo-testutils",
"android-support-annotations", "android-support-annotations",
"androidx.test.core", "androidx.test.core",
"androidx.test.rules",
"androidx.test.runner", "androidx.test.runner",
"androidx.test.ext.junit", "androidx.test.ext.junit",
"androidx.test.espresso.core", "androidx.test.espresso.core",

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.testutils.shadow; package com.android.settings.testutils.shadow;
import android.annotation.NonNull;
import android.os.storage.DiskInfo; import android.os.storage.DiskInfo;
import android.os.storage.StorageManager; import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo; import android.os.storage.VolumeInfo;
@@ -25,6 +26,9 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter; import org.robolectric.annotation.Resetter;
import java.util.ArrayList;
import java.util.List;
@Implements(StorageManager.class) @Implements(StorageManager.class)
public class ShadowStorageManager { public class ShadowStorageManager {
@@ -40,6 +44,10 @@ public class ShadowStorageManager {
return sIsForgetCalled; return sIsForgetCalled;
} }
public @NonNull List<VolumeInfo> getVolumes() {
return new ArrayList<VolumeInfo>();
}
@Resetter @Resetter
public static void reset() { public static void reset() {
sIsUnmountCalled = false; sIsUnmountCalled = false;