diff --git a/Android.bp b/Android.bp index 31895db32b1..51b4aa0d342 100644 --- a/Android.bp +++ b/Android.bp @@ -104,6 +104,7 @@ android_library { "Settings-change-ids", "androidx.room_room-runtime", "SystemUIUnfoldLib", + "ReverseWirelessCharging" ], plugins: ["androidx.room_room-compiler-plugin"], diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 453a71e1ad0..5b8451ceed5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -137,7 +137,8 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/drawable/ic_launcher_foreground.xml b/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000000..3a707cae45d --- /dev/null +++ b/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/res/drawable/ic_settings_install.xml b/res/drawable/ic_settings_install.xml index 5bd5e300eae..eefecc242a4 100644 --- a/res/drawable/ic_settings_install.xml +++ b/res/drawable/ic_settings_install.xml @@ -18,7 +18,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24"> + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> diff --git a/res/drawable/quickly_open_camera.xml b/res/drawable/quickly_open_camera.xml deleted file mode 100644 index dcbf9f4dc68..00000000000 --- a/res/drawable/quickly_open_camera.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/layout/face_enroll_introduction.xml b/res/layout/face_enroll_introduction.xml index 8b0352928ca..ea57f4acbc9 100644 --- a/res/layout/face_enroll_introduction.xml +++ b/res/layout/face_enroll_introduction.xml @@ -64,7 +64,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/BiometricEnrollIntroTitle" - android:text="@string/security_settings_face_enroll_introduction_info_title" /> + android:text="@string/security_settings_face_enroll_introduction_info_title_en" /> + android:text="@string/security_settings_face_enroll_introduction_how_title_en" /> + android:entries="@array/wifi_privacy_entries_extended"/> + + + + + diff --git a/res/mipmap-anydpi/ic_launcher_round.xml b/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 00000000000..5c84730caa7 --- /dev/null +++ b/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/raw/lottie_quickly_open_camera.json b/res/raw/lottie_quickly_open_camera.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 1ab98767200..ddaef75a2ff 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1059,6 +1059,12 @@ Treat as unmetered + + Use per-connection randomized MAC (default) + Use per-network randomized MAC + Use device MAC + + Use randomized MAC (default) Use device MAC @@ -1076,6 +1082,7 @@ + 100 1 0 diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 6f024a32be3..0a778da8894 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -68,6 +68,8 @@ + + diff --git a/res/values/config.xml b/res/values/config.xml index 17c33989502..54b8ee7ce44 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -42,14 +42,14 @@ com.android.settings.overlay.FeatureFactoryImpl - com.android.settings - com.android.settings.Settings$WallpaperSettingsActivity + com.android.wallpaper + com.android.wallpaper.picker.CategoryPickerActivity - + com.android.customization.picker.CustomizationPickerActivity - + android.intent.action.MAIN - + android.intent.action.MAIN com.android.wallpaper.LAUNCH_SOURCE @@ -75,6 +75,7 @@ com.example.package.first/com.example.class.FirstService com.example.package.second/com.example.class.SecondService --> + com.android.talkback/com.google.android.marvin.talkback.TalkBackService @@ -233,7 +234,7 @@ Whether or not the homepage should be powered by legacy suggestion (versus contextual cards) Default to true as not all devices support contextual cards. --> - true + false false @@ -460,7 +461,7 @@ true - true + false true @@ -499,7 +500,7 @@ false - content://com.google.android.gms.nearby.fastpair/device_status_list_item + @@ -586,7 +587,7 @@ content://com.android.providers.media.documents/root/documents_root - true + false Use your face to unlock your phone, authorize purchases, or sign in to apps + + Keep in mind + How it works + + Glasses or lightly tinted sunglasses are OK. + Looking at the phone can unlock it even when you don’t intend to. Your phone can also be unlocked by someone who looks a lot like you, like an identical sibling, or if someone holds it up to your face. + Using a face to unlock the phone may be less secure than a strong pattern or PIN. + Face Unlock can require your eyes to be open to unlock the phone or verify it’s you. You can turn this option on at any time in Settings. + Face Unlock creates a unique model of your face to verify it’s you. To create this face model during setup, you will take images of your face from different angles.\n\nWhen you use Face Unlock, images are used to update your face model. Images used to create your face model are not stored, but the face model is stored securely on your phone and never leaves the phone. All processing occurs securely on your phone. + You’re in control + You can delete your face model or turn off Face Unlock at any time in Settings. Face models are stored on the phone until you delete them.\n\nLearn more at g.co/pixel/faceunlock. + @@ -2430,6 +2442,10 @@ Camera access is required for Face Detection. Tap to manage permissions for Device Personalization Services Manage permissions + + Increase touch sensitivity + + Improves touch when using screen protectors Night Light @@ -2688,6 +2704,8 @@ Baseband version + Bootloader version + Kernel version Build number @@ -10781,6 +10799,8 @@ Preferred network mode: CDMA/EvDo/GSM/WCDMA Preferred network mode: LTE + + Preferred network mode: LTE only Preferred network mode: GSM/WCDMA/LTE @@ -10846,8 +10866,12 @@ 4G LTE (recommended) + + LTE only 4G (recommended) + + 4G only 3G @@ -11711,6 +11735,11 @@ Show a message when apps access text, images, or other content you\u2019ve copied + + Allow FLAG_SECURE screenshots + + Allow FLAG_SECURE activities to be screenshotted + All apps @@ -12310,4 +12339,23 @@ "This app can only be opened in 1 window" + Allow fingerprint unlocking + Allow fingerprints to unlock the screen lock. If this is disabled, fingerprints can still be used in apps. + + Send notifications to current user + Your lock screen notifications will be forwarded to the current user if you are active in the background. Only the user\'s name, the app\'s name, and the time received will be shown. + + Sandboxed Google Play + Sandboxed Google Play (work profile) + + Storage Scopes + + Allow access to Android/obb folder + Required for installation of some large apps (mostly games) that use OBB expansion files + + Installed: %1$s + Updated: %1$s + + Charge other devices by placing them on the back of your phone + Battery share diff --git a/res/values/strings_app_exploit_protection.xml b/res/values/strings_app_exploit_protection.xml new file mode 100644 index 00000000000..a8b765d3f5f --- /dev/null +++ b/res/values/strings_app_exploit_protection.xml @@ -0,0 +1,65 @@ + + + Exploit protection + + Allowed + Blocked + Default (%1$s) + Disabled + Enabled + Restricted + + Warning + This setting will weaken exploit protections for the selected app. + Proceed + + Default value can be changed in the Security section + + The selected app is known to be compatible with this setting + %1$s by default, because this app is known to need it. +This behavior can be disabled in the Security section. + + %1$s is enabled + + Exploit protection compatibility mode + Improves compatibility by disabling some of the GrapheneOS exploit protections for this app + + Hardened memory allocator + + Hardened memory allocator (hardened_malloc) provides substantial defenses against the most common classes of vulnerabilities (heap memory corruption) along with reducing the lifetime of sensitive data in memory. + This app doesn’t have native code + This is a 32-bit app, hardened_malloc is 64-bit only + hardened_malloc is not allowed to be disabled for a preinstalled app + This is a debuggable app, hardened_malloc can’t be disabled + + Extended virtual address space + Extends virtual address space from 39 to 48 bits, which significantly improves the address space layout randomization. + Incompatible with 32-bit apps + Required by hardened_malloc + + Native debugging is always blocked for preinstalled apps + + Memory tagging + + Memory tagging provides strong protections against exploitation of heap memory bugs (e.g. use-after-free, buffer overflow). + + Memory tagging is always enabled for preinstalled apps + + Memory tagging is always enabled for apps that don’t have native code + + This app has opted-in to memory tagging + + Memory tagging in third-party apps + Enable by default + + This setting doesn’t apply to apps that are known to be compatible with memory tagging + "Memory tagging provides strong protections against exploitation of heap memory bugs (e.g. use-after-free, buffer overflow). + +Memory tagging is always enabled for preinstalled apps and for third-party apps that are known to be +compatible with it (e.g. apps that don’t have native code), regardless of this setting." + + Enabled by default + + Disabled by default, except for apps that are known to be compatible with memory tagging + + diff --git a/res/values/strings_ext.xml b/res/values/strings_ext.xml new file mode 100644 index 00000000000..9f4813ba8f1 --- /dev/null +++ b/res/values/strings_ext.xml @@ -0,0 +1,139 @@ + + + Hardware SKU + Never + + Enable + Enabled + Disabled + + Allow Sensors permission to apps by default + Sensors is a non-standard permission, apps may malfunction if it’s denied. + A permission prompt will be shown when an app tries to access sensors. Note that some apps may need to be manually restarted after allowing the Sensors permission. + + Allow camera access when the device is locked + + Auto reboot + Automatically reboot the device if it hasn\'t been unlocked within the selected duration of time. + + Save screenshot timestamp to EXIF + Enables adding a timestamp to screenshot EXIF metadata + + Secure User Plane Location (SUPL) + A-GNSS (assisted satellite geolocation) based on nearby cell towers. + + Enabled (GrapheneOS proxy) + Enabled (standard server) + Disabled + Will make acquiring location lock significantly slower, especially if PSDS is disabled too + + Scramble PIN input layout + + Enable privileged eSIM management + Requires sandboxed Google Play installation + Device restart is required to disable this setting. + Restart + Device restart is required to allow enabling this setting. + + Attestation key provisioning + Enabled (GrapheneOS proxy) + Enabled (Google server) + Disabled + + Special access to hardware accelerators for Google apps + Access granted + Access not granted + Grant special access to hardware accelerators to Google apps + +"Some Google apps expect to be able to access hardware accelerators in a special, more capable way than regular apps do. + +Examples of such apps: +• Pixel Camera +• Google Photos +• Google Recorder + +This setting applies to all users on the device." + + + Predicted Satellite Data Service (PSDS) + +"If PSDS is enabled, static PSDS information files will be downloaded periodically to improve location resolution speed and accuracy. +No query or data is sent to the server. These files contain orbits and statuses of satellites, Earth environmental data and time adjustment information." + + Enabled (GrapheneOS server) + Enabled (Qualcomm server) + Enabled (standard server) + Disabled + Will make acquiring location lock significantly slower, especially if SUPL is disabled too + + Contact Scopes + + GrapheneOS server + Standard (Google) server + Disabled + + Internet connectivity checks + +"Connectivity check is a special empty server request that is made to find out whether the current network has internet connection. +There are HTTP, HTTPS, DNS, DNS-over-TLS and DNS-over-HTTPS connectivity checks. + + + Turn off Wi-Fi automatically + If Wi-Fi is disconnected, it will be turned off after the selected timeout. + + Turn off Bluetooth automatically + +"If there are no connected devices, Bluetooth will turn off after the selected timeout." + + + USB peripherals + This feature exists to reduce attack surface, especially for a locked device. + + Allow new USB peripherals + Allow new USB peripherals when unlocked + Disallow new USB peripherals + + Secure app spawning + Use secure app spawning + Launch apps in a more secure way than Android which takes slightly longer and increases memory usage by app processes. + + Native code debugging + Block for third-party apps by default + Blocked by default + Allowed for third-party apps by default + +Native code debugging (ptrace) slightly weakens the app sandbox. Some apps (most notably banking apps) use it as a tampering detection tool. + + + Widevine provisioning + Enabled (GrapheneOS proxy) + Enabled (Google server) + The provisioned certificate will be used for accessing DRM protected content. + + + App installs and updates + + Enabled + Enabled for first party sources + Disabled + + Enable app installs and updates + Enable app installs and updates from first party sources only + Disable app installs and updates + + Allow running in background + + Automatic exploit protection compatibility mode + + +"Allow automatically disabling exploit protections for apps that are known to need it" + + + "Warning: enabling this setting might cause some apps to stop working. + + Default value can be overridden per-app from the App info screen + + Notify about system process crashes + Such crashes may indicate exploitation attempts + + diff --git a/res/values/strings_keyguard_fingerprint.xml b/res/values/strings_keyguard_fingerprint.xml new file mode 100644 index 00000000000..edf85b2602a --- /dev/null +++ b/res/values/strings_keyguard_fingerprint.xml @@ -0,0 +1,4 @@ + + Use for screen unlocking + When disabled, fingerprint unlock can still be used in apps + diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index 73241b705f4..32d7dccc505 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -59,6 +59,17 @@ settings:summaryLineCount="1" settings:controller="com.android.settings.applications.appinfo.AppPermissionPreferenceController" /> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/bluetooth_screen.xml b/res/xml/bluetooth_screen.xml index 86dc8a12c72..fa7f59e3b91 100644 --- a/res/xml/bluetooth_screen.xml +++ b/res/xml/bluetooth_screen.xml @@ -36,6 +36,11 @@ settings:searchable="false" settings:controller="com.android.settings.connecteddevice.AddDevicePreferenceController"/> + + + + + app:lottie_rawRes="@raw/lottie_quickly_open_camera"/> - - - + + + + + + diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index fe87efd73d7..eff9d20af55 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -66,6 +66,16 @@ android:title="@string/location_services_preference_title" settings:controller="com.android.settings.location.LocationServicesPreferenceController"/> + + + + + + + + + + + + diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index 121e1fe9628..e35c53a89e2 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -57,6 +57,12 @@ android:summary="@string/battery_percentage_description" settings:controller="com.android.settings.display.BatteryPercentagePreferenceController" /> + + + + + + + + + + diff --git a/res/xml/security_dashboard_settings.xml b/res/xml/security_dashboard_settings.xml index 0550441d3f2..ec724f0536f 100644 --- a/res/xml/security_dashboard_settings.xml +++ b/res/xml/security_dashboard_settings.xml @@ -21,12 +21,6 @@ android:key="security_dashboard_page" android:title="@string/security_settings_title"> - - - @@ -55,11 +49,63 @@ android:summary="@string/summary_placeholder" settings:keywords="@string/keywords_face_settings" /> + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/res/xml/security_settings_fingerprint.xml b/res/xml/security_settings_fingerprint.xml index 0156ef97ac6..0577041c5d5 100644 --- a/res/xml/security_settings_fingerprint.xml +++ b/res/xml/security_settings_fingerprint.xml @@ -32,8 +32,7 @@ + settings:controller="com.android.settings.biometrics.fingerprint.FingerprintUnlockCategoryController"> + + + + + diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index d050a1f2747..ebae388688f 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -18,7 +18,8 @@ + android:key="top_level_settings" + android:title="Settings"> + + + + diff --git a/res/xml/wifi_configure_settings.xml b/res/xml/wifi_configure_settings.xml index 2ab7b6a28fe..d8833ef3216 100644 --- a/res/xml/wifi_configure_settings.xml +++ b/res/xml/wifi_configure_settings.xml @@ -34,6 +34,11 @@ settings:keywords="@string/keywords_wifi_notify_open_networks" settings:controller="com.android.settings.wifi.NotifyOpenNetworksPreferenceController"/> + + getEnabledPackageAllowlist() { if (mPm.getWellbeingPackageName() != null) { keepEnabledPackages.add(mPm.getWellbeingPackageName()); } + + // Bundled keyboard, needed for text input in Direct Boot mode if the selected 3rd party + // keyboard doesn't support it + keepEnabledPackages.add("com.android.inputmethod.latin"); + + // Replacing WebView is not supported + keepEnabledPackages.add("app.vanadium.webview"); + + // Only bundled camera can handle some of camera intents + keepEnabledPackages.add("app.grapheneos.camera"); + + // Disabling GmsCompat app breaks the gmscompat layer + keepEnabledPackages.add(com.android.internal.gmscompat.GmsCompatApp.PKG_NAME); + // Disabling GmsCompatConfig breaks updates through Apps app + keepEnabledPackages.add(com.android.internal.gmscompat.GmsCompatApp.PKG_NAME + ".config"); + + // EuiccSupportPixel handles firmware updates of embedded secure element that is used for eSIM, NFC, Felica etc + keepEnabledPackages.add(GoogleEuicc.EUICC_SUPPORT_PIXEL_PKG_NAME); + return keepEnabledPackages; } diff --git a/src/com/android/settings/applications/GmsCompatAppController.java b/src/com/android/settings/applications/GmsCompatAppController.java new file mode 100644 index 00000000000..ff8232d3601 --- /dev/null +++ b/src/com/android/settings/applications/GmsCompatAppController.java @@ -0,0 +1,65 @@ +/* + * 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.applications; + +import android.app.compat.gms.GmsCompat; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; +import android.text.TextUtils; + +import androidx.preference.Preference; + +import com.android.internal.gmscompat.GmsCompatApp; +import com.android.internal.gmscompat.GmsInfo; +import com.android.settings.core.BasePreferenceController; + +public class GmsCompatAppController extends BasePreferenceController { + private final Context context; + + public GmsCompatAppController(Context context, String key) { + super(context, key); + this.context = context; + } + + @Override + public int getAvailabilityStatus() { + UserHandle workProfile = getWorkProfileUser(); + int userId = workProfile != null ? + workProfile.getIdentifier() : + UserHandle.myUserId(); + + return GmsCompat.isGmsApp(GmsInfo.PACKAGE_GMS_CORE, userId) ? + AVAILABLE : DISABLED_FOR_USER; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + Intent intent = new Intent(GmsCompatApp.PKG_NAME + ".SETTINGS_LINK"); + intent.setPackage(GmsCompatApp.PKG_NAME); + + UserHandle workProfile = getWorkProfileUser(); + if (workProfile != null) { + context.startActivityAsUser(intent, workProfile); + } else { + context.startActivity(intent); + } + return true; + } +} diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java index ff191ab4476..fa415d6e053 100644 --- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java @@ -205,6 +205,15 @@ public void onDestroy() { } private class UninstallAndDisableButtonListener implements View.OnClickListener { + private boolean mChangeEnabledStateOfUserApp; + + UninstallAndDisableButtonListener() { + this(false); + } + + UninstallAndDisableButtonListener(boolean changeEnabledStateOfUserApp) { + mChangeEnabledStateOfUserApp = changeEnabledStateOfUserApp; + } @Override public void onClick(View v) { @@ -236,8 +245,13 @@ public void onClick(View v) { mUserId); if (admin != null && !uninstallBlockedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, admin); - } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 || mChangeEnabledStateOfUserApp) { if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { + if (mChangeEnabledStateOfUserApp) { + handleDialogClick(ButtonActionDialogFragment.DialogType.DISABLE); + return; + } + showDialogInner(ButtonActionDialogFragment.DialogType.DISABLE); } else { mMetricsFeatureProvider.action( @@ -468,6 +482,25 @@ void updateUninstallButton() { } mButtonsPref.setButton2Enabled(enabled); + + if (enabled && !isBundled) { + // "enabled" means "show uninstall button" in this context + int text; + int icon; + if (mAppEntry.info.enabled) { + text = R.string.disable_text; + icon = R.drawable.ic_settings_disable; + } else { + text = R.string.enable_text; + icon = R.drawable.ic_settings_enable; + } + mButtonsPref + .setButton4Text(text) + .setButton4Icon(icon) + .setButton4Visible(true) + .setButton4OnClickListener(new UninstallAndDisableButtonListener(true)) + ; + } } /** @@ -491,7 +524,9 @@ private void refreshAndFinishIfPossible(boolean removeTaskWhenFinishing) { @VisibleForTesting void updateForceStopButton() { - if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { + if (!mPackageInfo.applicationInfo.enabled) { + mButtonsPref.setButton3Visible(false); + } else if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { // User can't force stop device admin. Log.w(TAG, "User can't force stop device admin"); updateForceStopButtonInner(false /* enabled */); @@ -516,6 +551,7 @@ void updateForceStopButton() { @VisibleForTesting void updateForceStopButtonInner(boolean enabled) { + mButtonsPref.setButton3Visible(true); if (mAppsControlDisallowedBySystem) { mButtonsPref.setButton3Enabled(false); } else { @@ -530,6 +566,7 @@ void uninstallPkg(String packageName, boolean allUsers) { Uri packageUri = Uri.parse("package:" + packageName); Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); + uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_SHOW_MORE_OPTIONS_BUTTON, false); mMetricsFeatureProvider.action(mActivity, SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP); mFragment.startActivityForResult(uninstallIntent, mRequestUninstall); diff --git a/src/com/android/settings/applications/appinfo/AppContactScopesPreferenceController.java b/src/com/android/settings/applications/appinfo/AppContactScopesPreferenceController.java new file mode 100644 index 00000000000..c6e6d847e27 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppContactScopesPreferenceController.java @@ -0,0 +1,24 @@ +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.content.pm.GosPackageState; +import android.ext.cscopes.ContactScopesApi; + +import com.android.settings.ext.AppInfoPreferenceControllerBase2; + +public class AppContactScopesPreferenceController extends AppInfoPreferenceControllerBase2 { + public AppContactScopesPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return hasGosPackageStateFlags(GosPackageState.FLAG_CONTACT_SCOPES_ENABLED) ? + AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void onPreferenceClick(String packageName) { + mContext.startActivity(ContactScopesApi.createConfigActivityIntent(packageName)); + } +} diff --git a/src/com/android/settings/applications/appinfo/AppExploitProtectionCompatModePrefController.java b/src/com/android/settings/applications/appinfo/AppExploitProtectionCompatModePrefController.java new file mode 100644 index 00000000000..02b57d87a18 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppExploitProtectionCompatModePrefController.java @@ -0,0 +1,63 @@ +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; + +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +public class AppExploitProtectionCompatModePrefController extends AppInfoPreferenceControllerBase + implements Preference.OnPreferenceChangeListener { + + public AppExploitProtectionCompatModePrefController(Context ctx, String key) { + super(ctx, key); + } + + @Override + public int getAvailabilityStatus() { + ApplicationInfo ai = mParent.getPackageInfo().applicationInfo; + return ai.isSystemApp() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + GosPackageState ps = GosPackageState.get(getPackageName()); + + boolean checked = ps != null && ps.hasFlags(GosPackageState.FLAG_ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE); + + ((SwitchPreference) preference).setChecked(checked); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean checked = (boolean) newValue; + + Runnable r = () -> { + GosPackageState.edit(getPackageName()) + .setFlagsState(GosPackageState.FLAG_ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE, checked) + .killUidAfterApply() + .apply(); + + if (!mParent.refreshUi()) { + mParent.finish(); + } + }; + + if (checked) { + var d = AswExploitProtectionFragment.getExploitProtectionWarningOnDisable(mContext, r); + d.show(); + return false; + } + + r.run(); + + return true; + } + + private String getPackageName() { + return mParent.getPackageInfo().packageName; + } +} diff --git a/src/com/android/settings/applications/appinfo/AppExploitProtectionPrefCategoryController.java b/src/com/android/settings/applications/appinfo/AppExploitProtectionPrefCategoryController.java new file mode 100644 index 00000000000..2c2843f86ea --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppExploitProtectionPrefCategoryController.java @@ -0,0 +1,10 @@ +package com.android.settings.applications.appinfo; + +import android.content.Context; + +public class AppExploitProtectionPrefCategoryController extends AdvancedAppInfoPreferenceCategoryController { + + public AppExploitProtectionPrefCategoryController(Context ctx, String key) { + super(ctx, key); + } +} diff --git a/src/com/android/settings/applications/appinfo/AppExtendedVaSpace.kt b/src/com/android/settings/applications/appinfo/AppExtendedVaSpace.kt new file mode 100644 index 00000000000..b1257617f43 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppExtendedVaSpace.kt @@ -0,0 +1,41 @@ +package com.android.settings.applications.appinfo + +import android.content.Context +import android.ext.settings.app.AppSwitch +import android.ext.settings.app.AswUseExtendedVaSpace +import com.android.settings.R +import com.android.settings.ext.ExtSettingControllerHelper +import com.android.settingslib.widget.FooterPreference + +class AswAdapterUseExtendedVaSpace(ctx: Context) : AswAdapter(ctx) { + + override fun getAppSwitch() = AswUseExtendedVaSpace.I + + override fun getAswTitle() = getText(R.string.aep_ext_va_space) +} + +class AppExtendedVaSpacePrefController(ctx: Context, key: String) : + AswPrefController(ctx, key, AswAdapterUseExtendedVaSpace(ctx)) { + + override fun getDetailFragmentClass() = AppExtendedVaSpaceFragment::class.java + + override fun getAvailabilityStatus() = ExtSettingControllerHelper.getDevModeSettingAvailability(mContext) +} + +class AppExtendedVaSpaceFragment : AswExploitProtectionFragment() { + + override fun createAswAdapter(ctx: Context) = AswAdapterUseExtendedVaSpace(ctx) + + override fun getSummaryForImmutabilityReason(ir: Int): CharSequence? { + val id = when (ir) { + AppSwitch.IR_REQUIRED_BY_HARDENED_MALLOC -> R.string.aep_ext_va_space_ir_hardened_malloc + AppSwitch.IR_NON_64_BIT_NATIVE_CODE -> R.string.aep_ext_va_space_ir_32_bit_native_code + else -> return null + } + return getText(id) + } + + override fun updateFooter(fp: FooterPreference) { + fp.setTitle(R.string.aep_ext_va_space_footer) + } +} diff --git a/src/com/android/settings/applications/appinfo/AppHardenedMalloc.kt b/src/com/android/settings/applications/appinfo/AppHardenedMalloc.kt new file mode 100644 index 00000000000..c7b169aaacd --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppHardenedMalloc.kt @@ -0,0 +1,41 @@ +package com.android.settings.applications.appinfo + +import android.content.Context +import android.ext.settings.app.AppSwitch +import android.ext.settings.app.AswUseHardenedMalloc +import com.android.settings.R +import com.android.settingslib.widget.FooterPreference + +class AswAdapterUseHardenedMalloc(ctx: Context) : AswAdapter(ctx) { + + override fun getAppSwitch() = AswUseHardenedMalloc.I + + override fun getAswTitle() = getText(R.string.aep_hmalloc) +} + +class AppHardenedMallocPrefController(ctx: Context, key: String) : + AswPrefController(ctx, key, AswAdapterUseHardenedMalloc(ctx)) { + + override fun getDetailFragmentClass() = AppHardenedMallocFragment::class.java +} + +class AppHardenedMallocFragment : AswExploitProtectionFragment() { + + override fun createAswAdapter(ctx: Context) = AswAdapterUseHardenedMalloc(ctx) + + override fun getSummaryForImmutabilityReason(ir: Int): CharSequence? { + val id = when (ir) { + AppSwitch.IR_IS_SYSTEM_APP -> R.string.aep_hmalloc_ir_preinstalled_app + AppSwitch.IR_NO_NATIVE_CODE -> R.string.aep_hmalloc_ir_no_native_code + AppSwitch.IR_NON_64_BIT_NATIVE_CODE -> R.string.aep_hmalloc_ir_32_bit_native_code + AppSwitch.IR_IS_DEBUGGABLE_APP -> R.string.aep_hmalloc_ir_debuggable_app + else -> return null + } + return getText(id) + } + + override fun updateFooter(fp: FooterPreference) { + fp.setTitle(R.string.aep_hmalloc_footer) + setLearnMoreLink(fp, "https://grapheneos.org/features#exploit-mitigations") + } +} diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index e771ff47761..d665511a72e 100644 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -178,6 +178,8 @@ public void onAttach(Context context) { use(AppAllServicesPreferenceController.class).setPackageName(packageName); use(AppStoragePreferenceController.class).setParentFragment(this); use(AppVersionPreferenceController.class).setParentFragment(this); + use(AppStorageScopesPreferenceController.class).setParentFragment(this); + use(AppContactScopesPreferenceController.class).setParentFragment(this); use(InstantAppDomainsPreferenceController.class).setParentFragment(this); final HibernationSwitchPreferenceController appHibernationSettings = @@ -226,11 +228,31 @@ public void onAttach(Context context) { externalSource, acrossProfiles, alarmsAndReminders, longBackgroundTasks)); advancedAppInfo.setAppEntry(mAppEntry); + final AppExploitProtectionPrefCategoryController aepCategory = + use(AppExploitProtectionPrefCategoryController.class); + aepCategory.setAppEntry(mAppEntry); + + AppInfoPreferenceControllerBase[] aepPrefControllers = { + prepare(AppExploitProtectionCompatModePrefController.class), + prepare(AppHardenedMallocPrefController.class), + prepare(AppExtendedVaSpacePrefController.class), + prepare(AppNativeDebuggingPrefController.class), + prepare(AppMemtagPrefController.class), + }; + + aepCategory.setChildren(Arrays.asList(aepPrefControllers)); + final AppLocalePreferenceController appLocale = use(AppLocalePreferenceController.class); appLocale.setParentFragment(this); } + private AppInfoPreferenceControllerBase prepare(Class cls) { + T res = use(cls); + res.setParentFragment(this); + return res; + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); diff --git a/src/com/android/settings/applications/appinfo/AppMemoryTagging.kt b/src/com/android/settings/applications/appinfo/AppMemoryTagging.kt new file mode 100644 index 00000000000..51e2bc8f33c --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppMemoryTagging.kt @@ -0,0 +1,51 @@ +package com.android.settings.applications.appinfo + +import android.content.Context +import android.ext.settings.app.AswUseMemoryTagging +import android.ext.settings.app.AppSwitch +import com.android.internal.os.Zygote +import com.android.settings.R +import com.android.settingslib.widget.FooterPreference + +class AswAdapterUseMemoryTagging(ctx: Context) : AswAdapter(ctx) { + + override fun getAppSwitch() = AswUseMemoryTagging.I + + override fun getAswTitle() = getText(R.string.aep_memtag) +} + +class AppMemtagPrefController(ctx: Context, key: String) : + AswPrefController(ctx, key, AswAdapterUseMemoryTagging(ctx)) { + + override fun getDetailFragmentClass() = AppMemtagFragment::class.java + + private val isSupported = Zygote.nativeSupportsMemoryTagging() + override fun getAvailabilityStatus() = if (isSupported) AVAILABLE else UNSUPPORTED_ON_DEVICE +} + +class AppMemtagFragment : AswExploitProtectionFragment() { + + override fun createAswAdapter(ctx: Context) = AswAdapterUseMemoryTagging(ctx) + + override fun getSummaryForImmutabilityReason(ir: Int): CharSequence? { + val id = when (ir) { + AppSwitch.IR_IS_SYSTEM_APP -> R.string.aep_memtag_dvr_is_system_app + AppSwitch.IR_NO_NATIVE_CODE -> R.string.aep_memtag_dvr_no_native_code + AppSwitch.IR_OPTED_IN_VIA_MANIFEST -> R.string.aep_memtag_dvr_manifest_opt_in + else -> return null + } + return getText(id) + } + + override fun getSummaryForDefaultValueReason(dvr: Int): CharSequence? { + val id = when (dvr) { + AppSwitch.DVR_DEFAULT_SETTING -> R.string.aep_dvr_default_security_setting + else -> return null + } + return getText(id) + } + + override fun updateFooter(fp: FooterPreference) { + fp.setTitle(R.string.aep_memtag_footer) + } +} diff --git a/src/com/android/settings/applications/appinfo/AppNativeDebugging.kt b/src/com/android/settings/applications/appinfo/AppNativeDebugging.kt new file mode 100644 index 00000000000..c99420935b2 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppNativeDebugging.kt @@ -0,0 +1,48 @@ +package com.android.settings.applications.appinfo + +import android.content.Context +import android.ext.settings.app.AswDenyNativeDebug +import android.ext.settings.app.AppSwitch +import com.android.settings.R +import com.android.settingslib.widget.FooterPreference + +class AswAdapterNativeDebugging(ctx: Context) : AswAdapter(ctx) { + + override fun getAppSwitch() = AswDenyNativeDebug.I + + override fun getAswTitle() = getText(R.string.native_debugging_title) + + override fun getOnTitle() = getText(R.string.aep_blocked) + override fun getOffTitle() = getText(R.string.aep_allowed) +} + +class AppNativeDebuggingPrefController(ctx: Context, key: String) : + AswPrefController(ctx, key, AswAdapterNativeDebugging(ctx)) { + + override fun getDetailFragmentClass() = AppNativeDebuggingFragment::class.java +} + +class AppNativeDebuggingFragment : AswExploitProtectionFragment() { + + override fun createAswAdapter(ctx: Context) = AswAdapterNativeDebugging(ctx) + + override fun getSummaryForImmutabilityReason(ir: Int): CharSequence? { + val id = when (ir) { + AppSwitch.IR_IS_SYSTEM_APP -> R.string.native_debug_dvr_is_system_app + else -> return null + } + return getText(id) + } + + override fun getSummaryForDefaultValueReason(dvr: Int): CharSequence? { + val id = when (dvr) { + AppSwitch.DVR_DEFAULT_SETTING -> R.string.aep_dvr_default_security_setting + else -> return null + } + return getText(id) + } + + override fun updateFooter(fp: FooterPreference) { + fp.setTitle(R.string.native_debugging_footer) + } +} diff --git a/src/com/android/settings/applications/appinfo/AppStorageScopesPreferenceController.java b/src/com/android/settings/applications/appinfo/AppStorageScopesPreferenceController.java new file mode 100644 index 00000000000..e7f8b9cf507 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppStorageScopesPreferenceController.java @@ -0,0 +1,24 @@ +package com.android.settings.applications.appinfo; + +import android.app.StorageScope; +import android.content.Context; +import android.content.pm.GosPackageState; + +import com.android.settings.ext.AppInfoPreferenceControllerBase2; + +public class AppStorageScopesPreferenceController extends AppInfoPreferenceControllerBase2 { + public AppStorageScopesPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return hasGosPackageStateFlags(GosPackageState.FLAG_STORAGE_SCOPES_ENABLED) ? + AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void onPreferenceClick(String packageName) { + mContext.startActivity(StorageScope.createConfigActivityIntent(packageName)); + } +} diff --git a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java index 23dd9602f77..734bd6ff476 100644 --- a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java @@ -22,6 +22,9 @@ import com.android.settings.R; +import java.text.DateFormat; +import java.util.Date; + public class AppVersionPreferenceController extends AppInfoPreferenceControllerBase { public AppVersionPreferenceController(Context context, String key) { @@ -36,7 +39,40 @@ public CharSequence getSummary() { if (packageInfo == null) { return null; } - return mContext.getString(R.string.version_text, - BidiFormatter.getInstance().unicodeWrap(packageInfo.versionName)); + + Context ctx = mContext; + + DateFormat dateFormat = android.text.format.DateFormat.getMediumDateFormat(ctx); + DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(ctx); + + String times = null; + if (packageInfo.firstInstallTime != 0) { + String s = formatDate(packageInfo.firstInstallTime, dateFormat, timeFormat); + times = ctx.getString(R.string.app_info_install_time, s); + } + + if (packageInfo.lastUpdateTime != 0 && packageInfo.lastUpdateTime != packageInfo.firstInstallTime) { + String s = formatDate(packageInfo.lastUpdateTime, dateFormat, timeFormat); + String updateTime = ctx.getString(R.string.app_info_update_time, s); + if (times != null) { + times += '\n' + updateTime; + } else { + times = updateTime; + } + } + + return ctx.getString(R.string.version_text, + BidiFormatter.getInstance().unicodeWrap(packageInfo.versionName)) + + "\n\n" + packageInfo.packageName + + "\nversionCode " + packageInfo.getLongVersionCode() + + "\n\ntargetSdk " + packageInfo.applicationInfo.targetSdkVersion + + "\nminSdk " + packageInfo.applicationInfo.minSdkVersion + + (times != null ? ("\n\n" + times) : "") + ; + } + + private static String formatDate(long unixTs, DateFormat dateFormat, DateFormat timeFormat) { + Date d = new Date(unixTs); + return dateFormat.format(d) + "; " + timeFormat.format(d); } } diff --git a/src/com/android/settings/applications/appinfo/AswAdapter.kt b/src/com/android/settings/applications/appinfo/AswAdapter.kt new file mode 100644 index 00000000000..35b74b7091a --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AswAdapter.kt @@ -0,0 +1,38 @@ +package com.android.settings.applications.appinfo + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.GosPackageState +import android.ext.settings.app.AppSwitch +import androidx.annotation.StringRes +import com.android.settings.R + +abstract class AswAdapter(val context: Context, val userId: Int = context.userId) { + abstract fun getAppSwitch(): T + + fun getPreferenceSummary(appInfo: ApplicationInfo): CharSequence { + val asw = getAppSwitch() + val si = AppSwitch.StateInfo() + val isOn = asw.get(context, userId, appInfo, GosPackageState.get(appInfo.packageName, userId), si) + return if (si.isUsingDefaultValue) { + getDefaultTitle(isOn) + } else { + if (isOn) getOnTitle() else getOffTitle() + } + } + + abstract fun getAswTitle(): CharSequence + + fun getDefaultTitle(isOn: Boolean): CharSequence { + return context.getString(R.string.aep_default, + if (isOn) getOnTitle() else getOffTitle()) + } + + open fun getOnTitle(): CharSequence = getText(R.string.aep_enabled) + + open fun getOffTitle(): CharSequence = getText(R.string.aep_disabled) + + protected fun getText(@StringRes id: Int): CharSequence = context.getText(id) + + protected fun getString(@StringRes id: Int): String = context.getString(id) +} diff --git a/src/com/android/settings/applications/appinfo/AswAppInfoFragment.java b/src/com/android/settings/applications/appinfo/AswAppInfoFragment.java new file mode 100644 index 00000000000..d8d101eb676 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AswAppInfoFragment.java @@ -0,0 +1,155 @@ +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; +import android.ext.settings.app.AppSwitch; +import android.os.Bundle; + +import androidx.annotation.Nullable; + +import com.android.settings.R; + +public abstract class AswAppInfoFragment + extends RadioButtonAppInfoFragment { + + protected static final int ID_DEFAULT = 0; + protected static final int ID_ON = 1; + protected static final int ID_OFF = 2; + + public abstract AswAdapter createAswAdapter(Context ctx); + + protected AswAdapter adapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + adapter = createAswAdapter(requireContext()); + super.onCreate(savedInstanceState); + } + + @Override + protected CharSequence getTitle() { + return adapter.getAswTitle(); + } + + @Override + public Entry[] getEntries() { + Context ctx = adapter.getContext(); + AppSwitch asw = adapter.getAppSwitch(); + + int userId = mUserId; + var ps = GosPackageState.get(mPackageName, userId); + ApplicationInfo appInfo = getAppInfo(); + var si = new AppSwitch.StateInfo(); + boolean state = asw.get(ctx, userId, appInfo, ps, si); + + boolean isDefault = si.isUsingDefaultValue(); + boolean isImmutable = si.isImmutable(); + + var defaultSi = new AppSwitch.StateInfo(); + boolean defaultValue = asw.getDefaultValue(ctx, userId, appInfo, ps, defaultSi); + + var def = createEntry(ID_DEFAULT, adapter.getDefaultTitle(defaultValue)); + def.isChecked = isDefault; + def.isEnabled = !isImmutable; + if (def.isEnabled) { + int dvr = defaultSi.getDefaultValueReason(); + CharSequence summary = getSummaryForDefaultValueReason(dvr); + if (summary == null) { + summary = switch (dvr) { + case AppSwitch.DVR_APP_COMPAT_CONFIG_HARDENING_OPT_IN -> + getText(R.string.aep_dvr_compat_config_hardening_opt_in); + case AppSwitch.DVR_APP_COMPAT_CONFIG_HARDENING_OPT_OUT -> { + var s = defaultValue ? adapter.getOnTitle() : adapter.getOffTitle(); + yield getString(R.string.aep_dvr_compat_config_hardening_opt_out, s.toString()); + } + default -> null; + }; + } + def.summary = summary; + } + + var enabled = createEntry(ID_ON, adapter.getOnTitle()); + enabled.isChecked = !isDefault && state; + enabled.isEnabled = enabled.isChecked || !isImmutable; + + var disabled = createEntry(ID_OFF, adapter.getOffTitle()); + disabled.isChecked = !isDefault && !state; + disabled.isEnabled = disabled.isChecked || !isImmutable; + + if (isImmutable) { + int immutabilityReason = si.getImmutabilityReason(); + CharSequence summary = getSummaryForImmutabilityReason(immutabilityReason); + if (summary == null) { + if (immutabilityReason == AppSwitch.IR_EXPLOIT_PROTECTION_COMPAT_MODE) { + summary = getString(R.string.aep_ir_exploit_protection_compat_mode, + getString(R.string.aep_compat_mode_title)); + } + } + if (enabled.isChecked) { + enabled.summary = summary; + } + if (disabled.isChecked) { + disabled.summary = summary; + } + } + + return new Entry[] { def, enabled, disabled }; + } + + @Nullable + protected CharSequence getSummaryForDefaultValueReason(int dvr) { + return null; + } + + @Nullable + protected CharSequence getSummaryForImmutabilityReason(int ir) { + return null; + } + + @Override + public final void onEntrySelected(int id) { + Context ctx = requireContext(); + AppSwitch asw = adapter.getAppSwitch(); + int userId = mUserId; + String pkgName = mPackageName; + GosPackageState ps = GosPackageState.get(pkgName, userId); + ApplicationInfo appInfo = getAppInfo(); + + boolean isImmutable = asw.isImmutable(ctx, userId, appInfo, ps); + + if (isImmutable) { + return; + } + + Runnable r = () -> { + GosPackageState.Editor ed = GosPackageState.edit(pkgName, userId); + + switch (id) { + case ID_DEFAULT -> asw.setUseDefaultValue(ed); + case ID_ON, ID_OFF -> asw.set(ed, id == ID_ON); + default -> throw new IllegalStateException(); + } + + ed.setKillUidAfterApply(shouldKillUidAfterChange()); + + if (!ed.apply()) { + finish(); + } + + if (!refreshUi()) { + setIntentAndFinish(true); + } + }; + + completeStateChange(id, asw.get(ctx, userId, appInfo, ps), r); + } + + protected void completeStateChange(int newEntryId, boolean curValue, Runnable stateChangeAction) { + stateChangeAction.run(); + } + + protected boolean shouldKillUidAfterChange() { + return true; + } +} diff --git a/src/com/android/settings/applications/appinfo/AswExploitProtectionFragment.java b/src/com/android/settings/applications/appinfo/AswExploitProtectionFragment.java new file mode 100644 index 00000000000..6369749592f --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AswExploitProtectionFragment.java @@ -0,0 +1,44 @@ +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.content.pm.GosPackageState; +import android.ext.settings.app.AppSwitch; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; + +public abstract class AswExploitProtectionFragment extends AswAppInfoFragment { + + @Override + protected void completeStateChange(int newEntryId, boolean curValue, Runnable stateChangeAction) { + Context ctx = requireContext(); + + boolean showWarning = false; + if (curValue) { + if (newEntryId == ID_OFF) { + showWarning = true; + } else if (newEntryId == ID_DEFAULT) { + AppSwitch asw = adapter.getAppSwitch(); + int userId = mUserId; + var ps = GosPackageState.get(mPackageName, userId); + showWarning = !asw.getDefaultValue(ctx, userId, getAppInfo(), ps); + } + } + if (showWarning) { + var d = getExploitProtectionWarningOnDisable(ctx, stateChangeAction); + d.show(); + } else { + stateChangeAction.run(); + } + } + + public static AlertDialog.Builder getExploitProtectionWarningOnDisable(Context ctx, Runnable action) { + var b = new AlertDialog.Builder(ctx); + b.setTitle(R.string.aep_confirm_disable_title); + b.setMessage(R.string.aep_confirm_disable_warning_msg); + b.setNegativeButton(R.string.cancel, null); + b.setPositiveButton(R.string.aep_confirm_disable_proceed_btn, (d, w) -> action.run()); + return b; + } +} diff --git a/src/com/android/settings/applications/appinfo/AswPrefController.kt b/src/com/android/settings/applications/appinfo/AswPrefController.kt new file mode 100644 index 00000000000..5b0a37e82b4 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AswPrefController.kt @@ -0,0 +1,11 @@ +package com.android.settings.applications.appinfo + +import android.content.Context +import android.ext.settings.app.AppSwitch + +abstract class AswPrefController(ctx: Context, key: String, + val adapter: AswAdapter, +) : AppInfoPreferenceControllerBase(ctx, key) { + + override fun getSummary() = adapter.getPreferenceSummary(mAppEntry.info) +} diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java index b7232744365..f1ab2820520 100644 --- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java +++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java @@ -21,6 +21,7 @@ import android.app.AppOpsManager; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.pm.GosPackageState; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -133,6 +134,9 @@ protected boolean refreshUi() { return true; } mSwitchPref.setChecked(mInstallAppsState.canInstallApps()); + + setupObbToggle(); + return true; } @@ -145,4 +149,55 @@ protected AlertDialog createDialog(int id, int errorCode) { public int getMetricsCategory() { return SettingsEnums.MANAGE_EXTERNAL_SOURCES; } + + private RestrictedSwitchPreference mObbPref; + + private void setupObbToggle() { + RestrictedSwitchPreference p = mObbPref; + if (p == null) { + if (!GosPackageState.attachableToPackage(mPackageName)) { + return; + } + p = createObbToggle(); + mSwitchPref.getParent().addPreference(p); + mObbPref = p; + } + + boolean canInstallApps = mInstallAppsState.canInstallApps(); + + if (!canInstallApps) { + p.setChecked(false); + p.callChangeListener(Boolean.FALSE); + } else { + GosPackageState ps = GosPackageState.get(mPackageName); + p.setChecked(ps != null && ps.hasFlag(GosPackageState.FLAG_ALLOW_ACCESS_TO_OBB_DIRECTORY)); + } + + p.setEnabled(canInstallApps); + } + + private RestrictedSwitchPreference createObbToggle() { + RestrictedSwitchPreference p = new RestrictedSwitchPreference(mSwitchPref.getContext()); + p.setTitle(R.string.allow_access_to_obb_directory_title); + p.setSummary(R.string.allow_access_to_obb_directory_summary); + + p.setOnPreferenceChangeListener((preference, checkedB) -> { + var ps = GosPackageState.get(mPackageName); + boolean curValue = ps != null && ps.hasFlag(GosPackageState.FLAG_ALLOW_ACCESS_TO_OBB_DIRECTORY); + + if (curValue == (boolean) checkedB) { + return true; + } + + GosPackageState.edit(mPackageName) + .setFlagsState(GosPackageState.FLAG_ALLOW_ACCESS_TO_OBB_DIRECTORY, (boolean) checkedB) + // storage mount modes can't be updated dynamically + .killUidAfterApply() + .apply(); + + return true; + }); + + return p; + } } diff --git a/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java b/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java index 6c840d5d94d..517f75ff5c1 100644 --- a/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java +++ b/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java @@ -16,6 +16,7 @@ package com.android.settings.applications.appinfo; import android.app.AppOpsManager; +import android.app.StorageScope; import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; @@ -68,6 +69,8 @@ public void onCreate(Bundle savedInstanceState) { mMetricsFeatureProvider = FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider(); + + addStorageScopesLink(); } @Override @@ -176,4 +179,11 @@ private static CharSequence getSummary(Context context, PermissionState state) { ? R.string.app_permission_summary_allowed : R.string.app_permission_summary_not_allowed); } + + private void addStorageScopesLink() { + Preference p = new Preference(requireContext()); + p.setTitle(R.string.storage_scopes); + p.setIntent(StorageScope.createConfigActivityIntent(mPackageName)); + getPreferenceScreen().addPreference(p); + } } diff --git a/src/com/android/settings/applications/appinfo/RadioButtonAppInfoFragment.java b/src/com/android/settings/applications/appinfo/RadioButtonAppInfoFragment.java new file mode 100644 index 00000000000..4e2da7917f1 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/RadioButtonAppInfoFragment.java @@ -0,0 +1,156 @@ +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.applications.AppInfoWithHeader; +import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +public abstract class RadioButtonAppInfoFragment extends AppInfoWithHeader implements SelectorWithWidgetPreference.OnClickListener { + + public static class Entry { + public final int id; + public CharSequence title; + @Nullable + public CharSequence summary; + public boolean isChecked; + public boolean isEnabled; + + public Entry(int id, CharSequence title, @Nullable CharSequence summary, boolean isChecked, boolean isEnabled) { + this.id = id; + this.title = title; + this.summary = summary; + this.isChecked = isChecked; + this.isEnabled = isEnabled; + } + } + + public static Entry createEntry(int id, CharSequence title) { + return new Entry(id, title, null, false, true); + } + + public Entry createEntry(int id, @StringRes int title) { + return createEntry(id, getText(title)); + } + + public Entry createEntry(@StringRes int title) { + return createEntry(title, title); + } + + public abstract Entry[] getEntries(); + + public boolean hasFooter() { + return true; + } + + public abstract void updateFooter(FooterPreference fp); + + public void setLearnMoreLink(FooterPreference p, String url) { + p.setLearnMoreAction(v -> { + var i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(i); + }); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getPrefContext()); + setPreferenceScreen(screen); + requireActivity().setTitle(getTitle()); + } + + protected abstract CharSequence getTitle(); + + @Nullable + private SelectorWithWidgetPreference[] radioButtons; + @Nullable + private FooterPreference footer; + + @Override + protected boolean refreshUi() { + Entry[] entries = getEntries(); + int entryCnt = entries.length; + + Context ctx = getPrefContext(); + PreferenceScreen screen = getPreferenceScreen(); + + if (radioButtons == null || radioButtons.length != entryCnt) { + if (radioButtons != null) { + for (Preference p : radioButtons) { + screen.removePreference(p); + } + } + + radioButtons = new SelectorWithWidgetPreference[entryCnt]; + + for (int i = 0; i < entryCnt; ++i) { + var p = new SelectorWithWidgetPreference(ctx); + p.setOnClickListener(this); + screen.addPreference(p); + radioButtons[i] = p; + } + } + + for (int i = 0; i < entryCnt; ++i) { + SelectorWithWidgetPreference p = radioButtons[i]; + Entry e = entries[i]; + p.setKey(Integer.toString(e.id)); + p.setTitle(e.title); + p.setSummary(e.summary); + p.setEnabled(e.isEnabled); + p.setChecked(e.isChecked); + } + + if (hasFooter()) { + if (footer == null) { + footer = new FooterPreference(ctx); + screen.addPreference(footer); + } + updateFooter(footer); + } else { + if (footer != null) { + screen.removePreference(footer); + footer = null; + } + } + + return true; + } + + @Override + public final void onRadioButtonClicked(SelectorWithWidgetPreference emiter) { + int id = Integer.parseInt(emiter.getKey()); + onEntrySelected(id); + if (!refreshUi()) { + setIntentAndFinish(true); + } + } + + public abstract void onEntrySelected(int id); + + @Override + protected AlertDialog createDialog(int id, int errorCode) { + return null; + } + + @Override + public int getMetricsCategory() { + return METRICS_CATEGORY_UNKNOWN; + } + + protected ApplicationInfo getAppInfo() { + return mPackageInfo.applicationInfo; + } +} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index d734a27f033..27dbd80be73 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -850,9 +850,11 @@ void updateOptionsMenu() { } mOptionsMenu.findItem(R.id.advanced).setVisible(false); - mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE + mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible( + (mListType == LIST_TYPE_STORAGE || mListType == LIST_TYPE_MAIN) && mSortOrder != R.id.sort_order_alpha); - mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE + mOptionsMenu.findItem(R.id.sort_order_size).setVisible( + (mListType == LIST_TYPE_STORAGE || mListType == LIST_TYPE_MAIN) && mSortOrder != R.id.sort_order_size); mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem diff --git a/src/com/android/settings/applications/specialaccess/GoogleSpecialAcceleratorAccessFragment.java b/src/com/android/settings/applications/specialaccess/GoogleSpecialAcceleratorAccessFragment.java new file mode 100644 index 00000000000..d681703f4cd --- /dev/null +++ b/src/com/android/settings/applications/specialaccess/GoogleSpecialAcceleratorAccessFragment.java @@ -0,0 +1,46 @@ +package com.android.settings.applications.specialaccess; + +import android.app.ActivityThread; +import android.ext.settings.BoolSetting; +import android.ext.settings.ExtSettings; +import android.os.RemoteException; + +import com.android.internal.util.GoogleCameraUtils; +import com.android.settings.R; +import com.android.settings.ext.BoolSettingFragment; +import com.android.settingslib.widget.FooterPreference; + +public class GoogleSpecialAcceleratorAccessFragment extends BoolSettingFragment { + private static final String TAG = GoogleSpecialAcceleratorAccessFragment.class.getSimpleName(); + + @Override + protected BoolSetting getSetting() { + return ExtSettings.ALLOW_GOOGLE_APPS_SPECIAL_ACCESS_TO_ACCELERATORS; + } + + @Override + protected CharSequence getTitle() { + return resText(R.string.Google_apps_special_accelerator_access_title); + } + + @Override + protected CharSequence getMainSwitchTitle() { + return resText(R.string.Google_apps_special_accelerator_access_main_switch); + } + + @Override + protected void onMainSwitchChanged(boolean state) { + if (GoogleCameraUtils.isCustomSeInfoNeededForAccessToAccelerators(requireContext())) { + try { + ActivityThread.getPackageManager().updateSeInfo(GoogleCameraUtils.PACKAGE_NAME); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + @Override + protected FooterPreference makeFooterPref(FooterPreference.Builder builder) { + return builder.setTitle(R.string.Google_apps_special_accelerator_access_footer).build(); + } +} diff --git a/src/com/android/settings/applications/specialaccess/GoogleSpecialAcceleratorAccessPrefController.java b/src/com/android/settings/applications/specialaccess/GoogleSpecialAcceleratorAccessPrefController.java new file mode 100644 index 00000000000..ac7d84fcbb0 --- /dev/null +++ b/src/com/android/settings/applications/specialaccess/GoogleSpecialAcceleratorAccessPrefController.java @@ -0,0 +1,34 @@ +package com.android.settings.applications.specialaccess; + +import android.content.Context; +import android.ext.settings.ExtSettings; + +import com.android.settings.R; +import com.android.settings.ext.BoolSettingFragmentPrefController; + +public class GoogleSpecialAcceleratorAccessPrefController extends BoolSettingFragmentPrefController { + + public GoogleSpecialAcceleratorAccessPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.ALLOW_GOOGLE_APPS_SPECIAL_ACCESS_TO_ACCELERATORS); + } + + @Override + public int getAvailabilityStatus() { + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_Google_apps_can_have_special_access_to_accelerators)) { + return UNSUPPORTED_ON_DEVICE; + } + + return super.getAvailabilityStatus(); + } + + @Override + protected CharSequence getSummaryOn() { + return resText(R.string.Google_apps_special_accelerator_access_summary_granted); + } + + @Override + protected CharSequence getSummaryOff() { + return resText(R.string.Google_apps_special_accelerator_access_summary_not_granted); + } +} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index bea0c3389ec..155d540b6cd 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -357,32 +357,32 @@ protected boolean generateChallengeOnCreate() { @StringRes protected int getInfoMessageGlasses() { - return R.string.security_settings_face_enroll_introduction_info_glasses; + return R.string.security_settings_face_enroll_introduction_info_glasses_en; } @StringRes protected int getInfoMessageLooking() { - return R.string.security_settings_face_enroll_introduction_info_looking; + return R.string.security_settings_face_enroll_introduction_info_looking_en; } @StringRes protected int getInfoMessageRequireEyes() { - return R.string.security_settings_face_enroll_introduction_info_gaze; + return R.string.security_settings_face_enroll_introduction_info_gaze_en; } @StringRes protected int getHowMessage() { - return R.string.security_settings_face_enroll_introduction_how_message; + return R.string.security_settings_face_enroll_introduction_how_message_en; } @StringRes protected int getInControlTitle() { - return R.string.security_settings_face_enroll_introduction_control_title; + return R.string.security_settings_face_enroll_introduction_control_title_en; } @StringRes protected int getInControlMessage() { - return R.string.security_settings_face_enroll_introduction_control_message; + return R.string.security_settings_face_enroll_introduction_control_message_en; } @StringRes diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 505fe1c1fc4..d1d12137795 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -285,9 +285,7 @@ public void handleMessage(android.os.Message msg) { case MSG_REFRESH_FINGERPRINT_TEMPLATES: removeFingerprintPreference(msg.arg1); updateAddPreference(); - if (isSfps()) { - updateFingerprintUnlockCategoryVisibility(); - } + updateFingerprintUnlockCategoryVisibility(); updatePreferences(); break; case MSG_FINGER_AUTH_SUCCESS: @@ -540,16 +538,17 @@ private void addFingerprintPreferences(PreferenceGroup root) { ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId); } else if (controller instanceof FingerprintUnlockCategoryController) { ((FingerprintUnlockCategoryController) controller).setUserId(mUserId); + } else if (controller instanceof FingerprintSettingsKeyguardPreferenceController c) { + c.setUserId(mUserId); } } // This needs to be after setting ids, otherwise // |mRequireScreenOnToAuthPreferenceController.isChecked| is always checking the primary // user instead of the user with |mUserId|. - if (isSfps()) { - scrollToPreference(fpPrefKey); - addFingerprintUnlockCategory(); - } + scrollToPreference(fpPrefKey); + addFingerprintUnlockCategory(); + createFooterPreference(root); } @@ -609,8 +608,11 @@ private void updateFingerprintUnlockCategoryVisibility() { } } + private FingerprintSettingsKeyguardPreferenceController mFingerprintKeyguardController; + private void setupFingerprintUnlockCategoryPreferences() { mRequireScreenOnToAuthPreference = findPreference(KEY_REQUIRE_SCREEN_ON_TO_AUTH); + mRequireScreenOnToAuthPreference.setVisible(mRequireScreenOnToAuthPreferenceController.isAvailable()); mRequireScreenOnToAuthPreference.setChecked( mRequireScreenOnToAuthPreferenceController.isChecked()); mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( @@ -619,6 +621,13 @@ private void setupFingerprintUnlockCategoryPreferences() { mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked); return true; }); + + RestrictedSwitchPreference keyguardFingerprintPref = findPreference(KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE); + keyguardFingerprintPref.setChecked(mFingerprintKeyguardController.isChecked()); + keyguardFingerprintPref.setOnPreferenceChangeListener((p, value) -> { + mFingerprintKeyguardController.setChecked((boolean) value); + return true; + }); } private void updateAddPreference() { @@ -848,20 +857,22 @@ protected List createPreferenceControllers(Context private List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); - if (isSfps()) { - mFingerprintUnlockCategoryPreferenceController = - new FingerprintUnlockCategoryController( - context, - KEY_FINGERPRINT_UNLOCK_CATEGORY + + mFingerprintUnlockCategoryPreferenceController = + new FingerprintUnlockCategoryController( + context, + KEY_FINGERPRINT_UNLOCK_CATEGORY + ); + mRequireScreenOnToAuthPreferenceController = + new FingerprintSettingsRequireScreenOnToAuthPreferenceController( + context, + KEY_REQUIRE_SCREEN_ON_TO_AUTH ); - mRequireScreenOnToAuthPreferenceController = - new FingerprintSettingsRequireScreenOnToAuthPreferenceController( - context, - KEY_REQUIRE_SCREEN_ON_TO_AUTH - ); - controllers.add(mFingerprintUnlockCategoryPreferenceController); - controllers.add(mRequireScreenOnToAuthPreferenceController); - } + controllers.add(mFingerprintUnlockCategoryPreferenceController); + controllers.add(mRequireScreenOnToAuthPreferenceController); + mFingerprintKeyguardController = new FingerprintSettingsKeyguardPreferenceController( + context, KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE); + controllers.add(mFingerprintKeyguardController); return controllers; } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardPreferenceController.java new file mode 100644 index 00000000000..87b10aa23ba --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardPreferenceController.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 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.biometrics.fingerprint; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; + +import com.android.settings.Utils; +import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; + +import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED; + +// based on src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java +// from android-14.0.0_r1 +public class FingerprintSettingsKeyguardPreferenceController extends TogglePreferenceController { + private static final int ON = 1; + private static final int OFF = 0; + private static final int DEFAULT = ON; + + private int mUserId; + + public FingerprintSettingsKeyguardPreferenceController(Context context, String key) { + super(context, key); + } + + protected RestrictedLockUtils.EnforcedAdmin getRestrictingAdmin() { + return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(mContext, + DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS, mUserId); + } + + public void setUserId(int userId) { + mUserId = userId; + } + + @Override + public boolean isChecked() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + BIOMETRIC_KEYGUARD_ENABLED, DEFAULT, mUserId) == ON; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putIntForUser(mContext.getContentResolver(), + BIOMETRIC_KEYGUARD_ENABLED, isChecked ? ON : OFF, mUserId); + } + + @Override + public int getAvailabilityStatus() { + if (UserManager.get(mContext).isManagedProfile(mUserId)) { + return DISABLED_FOR_USER; + } + + return getAvailabilityFromRestrictingAdmin(); + } + + private int getAvailabilityFromRestrictingAdmin() { + return getRestrictingAdmin() != null ? DISABLED_FOR_USER : AVAILABLE; + } + + @Override + public final boolean isSliceable() { + return false; + } + + @Override + public int getSliceHighlightMenuRes() { + // not needed since it's not sliceable + return NO_RES; + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java index 674a0dfa758..acca88fc0af 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java @@ -41,8 +41,7 @@ public FingerprintUnlockCategoryController(Context context, String key) { @Override public int getAvailabilityStatus() { if (mFingerprintManager != null - && mFingerprintManager.isHardwareDetected() - && mFingerprintManager.isPowerbuttonFps()) { + && mFingerprintManager.isHardwareDetected()) { return mFingerprintManager.hasEnrolledTemplates(getUserId()) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } else { diff --git a/src/com/android/settings/bluetooth/BluetoothAutoOffPrefController.java b/src/com/android/settings/bluetooth/BluetoothAutoOffPrefController.java new file mode 100644 index 00000000000..14791951802 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothAutoOffPrefController.java @@ -0,0 +1,39 @@ +package com.android.settings.bluetooth; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.ext.settings.ExtSettings; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.AutoOffSetting; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +public class BluetoothAutoOffPrefController extends IntSettingPrefController { + + public BluetoothAutoOffPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.BLUETOOTH_AUTO_OFF); + } + + @Override + public int getAvailabilityStatus() { + int r = super.getAvailabilityStatus(); + if (r == AVAILABLE) { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) ? + AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + return r; + } + + @Override + public void addPrefsBeforeList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.bluetooth_auto_off_footer); + } + + @Override + protected void getEntries(Entries entries) { + AutoOffSetting.getEntries(entries); + } +} diff --git a/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java b/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java index ee0021ec951..f16dd378db5 100644 --- a/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java +++ b/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java @@ -16,21 +16,47 @@ package com.android.settings.connecteddevice; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.nfc.NfcAdapter; import android.os.UserManager; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnStop; /** * Controller that used to show NFC and payment features */ -public class NfcAndPaymentFragmentController extends BasePreferenceController { +public class NfcAndPaymentFragmentController extends BasePreferenceController + implements LifecycleObserver, OnResume, OnStop { private final NfcAdapter mNfcAdapter; private final PackageManager mPackageManager; private final UserManager mUserManager; + private final IntentFilter mIntentFilter; + private Preference mPreference; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mPreference == null) { + return; + } + + final String action = intent.getAction(); + if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) { + refreshSummary(mPreference); + } + } + }; public NfcAndPaymentFragmentController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -38,6 +64,15 @@ public NfcAndPaymentFragmentController(Context context, String preferenceKey) { mPackageManager = context.getPackageManager(); mUserManager = context.getSystemService(UserManager.class); mNfcAdapter = NfcAdapter.getDefaultAdapter(context); + + mIntentFilter = isNfcAvailable() + ? new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) : null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); } @Override @@ -61,4 +96,26 @@ public CharSequence getSummary() { } return null; } + + @Override + public void onStop() { + if (!isNfcAvailable()) { + return; + } + + mContext.unregisterReceiver(mReceiver); + } + + @Override + public void onResume() { + if (!isNfcAvailable()) { + return; + } + + mContext.registerReceiver(mReceiver, mIntentFilter); + } + + private boolean isNfcAvailable() { + return mNfcAdapter != null; + } } diff --git a/src/com/android/settings/core/PreferenceControllerListHelper.java b/src/com/android/settings/core/PreferenceControllerListHelper.java index 6d450fe137f..18e67982583 100644 --- a/src/com/android/settings/core/PreferenceControllerListHelper.java +++ b/src/com/android/settings/core/PreferenceControllerListHelper.java @@ -28,6 +28,7 @@ import android.util.Log; import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; +import com.android.settings.ext.BoolSettingPrefController; import com.android.settingslib.core.AbstractPreferenceController; import org.xmlpull.v1.XmlPullParserException; @@ -56,6 +57,7 @@ public static List getPreferenceControllersFromXml(Con try { preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId, MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER + | MetadataFlag.FLAG_NEED_BOOL_SETTING_FIELD | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_FOR_WORK); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "Failed to parse preference xml for getting controllers", e); @@ -65,6 +67,7 @@ public static List getPreferenceControllersFromXml(Con for (Bundle metadata : preferenceMetadata) { final String controllerName = metadata.getString(METADATA_CONTROLLER); if (TextUtils.isEmpty(controllerName)) { + BoolSettingPrefController.maybeAdd(context, metadata, controllers); continue; } BasePreferenceController controller; diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java index a1a8d6731a7..d9a1c397b64 100644 --- a/src/com/android/settings/core/PreferenceXmlParserUtils.java +++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java @@ -68,6 +68,7 @@ public class PreferenceXmlParserUtils { MetadataFlag.FLAG_NEED_KEY, MetadataFlag.FLAG_NEED_PREF_TYPE, MetadataFlag.FLAG_NEED_PREF_CONTROLLER, + MetadataFlag.FLAG_NEED_BOOL_SETTING_FIELD, MetadataFlag.FLAG_NEED_PREF_TITLE, MetadataFlag.FLAG_NEED_PREF_SUMMARY, MetadataFlag.FLAG_NEED_PREF_ICON, @@ -91,11 +92,14 @@ public class PreferenceXmlParserUtils { int FLAG_UNAVAILABLE_SLICE_SUBTITLE = 1 << 11; int FLAG_FOR_WORK = 1 << 12; int FLAG_NEED_HIGHLIGHTABLE_MENU_KEY = 1 << 13; + + int FLAG_NEED_BOOL_SETTING_FIELD = 1 << 30; } public static final String METADATA_PREF_TYPE = "type"; public static final String METADATA_KEY = "key"; public static final String METADATA_CONTROLLER = "controller"; + public static final String METADATA_BOOL_SETTING_FIELD = "bool_setting_field"; public static final String METADATA_TITLE = "title"; public static final String METADATA_SUMMARY = "summary"; public static final String METADATA_ICON = "icon"; @@ -225,6 +229,10 @@ public static List extractMetadata(Context context, @XmlRes int xmlResId preferenceMetadata.putString(METADATA_CONTROLLER, getController(preferenceAttributes)); } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_BOOL_SETTING_FIELD)) { + preferenceMetadata.putString(METADATA_BOOL_SETTING_FIELD, + getBoolSettingField(preferenceAttributes)); + } if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TITLE)) { preferenceMetadata.putString(METADATA_TITLE, getTitle(preferenceAttributes)); } @@ -321,6 +329,10 @@ private static String getController(TypedArray styledAttributes) { return styledAttributes.getString(R.styleable.Preference_controller); } + private static String getBoolSettingField(TypedArray styledAttributes) { + return styledAttributes.getString(R.styleable.Preference_boolSettingField); + } + private static String getHighlightableMenuKey(TypedArray styledAttributes) { return styledAttributes.getString(R.styleable.Preference_highlightableMenuKey); } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 7b1abaf8b24..9d564305c58 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -202,6 +202,8 @@ public class SettingsGateway { * security exception if the fragment it needs to display is not in this list. */ public static final String[] ENTRY_FRAGMENTS = { + com.android.settings.applications.appinfo.AppNativeDebuggingFragment.class.getName(), + com.android.settings.applications.appinfo.AppMemtagFragment.class.getName(), AdvancedConnectedDeviceDashboardFragment.class.getName(), CreateShortcut.class.getName(), BluetoothPairingDetail.class.getName(), diff --git a/src/com/android/settings/development/DSULoader.java b/src/com/android/settings/development/DSULoader.java index fede67a7766..957e3d79e6f 100644 --- a/src/com/android/settings/development/DSULoader.java +++ b/src/com/android/settings/development/DSULoader.java @@ -305,7 +305,7 @@ Date getDeviceSPL() { } boolean isSupported() { - boolean supported = true; + boolean supported = false; String cpu = getDeviceCpu(); if (!mCpuAbi.equals(cpu)) { Slog.i(TAG, mCpuAbi + " != " + cpu); diff --git a/src/com/android/settings/deviceinfo/firmwareversion/BootloaderVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/BootloaderVersionPreferenceController.java new file mode 100644 index 00000000000..32b1cb64de0 --- /dev/null +++ b/src/com/android/settings/deviceinfo/firmwareversion/BootloaderVersionPreferenceController.java @@ -0,0 +1,28 @@ +package com.android.settings.deviceinfo.firmwareversion; + +import android.content.Context; +import android.os.SystemProperties; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; + +public class BootloaderVersionPreferenceController extends BasePreferenceController { + + static final String BOOTLOADER_PROPERTY = "ro.bootloader"; + + public BootloaderVersionPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + return SystemProperties.get(BOOTLOADER_PROPERTY, + mContext.getString(R.string.device_info_default)); + } +} diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/HardwareSkuPreferenceController.java b/src/com/android/settings/deviceinfo/hardwareinfo/HardwareSkuPreferenceController.java new file mode 100644 index 00000000000..4be09710e8c --- /dev/null +++ b/src/com/android/settings/deviceinfo/hardwareinfo/HardwareSkuPreferenceController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 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.deviceinfo.hardwareinfo; + +import android.content.Context; +import android.os.SystemProperties; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.slices.Sliceable; + +public class HardwareSkuPreferenceController extends BasePreferenceController { + + public HardwareSkuPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean(R.bool.config_show_device_model) && + !TextUtils.isEmpty(getSummary()) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean useDynamicSliceSummary() { + return true; + } + + @Override + public CharSequence getSummary() { + return SystemProperties.get("ro.boot.hardware.sku"); + } +} diff --git a/src/com/android/settings/display/BatterySharePreferenceController.java b/src/com/android/settings/display/BatterySharePreferenceController.java new file mode 100644 index 00000000000..e87d430d68a --- /dev/null +++ b/src/com/android/settings/display/BatterySharePreferenceController.java @@ -0,0 +1,99 @@ +package com.android.settings.display; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import com.android.internal.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import vendor.google.wireless_charger.ReverseWirelessCharger; + +public class BatterySharePreferenceController extends BasePreferenceController implements + PreferenceControllerMixin, Preference.OnPreferenceChangeListener, LifecycleObserver, + OnStart, OnStop { + + private static final String KEY_BATTERY_SHARE = "battery_share"; + private final ReverseWirelessCharger wirelessCharger; + private final Context mContext; + private Preference mPreference; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ((Activity) mContext).runOnUiThread(() -> update()); + } + }; + + + public BatterySharePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContext = context; + wirelessCharger = ReverseWirelessCharger.getInstance(); + } + + public boolean isPlugged(Context context) { + Intent intent = context.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + update(); + } + + @Override + public String getPreferenceKey() { + return KEY_BATTERY_SHARE; + } + + private void update() { + if (mPreference == null) return; + boolean enabled = !isPlugged(mContext) && wirelessCharger.isRtxSupported(); + mPreference.setEnabled(enabled); + ((SwitchPreference) mPreference).setChecked(wirelessCharger.isRtxModeOn()); + } + + @Override + public int getAvailabilityStatus() { + return wirelessCharger.isRtxSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void updateState(Preference preference) { + update(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + wirelessCharger.setRtxMode((Boolean) newValue); + return true; + } + + @Override + public void onStart() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, filter); + } + + @Override + public void onStop() { + mContext.unregisterReceiver(mBroadcastReceiver); + } +} diff --git a/src/com/android/settings/display/ScreenResolutionFragment.java b/src/com/android/settings/display/ScreenResolutionFragment.java index daf1793f11f..9954efb1dca 100644 --- a/src/com/android/settings/display/ScreenResolutionFragment.java +++ b/src/com/android/settings/display/ScreenResolutionFragment.java @@ -101,7 +101,6 @@ protected int getPreferenceScreenResId() { @Override protected void addStaticPreferences(PreferenceScreen screen) { - updateIllustrationImage(mImagePreference); screen.addPreference(mImagePreference); final FooterPreference footerPreference = new FooterPreference(screen.getContext()); @@ -222,7 +221,6 @@ protected boolean setDefaultKey(final String key) { } setDisplayMode(width); - updateIllustrationImage(mImagePreference); return true; } diff --git a/src/com/android/settings/display/TouchSensitivityPreferenceController.java b/src/com/android/settings/display/TouchSensitivityPreferenceController.java new file mode 100755 index 00000000000..a6f90f593f0 --- /dev/null +++ b/src/com/android/settings/display/TouchSensitivityPreferenceController.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 The Proton AOSP 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.display; + +import android.content.Context; +import android.os.SystemProperties; +import android.provider.Settings; + +import com.android.settings.core.TogglePreferenceController; +import com.android.settings.R; + +public class TouchSensitivityPreferenceController extends TogglePreferenceController { + + // Settings can only set the debug.* property, so we need to persist it + // in system settings. Match the stock setting name for backup compatibility. + private static final String SETTINGS_KEY = "touch_sensitivity_enabled"; + private static final String PROP_NAME = "debug.touch_sensitivity_mode"; + + public TouchSensitivityPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean(com.android.internal.R.bool.config_supportGloveMode) + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean setChecked(boolean value) { + Settings.Secure.putInt(mContext.getContentResolver(), SETTINGS_KEY, value ? 1 : 0); + SystemProperties.set(PROP_NAME, value ? "1" : "0"); + return true; + } + + @Override + public boolean isChecked() { + // debug prop isn't persistent + return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_KEY, 0) == 1; + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_display; + } +} diff --git a/src/com/android/settings/ext/AbstractListPreferenceController.java b/src/com/android/settings/ext/AbstractListPreferenceController.java new file mode 100644 index 00000000000..342ffeef5df --- /dev/null +++ b/src/com/android/settings/ext/AbstractListPreferenceController.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.ext; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.UserHandle; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.SparseIntArray; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.utils.CandidateInfoExtra; +import com.android.settingslib.widget.CandidateInfo; +import com.android.settingslib.widget.FooterPreference; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public abstract class AbstractListPreferenceController extends BasePreferenceController + implements DefaultLifecycleObserver { + + private Preference preference; + private Entries entries; + + @Nullable + public RadioButtonPickerFragment2 fragment; + + protected AbstractListPreferenceController(Context ctx, String key) { + super(ctx, key); + } + + // call entries.add(entryName, entryValue) to add entries. + // entryValues can be mapped from other values or sets of values, as long as getCurrentValue() + // and setValue() methods are consistent + protected abstract void getEntries(Entries entries); + + public void getEntriesAsCandidates(ArrayList dst) { + Entries e = new Entries(mContext); + getEntries(e); + + dst.addAll(e.list); + } + + protected abstract int getCurrentValue(); + protected abstract boolean setValue(int val); + + @Override + public void updateState(Preference p) { + if (entries == null) { + entries = new Entries(mContext); + getEntries(entries); + } + + if (p != preference) { + p.setSingleLineTitle(false); + p.setPersistent(false); + this.preference = p; + } + + updatePreference(); + } + + void updatePreference() { + if (fragment != null) { + fragment.updateCandidates(); + } + + Preference p = preference; + if (p == null) { + return; + } + + int idx = entries.getIndexForValue(getCurrentValue()); + if (idx >= 0) { + p.setSummary(entries.list.get(idx).loadLabel()); + } else { + p.setSummary(null); + } + } + + public static class Entries { + private final Context context; + private final ArrayList list = new ArrayList<>(); + private final SparseIntArray valueToIndexMap = new SparseIntArray(); + + Entries(Context context) { + this.context = context; + } + + public void add(@StringRes int title, int value) { + add(context.getText(title), value); + } + + public void add(@StringRes int title, @StringRes int summary, int value) { + add(context.getText(title), context.getText(summary), value); + } + + public void add(int duration, TimeUnit timeUnit) { + long durationMillis = timeUnit.toMillis(duration); + if (durationMillis > Integer.MAX_VALUE) { + throw new IllegalArgumentException(); + } + + add(DateUtils.formatDuration(durationMillis), (int) durationMillis); + } + + public void add(CharSequence title, int value) { + add(title, null, value, true); + } + + public void add(CharSequence title, CharSequence summary, int value) { + add(title, summary, value, true); + } + + public void add(CharSequence title, @Nullable CharSequence summary, int value, boolean enabled) { + String prefKey = Integer.toString(value); + list.add(new CandidateInfoExtra(title, summary, prefKey, enabled)); + valueToIndexMap.put(value, list.size() - 1); + } + + public int getIndexForValue(int val) { + return valueToIndexMap.get(val, -1); + } + } + + @Override + public boolean isSliceable() { + return false; + } + + @Override + public int getSliceHighlightMenuRes() { + return NO_RES; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return super.handlePreferenceTreeClick(preference); + } + + if (this.preference instanceof ListPreference) { + return super.handlePreferenceTreeClick(preference); + } + + UserHandle workProfileUser = getWorkProfileUser(); + boolean isForWork = workProfileUser != null; + + RadioButtonPickerFragment2.fillArgs(preference, this, isForWork); + + new SubSettingLauncher(preference.getContext()) + .setDestination(RadioButtonPickerFragment2.class.getName()) + .setSourceMetricsCategory(preference.getExtras().getInt(DashboardFragment.CATEGORY, + SettingsEnums.PAGE_UNKNOWN)) + .setTitleText(preference.getTitle()) + .setArguments(preference.getExtras()) + .setUserHandle(workProfileUser) + .launch(); + return true; + } + + public void addPrefsBeforeList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + + } + + public void addPrefsAfterList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + + } + + public FooterPreference addFooterPreference(PreferenceScreen screen, @StringRes int text) { + Context ctx = screen.getContext(); + return addFooterPreference(screen, ctx.getText(text), null, null); + } + + public FooterPreference addFooterPreference(PreferenceScreen screen, + @StringRes int text, String learnMoreUrl) { + return addFooterPreference(screen, text, R.string.learn_more, learnMoreUrl); + } + + public FooterPreference addFooterPreference( + PreferenceScreen screen, @StringRes int text, + @StringRes int learnMoreText, String learnMoreUrl) { + Context ctx = screen.getContext(); + Runnable learnMoreAction = () -> { + var intent = new Intent(Intent.ACTION_VIEW, Uri.parse(learnMoreUrl)); + ctx.startActivity(intent); + }; + return addFooterPreference(screen, ctx.getText(text), + ctx.getText(learnMoreText), learnMoreAction); + } + + public FooterPreference addFooterPreference( + PreferenceScreen screen, @StringRes int text, + @StringRes int learnMoreText, Runnable learnMoreAction) { + Context ctx = screen.getContext(); + return addFooterPreference(screen, ctx.getText(text), ctx.getText(learnMoreText), learnMoreAction); + } + + public FooterPreference addFooterPreference(PreferenceScreen screen, CharSequence text, + @Nullable CharSequence learnMoreText, + @Nullable Runnable learnMoreAction) { + var p = new FooterPreference(screen.getContext()); + p.setSelectable(false); + p.setSummary(text); + if (learnMoreText != null) { + p.setLearnMoreText(learnMoreText); + Objects.requireNonNull(learnMoreAction); + p.setLearnMoreAction(v -> learnMoreAction.run()); + } + p.setOrder(Preference.DEFAULT_ORDER); + screen.addPreference(p); + return p; + } + + protected final CharSequence getText(@StringRes int resId) { + return mContext.getText(resId); + } +} diff --git a/src/com/android/settings/ext/AbstractTogglePrefController.java b/src/com/android/settings/ext/AbstractTogglePrefController.java new file mode 100644 index 00000000000..ceee6984bfd --- /dev/null +++ b/src/com/android/settings/ext/AbstractTogglePrefController.java @@ -0,0 +1,38 @@ +package com.android.settings.ext; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; + +import com.android.settings.core.TogglePreferenceController; + +public abstract class AbstractTogglePrefController extends TogglePreferenceController { + + protected AbstractTogglePrefController(Context ctx, String key) { + super(ctx, key); + } + + @Nullable protected Preference preference; + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (preference != this.preference) { + preference.setSingleLineTitle(false); + preference.setPersistent(false); + this.preference = preference; + } + } + + @Override + public boolean isSliceable() { + return false; + } + + @Override + public int getSliceHighlightMenuRes() { + return NO_RES; + } +} diff --git a/src/com/android/settings/ext/AppInfoPreferenceControllerBase2.java b/src/com/android/settings/ext/AppInfoPreferenceControllerBase2.java new file mode 100644 index 00000000000..d0794d13925 --- /dev/null +++ b/src/com/android/settings/ext/AppInfoPreferenceControllerBase2.java @@ -0,0 +1,37 @@ +package com.android.settings.ext; + +import android.content.Context; +import android.content.pm.GosPackageState; +import android.text.TextUtils; + +import androidx.preference.Preference; + +import com.android.settings.applications.appinfo.AppInfoPreferenceControllerBase; + +public abstract class AppInfoPreferenceControllerBase2 extends AppInfoPreferenceControllerBase { + + protected AppInfoPreferenceControllerBase2(Context ctx, String key) { + super(ctx, key); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + + onPreferenceClick(getPackageName()); + return true; + } + + public abstract void onPreferenceClick(String packageName); + + protected boolean hasGosPackageStateFlags(int flags) { + var ps = GosPackageState.get(getPackageName()); + return ps != null && ps.hasFlags(flags); + } + + protected String getPackageName() { + return mParent.getPackageInfo().packageName; + } +} diff --git a/src/com/android/settings/ext/AppPrefUtils.java b/src/com/android/settings/ext/AppPrefUtils.java new file mode 100644 index 00000000000..3ae426bf36f --- /dev/null +++ b/src/com/android/settings/ext/AppPrefUtils.java @@ -0,0 +1,14 @@ +package com.android.settings.ext; + +import android.content.Context; + +import com.android.settings.R; + +import androidx.annotation.StringRes; + +public class AppPrefUtils { + + public static String getFooterForDefaultHardeningSetting(Context ctx, @StringRes int baseText) { + return ctx.getString(R.string.app_exploit_protection_default_value_warning) + "\n\n" + ctx.getString(baseText); + } +} diff --git a/src/com/android/settings/ext/AutoOffSetting.java b/src/com/android/settings/ext/AutoOffSetting.java new file mode 100644 index 00000000000..98be94b8589 --- /dev/null +++ b/src/com/android/settings/ext/AutoOffSetting.java @@ -0,0 +1,25 @@ +package com.android.settings.ext; + +import com.android.settings.R; + +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class AutoOffSetting { + + public static void getEntries(AbstractListPreferenceController.Entries entries) { + entries.add(R.string.auto_off_never, 0); + entries.add(15, SECONDS); + entries.add(30, SECONDS); + entries.add(1, MINUTES); + entries.add(2, MINUTES); + entries.add(5, MINUTES); + entries.add(10, MINUTES); + entries.add(30, MINUTES); + entries.add(1, HOURS); + entries.add(2, HOURS); + entries.add(4, HOURS); + entries.add(8, HOURS); + } +} diff --git a/src/com/android/settings/ext/BoolSettingFragment.java b/src/com/android/settings/ext/BoolSettingFragment.java new file mode 100644 index 00000000000..12aac5fc4ad --- /dev/null +++ b/src/com/android/settings/ext/BoolSettingFragment.java @@ -0,0 +1,149 @@ +package com.android.settings.ext; + +import android.content.Context; +import android.content.Intent; +import android.ext.settings.BoolSetting; +import android.net.Uri; +import android.os.Bundle; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.widget.FooterPreference; + +public abstract class BoolSettingFragment extends DashboardFragment implements ExtSettingPrefController { + + private static final String TAG = BoolSettingFragment.class.getSimpleName(); + + protected SwitchPreference mainSwitch; + protected boolean invertSetting; + + private ExtSettingControllerHelper helper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + helper = new ExtSettingControllerHelper<>(requireContext(), getSetting()); + + getActivity().setTitle(getTitle()); + + Context ctx = requireContext(); + + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(ctx); + + var mainSwitch = new SwitchPreference(ctx); + mainSwitch.setTitle(getMainSwitchTitle()); + + this.mainSwitch = mainSwitch; + refreshMainSwitch(); + + mainSwitch.setOnPreferenceChangeListener((preference, newValue) -> { + boolean state = (boolean) newValue; + + if (invertSetting) { + state = !state; + } + + if (!getSetting().put(requireContext(), state)) { + return false; + } + + onMainSwitchChanged(state); + + return true; + }); + + screen.addPreference(mainSwitch); + + addExtraPrefs(screen); + + FooterPreference footer = makeFooterPref(new FooterPreference.Builder(ctx)); + + if (footer != null) { + screen.addPreference(footer); + } + + setPreferenceScreen(screen); + } + + protected abstract BoolSetting getSetting(); + + protected abstract CharSequence getTitle(); + + protected CharSequence getMainSwitchTitle() { + return getText(R.string.bool_setting_enable); + } + + protected CharSequence getMainSwitchSummary() { + return null; + } + + protected void addExtraPrefs(PreferenceScreen screen) {} + + protected FooterPreference makeFooterPref(FooterPreference.Builder builder) { + return null; + } + + protected static void setFooterPrefLearnMoreUri(FooterPreference p, Uri uri) { + p.setLearnMoreAction(v -> { + var intent = new Intent(Intent.ACTION_VIEW, uri); + p.getContext().startActivity(intent); + }); + } + + protected void onMainSwitchChanged(boolean state) {} + + private void refreshMainSwitch() { + boolean state = getSetting().get(requireContext()); + if (invertSetting) { + state = !state; + } + mainSwitch.setChecked(state); + + CharSequence mainSwitchSummary = getMainSwitchSummary(); + if (mainSwitchSummary != null) { + mainSwitch.setSummary(mainSwitchSummary); + } + } + + @Override + public void onResume() { + super.onResume(); + helper.onResume(this); + refreshMainSwitch(); + } + + @Override + public void onPause() { + super.onPause(); + + helper.onPause(this); + } + + @Override + public void accept(BoolSetting setting) { + refreshMainSwitch(); + } + + @Override + public int getMetricsCategory() { + return METRICS_CATEGORY_UNKNOWN; + } + + @Override + protected int getPreferenceScreenResId() { + return 0; + } + + @Override + protected String getLogTag() { + return TAG; + } + + protected final CharSequence resText(int res) { + return requireContext().getText(res); + } +} diff --git a/src/com/android/settings/ext/BoolSettingFragmentPrefController.java b/src/com/android/settings/ext/BoolSettingFragmentPrefController.java new file mode 100644 index 00000000000..3f9223b800a --- /dev/null +++ b/src/com/android/settings/ext/BoolSettingFragmentPrefController.java @@ -0,0 +1,26 @@ +package com.android.settings.ext; + +import android.content.Context; +import android.ext.settings.BoolSetting; + +import com.android.settings.R; + +public abstract class BoolSettingFragmentPrefController extends ExtSettingFragmentPrefController { + + protected BoolSettingFragmentPrefController(Context ctx, String key, BoolSetting setting) { + super(ctx, key, setting); + } + + @Override + public CharSequence getSummary() { + return setting.get(mContext) ? getSummaryOn() : getSummaryOff(); + } + + protected CharSequence getSummaryOn() { + return resText(R.string.bool_setting_enabled); + } + + protected CharSequence getSummaryOff() { + return resText(R.string.bool_setting_disabled); + } +} diff --git a/src/com/android/settings/ext/BoolSettingPrefController.java b/src/com/android/settings/ext/BoolSettingPrefController.java new file mode 100644 index 00000000000..2d6d668cfc2 --- /dev/null +++ b/src/com/android/settings/ext/BoolSettingPrefController.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.ext; + +import android.content.Context; +import android.ext.settings.BoolSetting; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; + +import com.android.settings.core.BasePreferenceController; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; + +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_BOOL_SETTING_FIELD; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; + +public class BoolSettingPrefController extends AbstractTogglePrefController + implements ExtSettingPrefController { + private final BoolSetting setting; + private final ExtSettingControllerHelper helper; + + protected BoolSettingPrefController(Context ctx, String key, BoolSetting setting) { + super(ctx, key); + helper = new ExtSettingControllerHelper(ctx, setting); + this.setting = setting; + } + + @Override + public int getAvailabilityStatus() { + return helper.getAvailabilityStatus(); + } + + @Override + public final boolean isChecked() { + return setting.get(mContext); + } + + @Override + public final boolean setChecked(boolean isChecked) { + return setting.put(mContext, isChecked); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) { + helper.onResume(this); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + helper.onPause(this); + } + + // called by the setting observer + @Override + public void accept(BoolSetting boolSetting) { + if (preference != null) { + updateState(preference); + } + } + + // called when PreferenceScreen XML is parsed + public static void maybeAdd(Context context, Bundle metadata, + List dest) { + String boolSettingField = metadata.getString(METADATA_BOOL_SETTING_FIELD); + if (boolSettingField == null) { + return; + } + String[] split = boolSettingField.split(" "); + + BoolSetting boolSetting; + try { + Class c = Class.forName(split[0]); + Field field = c.getField(split[1]); + boolSetting = (BoolSetting) Objects.requireNonNull(field.get(null)); + } catch (Exception e) { + throw new IllegalStateException("Invalid BoolSetting field " + boolSettingField); + } + + String key = Objects.requireNonNull(metadata.getString(METADATA_KEY)); + + dest.add(new BoolSettingPrefController(context, key, boolSetting)); + } +} diff --git a/src/com/android/settings/ext/DefaultOfPerAppSettingFragment.java b/src/com/android/settings/ext/DefaultOfPerAppSettingFragment.java new file mode 100644 index 00000000000..fca6febcedf --- /dev/null +++ b/src/com/android/settings/ext/DefaultOfPerAppSettingFragment.java @@ -0,0 +1,14 @@ +package com.android.settings.ext; + +import android.content.res.Resources; + +import androidx.annotation.StringRes; + +import com.android.settings.R; + +public abstract class DefaultOfPerAppSettingFragment extends BoolSettingFragment { + + protected CharSequence getMainSwitchSummary() { + return getText(R.string.summary_of_default_of_per_app_setting); + } +} diff --git a/src/com/android/settings/ext/ExtSettingControllerHelper.java b/src/com/android/settings/ext/ExtSettingControllerHelper.java new file mode 100644 index 00000000000..46ad5cfeecf --- /dev/null +++ b/src/com/android/settings/ext/ExtSettingControllerHelper.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.ext; + +import android.content.ContentResolver; +import android.content.Context; +import android.ext.settings.Setting; +import android.os.Process; +import android.provider.Settings; + +import java.util.function.Consumer; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER; + +public class ExtSettingControllerHelper { + private final Context context; + private final T setting; + + ExtSettingControllerHelper(Context context, T setting) { + this.context = context; + this.setting = setting; + } + + public static int getGlobalSettingAvailability(Context ctx) { + return Process.myUserHandle().isSystem() ? AVAILABLE : DISABLED_FOR_USER; + } + + public static int getDevModeSettingAvailability(Context ctx) { + ContentResolver cr = ctx.getContentResolver(); + String key = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED; + + return (Settings.Global.getInt(cr, key, 0) == 0) ? + CONDITIONALLY_UNAVAILABLE : AVAILABLE; + } + + int getAvailabilityStatus() { + if (setting.getScope() != Setting.Scope.PER_USER) { + return getGlobalSettingAvailability(context); + } + return AVAILABLE; + } + + private Object observer; + + void onResume(ExtSettingPrefController espc) { + registerObserver(espc); + } + + void onPause(ExtSettingPrefController espc) { + unregisterObserver(); + } + + void registerObserver(Consumer settingObserver) { + if (setting.canObserveState()) { + observer = setting.registerObserver(context, settingObserver, context.getMainThreadHandler()); + } + } + + void unregisterObserver() { + if (setting.canObserveState()) { + setting.unregisterObserver(context, observer); + } + } +} diff --git a/src/com/android/settings/ext/ExtSettingFragmentPrefController.java b/src/com/android/settings/ext/ExtSettingFragmentPrefController.java new file mode 100644 index 00000000000..2d87635e0d2 --- /dev/null +++ b/src/com/android/settings/ext/ExtSettingFragmentPrefController.java @@ -0,0 +1,42 @@ +package com.android.settings.ext; + +import android.content.Context; +import android.ext.settings.Setting; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; + +public abstract class ExtSettingFragmentPrefController extends FragmentPrefController + implements ExtSettingPrefController { + protected final T setting; + protected final ExtSettingControllerHelper helper; + + protected ExtSettingFragmentPrefController(Context ctx, String key, T setting) { + super(ctx, key); + this.setting = setting; + helper = new ExtSettingControllerHelper(ctx, setting); + } + + @Override + public int getAvailabilityStatus() { + return helper.getAvailabilityStatus(); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) { + helper.onResume(this); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + helper.onPause(this); + } + + // called by the setting observer + @Override + public void accept(T setting) { + if (preference != null) { + updateState(preference); + } + } +} diff --git a/src/com/android/settings/ext/ExtSettingPrefController.java b/src/com/android/settings/ext/ExtSettingPrefController.java new file mode 100644 index 00000000000..f641e344b50 --- /dev/null +++ b/src/com/android/settings/ext/ExtSettingPrefController.java @@ -0,0 +1,12 @@ +package com.android.settings.ext; + +import android.ext.settings.Setting; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; + +import java.util.function.Consumer; + +interface ExtSettingPrefController extends DefaultLifecycleObserver, Consumer { +} diff --git a/src/com/android/settings/ext/FragmentPrefController.java b/src/com/android/settings/ext/FragmentPrefController.java new file mode 100644 index 00000000000..b09ab9615c2 --- /dev/null +++ b/src/com/android/settings/ext/FragmentPrefController.java @@ -0,0 +1,36 @@ +package com.android.settings.ext; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.android.settings.core.BasePreferenceController; + +public abstract class FragmentPrefController + extends BasePreferenceController { + + protected FragmentPrefController(Context ctx, String key) { + super(ctx, key); + } + + @Nullable + protected Preference preference; + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (preference != this.preference) { + preference.setSingleLineTitle(false); + preference.setPersistent(false); + + this.preference = preference; + } + } + + protected final CharSequence resText(int res) { + return mContext.getText(res); + } +} diff --git a/src/com/android/settings/ext/IntSettingPrefController.java b/src/com/android/settings/ext/IntSettingPrefController.java new file mode 100644 index 00000000000..53f3bf810b9 --- /dev/null +++ b/src/com/android/settings/ext/IntSettingPrefController.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.ext; + +import android.content.Context; +import android.ext.settings.IntSetting; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; + +public abstract class IntSettingPrefController extends AbstractListPreferenceController + implements ExtSettingPrefController +{ + private final IntSetting setting; + + private final ExtSettingControllerHelper helper; + + protected IntSettingPrefController(Context ctx, String key, IntSetting setting) { + super(ctx, key); + this.setting = setting; + helper = new ExtSettingControllerHelper(ctx, setting); + } + + @Override + public int getAvailabilityStatus() { + return helper.getAvailabilityStatus(); + } + + @Override + protected final int getCurrentValue() { + return setting.get(mContext); + } + + @Override + protected final boolean setValue(int val) { + return setting.put(mContext, val); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) { + helper.onResume(this); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + helper.onPause(this); + } + + // called by the setting observer + @Override + public void accept(IntSetting intSetting) { + updatePreference(); + } +} diff --git a/src/com/android/settings/ext/RadioButtonPickerFragment2.java b/src/com/android/settings/ext/RadioButtonPickerFragment2.java new file mode 100644 index 00000000000..f6e8b91486c --- /dev/null +++ b/src/com/android/settings/ext/RadioButtonPickerFragment2.java @@ -0,0 +1,117 @@ +package com.android.settings.ext; + +import android.content.Context; +import android.os.Bundle; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.utils.CandidateInfoExtra; +import com.android.settings.widget.RadioButtonPickerFragment; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.widget.CandidateInfo; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import java.util.ArrayList; +import java.util.List; + +public class RadioButtonPickerFragment2 extends RadioButtonPickerFragment { + + private final ArrayList candidates = new ArrayList<>(); + + static final String KEY_PREF_CONTROLLER_CLASS = "pref_controller"; + static final String KEY_PREF_KEY = "pref_key"; + + private AbstractListPreferenceController prefController; + + public static void fillArgs(Preference pref, AbstractListPreferenceController pc, boolean isForWork) { + Bundle args = pref.getExtras(); + args.putString(KEY_PREF_CONTROLLER_CLASS, pc.getClass().getName()); + args.putString(KEY_PREF_KEY, pc.getPreferenceKey()); + args.putBoolean(EXTRA_FOR_WORK, isForWork); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Bundle args = requireArguments(); + String prefControllerClass = args.getString(KEY_PREF_CONTROLLER_CLASS); + String prefKey = args.getString(KEY_PREF_KEY); + boolean forWork = args.getBoolean(EXTRA_FOR_WORK); + + Context ctx = requireContext(); + + prefController = (AbstractListPreferenceController) BasePreferenceController + .createInstance(ctx,prefControllerClass, prefKey, forWork); + prefController.fragment = this; + + super.onCreate(savedInstanceState); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(requireContext()); + setPreferenceScreen(ps); + } + + @Override + protected int getPreferenceScreenResId() { + return -1; + } + + @Override + protected List getCandidates() { + candidates.clear(); + prefController.getEntriesAsCandidates(candidates); + return candidates; + } + + @Override + protected void addPrefsBeforeList(PreferenceScreen screen) { + prefController.addPrefsBeforeList(this, screen); + } + + @Override + protected void addPrefsAfterList(PreferenceScreen screen) { + prefController.addPrefsAfterList(this, screen); + } + + @Override + protected String getDefaultKey() { + return Integer.toString(prefController.getCurrentValue()); + } + + @Override + protected boolean setDefaultKey(String key) { + return prefController.setValue(Integer.parseInt(key)); + } + + @Override + public void bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, + String defaultKey, String systemDefaultKey) { + pref.setSingleLineTitle(false); + + if (info instanceof CandidateInfoExtra) { + var cie = (CandidateInfoExtra) info; + pref.setSummary(cie.loadSummary()); + } + } + + @Override + public int getMetricsCategory() { + return METRICS_CATEGORY_UNKNOWN; + } + + @Override + public void onPause() { + super.onPause(); + prefController.onPause(this); + } + + @Override + public void onResume() { + super.onResume(); + prefController.onResume(this); + updateCandidates(); + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java index 86538ee8811..f0e157525e0 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java @@ -572,9 +572,10 @@ public static NameAndIcon getNameAndIconFromPowerComponent( iconId = R.drawable.ic_settings_aod; break; default: - Log.w(TAG, "unknown attribute:" + DebugUtils.constantToString( - BatteryConsumer.class, "POWER_COMPONENT_", powerComponentId)); - name = null; + String fieldName = DebugUtils.constantToString( + BatteryConsumer.class, "POWER_COMPONENT_", powerComponentId); + Log.w(TAG, "unknown attribute:" + fieldName); + name = context.getResources().getString(R.string.header_category_system) + " (" + fieldName + ")"; iconId = R.drawable.ic_power_system; break; } diff --git a/src/com/android/settings/location/GnssPsdsPrefController.java b/src/com/android/settings/location/GnssPsdsPrefController.java new file mode 100644 index 00000000000..cc8c3ed076c --- /dev/null +++ b/src/com/android/settings/location/GnssPsdsPrefController.java @@ -0,0 +1,52 @@ +package com.android.settings.location; + +import android.content.Context; +import android.ext.settings.ExtSettings; +import android.ext.settings.GnssConstants; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +public class GnssPsdsPrefController extends IntSettingPrefController { + private final String psdsType; + + public GnssPsdsPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.getGnssPsdsSetting(ctx)); + psdsType = ctx.getString(com.android.internal.R.string.config_gnssPsdsType); + } + + @Override + public int getAvailabilityStatus() { + int result = super.getAvailabilityStatus(); + if (result == AVAILABLE) { + if (psdsType.isEmpty()) { + result = UNSUPPORTED_ON_DEVICE; + } + } + return result; + } + + @Override + public void addPrefsAfterList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.gnss_psds_footer); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.psds_enabled_grapheneos_server, GnssConstants.PSDS_SERVER_GRAPHENEOS); + int standardServerString; + switch (psdsType) { + case GnssConstants.PSDS_TYPE_QUALCOMM_XTRA: + standardServerString = R.string.psds_enabled_qualcomm_server; + break; + default: + standardServerString = R.string.psds_enabled_standard_server; + break; + } + entries.add(standardServerString, GnssConstants.PSDS_SERVER_STANDARD); + entries.add(R.string.psds_disabled, R.string.psds_disabled_summary, GnssConstants.PSDS_DISABLED); + } +} diff --git a/src/com/android/settings/location/GnssSuplPrefController.java b/src/com/android/settings/location/GnssSuplPrefController.java new file mode 100644 index 00000000000..c1f076d00ed --- /dev/null +++ b/src/com/android/settings/location/GnssSuplPrefController.java @@ -0,0 +1,30 @@ +package com.android.settings.location; + +import android.content.Context; +import android.ext.settings.ExtSettings; +import android.ext.settings.GnssConstants; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +public class GnssSuplPrefController extends IntSettingPrefController { + + public GnssSuplPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.GNSS_SUPL); + } + + @Override + public void addPrefsAfterList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.pref_gnss_supl_footer); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.supl_enabled_grapheneos_proxy, GnssConstants.SUPL_SERVER_GRAPHENEOS_PROXY); + entries.add(R.string.supl_enabled_standard_server, GnssConstants.SUPL_SERVER_STANDARD); + entries.add(R.string.supl_disabled, R.string.supl_disabled_summary, GnssConstants.SUPL_DISABLED); + } +} diff --git a/src/com/android/settings/location/LocationIndicatorsPreferenceController.java b/src/com/android/settings/location/LocationIndicatorsPreferenceController.java index 75ffb3a9286..1b1af8f8f73 100644 --- a/src/com/android/settings/location/LocationIndicatorsPreferenceController.java +++ b/src/com/android/settings/location/LocationIndicatorsPreferenceController.java @@ -34,7 +34,7 @@ public LocationIndicatorsPreferenceController(Context context, String preference @Override public boolean isChecked() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_LOCATION_INDICATORS_ENABLED, false); + Utils.PROPERTY_LOCATION_INDICATORS_ENABLED, true); } @Override diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java index 61682d00a8c..6b28d921efa 100644 --- a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java @@ -48,7 +48,7 @@ public class RecentLocationAccessPreferenceController extends LocationBasePrefer RecentAppOpsAccess mRecentLocationApps; private PreferenceCategory mCategoryRecentLocationRequests; private int mType = ProfileSelectFragment.ProfileType.ALL; - private boolean mShowSystem = false; + private boolean mShowSystem = true; private boolean mSystemSettingChanged = false; private static class PackageEntryClickedListener implements @@ -85,9 +85,9 @@ public RecentLocationAccessPreferenceController(Context context, String key, super(context, key); mRecentLocationApps = recentLocationApps; mShowSystem = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false) + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, true) ? Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1 + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1) == 1 : false; } diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java b/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java index 0ea9ffbdacb..b45bca51c4f 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java @@ -40,7 +40,7 @@ public class RecentLocationAccessSeeAllFragment extends DashboardFragment { private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 1; private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 2; - private boolean mShowSystem = false; + private boolean mShowSystem = true; private MenuItem mShowSystemMenu; private MenuItem mHideSystemMenu; private RecentLocationAccessSeeAllPreferenceController mController; @@ -61,9 +61,9 @@ public void onAttach(Context context) { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mShowSystem = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false) + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, true) ? Settings.Secure.getInt(getContentResolver(), - Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1 + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1) == 1 : false; } diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java index d6586673f89..abda482a1e5 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java @@ -51,9 +51,9 @@ public class RecentLocationAccessSeeAllPreferenceController public RecentLocationAccessSeeAllPreferenceController(Context context, String key) { super(context, key); mShowSystem = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false) + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, true) ? Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1 + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1) == 1 : false; mRecentLocationAccesses = RecentAppOpsAccess.createForLocation(context); diff --git a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java index 39211ee5202..bee904efdc4 100644 --- a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java @@ -87,9 +87,9 @@ public void displayPreference(PreferenceScreen screen) { final List recentLocationRequests = new ArrayList<>(); final UserManager userManager = UserManager.get(mContext); final boolean showSystem = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false) + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, true) ? Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1 + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1) == 1 : false; for (RecentLocationApps.Request request : mRecentLocationApps.getAppListSorted( diff --git a/src/com/android/settings/network/ConnectivityChecksPrefController.java b/src/com/android/settings/network/ConnectivityChecksPrefController.java new file mode 100644 index 00000000000..dfd556f7fd1 --- /dev/null +++ b/src/com/android/settings/network/ConnectivityChecksPrefController.java @@ -0,0 +1,30 @@ +package com.android.settings.network; + +import android.content.Context; +import android.ext.settings.ConnChecksSetting; +import android.ext.settings.ExtSettings; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +public class ConnectivityChecksPrefController extends IntSettingPrefController { + + public ConnectivityChecksPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.CONNECTIVITY_CHECKS); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.conn_checks_grapheneos_server, ConnChecksSetting.VAL_GRAPHENEOS); + entries.add(R.string.conn_checks_google_server, ConnChecksSetting.VAL_STANDARD); + entries.add(R.string.conn_checks_disabled, ConnChecksSetting.VAL_DISABLED); + } + + @Override + public void addPrefsAfterList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.conn_checks_footer); + } +} diff --git a/src/com/android/settings/network/GoogleEuiccLpaController.java b/src/com/android/settings/network/GoogleEuiccLpaController.java new file mode 100644 index 00000000000..8997be513bb --- /dev/null +++ b/src/com/android/settings/network/GoogleEuiccLpaController.java @@ -0,0 +1,174 @@ +package com.android.settings.network; + +import android.Manifest; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.PatternMatcher; +import android.os.PowerManager; +import android.os.Process; +import android.os.UserHandle; +import android.permission.PermissionManager; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; + +import com.android.internal.util.GoogleEuicc; +import com.android.settings.R; +import com.android.settings.ext.AbstractTogglePrefController; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + +public class GoogleEuiccLpaController extends AbstractTogglePrefController implements DefaultLifecycleObserver { + private final PackageManager packageManager; + + public GoogleEuiccLpaController(Context context, String key) { + super(context, key); + packageManager = context.getPackageManager(); + } + + @Override + public boolean isChecked() { + try { + return packageManager.getApplicationEnabledSetting(GoogleEuicc.LPA_PKG_NAME) + == COMPONENT_ENABLED_STATE_ENABLED; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return false; + } + } + + @Override + public int getAvailabilityStatus() { + if (!Process.myUserHandle().isSystem()) { + return DISABLED_FOR_USER; + } + + boolean isPresent = false; + try { + var ai = packageManager.getApplicationInfo(GoogleEuicc.LPA_PKG_NAME, 0); + isPresent = ai.isSystemApp(); + } catch (PackageManager.NameNotFoundException e) { + } + + if (!isPresent) { + return UNSUPPORTED_ON_DEVICE; + } + + return GoogleEuicc.checkLpaDependencies() ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + preference.setEnabled(getAvailabilityStatus() == AVAILABLE); + } + + @Override + public boolean setChecked(boolean isChecked) { + if (!isChecked) { + var b = new AlertDialog.Builder(mContext); + b.setMessage(R.string.privileged_euicc_management_restart_to_disable_dialog); + b.setPositiveButton(R.string.privileged_euicc_management_restart_button, (dialogInterface, btn) -> { + var pm = mContext.getSystemService(PowerManager.class); + pm.reboot(null); + }); + b.show(); + return false; + } + + if (!GoogleEuicc.checkLpaDependencies()) { + // this is a race condition, toggle hasn't been grayed out yet + return false; + } + + ContentResolver cr = mContext.getContentResolver(); + + if (Settings.Global.getInt(cr, Settings.Global.BOOT_COUNT, 0) == 1) { + // When BOOT_COUNT is 1, Google's LPA assumes that it was started together with Google's + // SetupWizard, and changes its behavior in unwanted ways. + // + // To avoid this, require a reboot, which will increment BOOT_COUNT + + var b = new AlertDialog.Builder(mContext); + b.setMessage(R.string.privileged_euicc_management_restart_due_to_first_boot); + b.setPositiveButton(R.string.privileged_euicc_management_restart_button, (dialogInterface, btn) -> { + var pm = mContext.getSystemService(PowerManager.class); + pm.reboot(null); + }); + b.show(); + return false; + } + + PermissionManager permissionManager = mContext.getSystemService(PermissionManager.class); + + try { + String pkg = GoogleEuicc.LPA_PKG_NAME; + + UserHandle user = mContext.getUser(); + String perm = Manifest.permission.CAMERA; + permissionManager.revokeRuntimePermission(pkg, perm, user, null); + // Previously, Camera permission was auto-granted with the FLAG_PERMISSION_SYSTEM_FIXED, + // which made it unchangeable by the user. + // Removing FLAG_PERMISSION_USER_FIXED is needed to make sure that the app is always + // able to show a permission request dialog after being enabled + int permFlagsToRemove = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED + | PackageManager.FLAG_PERMISSION_USER_FIXED; + permissionManager.updatePermissionFlags(pkg, perm, permFlagsToRemove, 0, user); + + packageManager.setApplicationEnabledSetting(pkg, COMPONENT_ENABLED_STATE_ENABLED, 0); + { + // Google's LPA expects to be enabled at boot, when ACTION_LOCKED_BOOT_COMPLETED + // is normally sent. + // Upon receiving this broadcast, it performs additional initialization that is + // skipped otherwise, see RestoreUiccSlotSettingsOnBootReceiver and CompleteBootService + var intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED); + intent.setPackage(pkg); + mContext.sendBroadcast(intent); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private final BroadcastReceiver packageChangeListener = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (preference != null) { + updateState(preference); + } + } + }; + + @Override + public void onResume(@NonNull LifecycleOwner owner) { + var f = new IntentFilter(); + f.addAction(Intent.ACTION_PACKAGE_ADDED); + f.addAction(Intent.ACTION_PACKAGE_CHANGED); + f.addAction(Intent.ACTION_PACKAGE_REMOVED); + f.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + + f.addDataScheme("package"); + for (String pkg : GoogleEuicc.getLpaDependencies()) { + f.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL); + } + + f.addDataSchemeSpecificPart(GoogleEuicc.LPA_PKG_NAME, PatternMatcher.PATTERN_LITERAL); + + mContext.registerReceiver(packageChangeListener, f); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + mContext.unregisterReceiver(packageChangeListener); + } +} diff --git a/src/com/android/settings/network/RemoteProvisioningPrefController.java b/src/com/android/settings/network/RemoteProvisioningPrefController.java new file mode 100644 index 00000000000..64155249c91 --- /dev/null +++ b/src/com/android/settings/network/RemoteProvisioningPrefController.java @@ -0,0 +1,37 @@ +/* + * 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.network; + +import android.content.Context; +import android.ext.settings.ExtSettings; +import android.ext.settings.RemoteKeyProvisioningSettings; + +import com.android.settings.R; +import com.android.settings.ext.IntSettingPrefController; + +public class RemoteProvisioningPrefController extends IntSettingPrefController { + + public RemoteProvisioningPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.REMOTE_KEY_PROVISIONING_SERVER); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.remote_provisioning_enabled_grapheneos_proxy, RemoteKeyProvisioningSettings.GRAPHENEOS_PROXY); + entries.add(R.string.remote_provisioning_enabled_google_server, RemoteKeyProvisioningSettings.STANDARD_SERVER); + } +} diff --git a/src/com/android/settings/network/WidevineProvisioningPrefController.java b/src/com/android/settings/network/WidevineProvisioningPrefController.java new file mode 100644 index 00000000000..7ebc5d12bee --- /dev/null +++ b/src/com/android/settings/network/WidevineProvisioningPrefController.java @@ -0,0 +1,44 @@ +/* + * 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.network; + +import android.content.Context; +import android.ext.settings.ExtSettings; +import android.ext.settings.WidevineProvisioningSettings; + +import com.android.settings.R; +import androidx.preference.PreferenceScreen; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +public class WidevineProvisioningPrefController extends IntSettingPrefController { + + public WidevineProvisioningPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.WIDEVINE_PROVISIONING_SERVER); + } + + @Override + public void addPrefsAfterList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.widevine_provisioning_footer); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.widevine_provisioning_enabled_grapheneos_proxy, WidevineProvisioningSettings.WV_GRAPHENEOS_PROXY); + entries.add(R.string.widevine_provisioning_enabled_google_server, WidevineProvisioningSettings.WV_STANDARD_SERVER); + } +} \ No newline at end of file diff --git a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java index 9fb80a5d412..3d7d6a85ab6 100644 --- a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java +++ b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java @@ -91,7 +91,7 @@ public int getAvailabilityStatus(int subId) { CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) || carrierConfig.getBoolean( CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL)) { - visible = false; + visible = true; } else if (carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { visible = false; } else if (!isCallStateIdle()) { @@ -276,6 +276,7 @@ public void updateConfig() { } void setPreferenceEntries() { + boolean lteOnlyUnsupported = false; mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); clearAllEntries(); @@ -291,6 +292,7 @@ void setPreferenceEntries() { .addFormat(UiOptions.PresentFormat.addGlobalEntry); break; case ENABLED_NETWORKS_CDMA_NO_LTE_CHOICES: + lteOnlyUnsupported = true; uiOptions = uiOptions .setChoices(R.array.enabled_networks_cdma_no_lte_values) .addFormat(UiOptions.PresentFormat.add3gEntry) @@ -310,6 +312,7 @@ void setPreferenceEntries() { .addFormat(UiOptions.PresentFormat.add2gEntry); break; case ENABLED_NETWORKS_EXCEPT_GSM_LTE_CHOICES: + lteOnlyUnsupported = true; uiOptions = uiOptions .setChoices(R.array.enabled_networks_except_gsm_lte_values) .addFormat(UiOptions.PresentFormat.add3gEntry); @@ -327,6 +330,7 @@ void setPreferenceEntries() { .addFormat(UiOptions.PresentFormat.add3gEntry); break; case ENABLED_NETWORKS_EXCEPT_LTE_CHOICES: + lteOnlyUnsupported = true; uiOptions = uiOptions .setChoices(R.array.enabled_networks_except_lte_values) .addFormat(UiOptions.PresentFormat.add3gEntry) @@ -375,6 +379,11 @@ void setPreferenceEntries() { throw new IllegalArgumentException( uiOptions.getType().name() + " index error."); } + + if (!lteOnlyUnsupported){ + addLteOnlyEntry(); + } + // Compose options based on given values and formats. IntStream.range(0, formatList.size()).forEach(entryIndex -> { switch (formatList.get(entryIndex)) { @@ -561,6 +570,9 @@ void setPreferenceValueAndSummary(int networkMode) { break; } case TelephonyManagerConstants.NETWORK_MODE_LTE_ONLY: + setSummary(mShow4gForLTE + ? R.string.network_4G_only : R.string.network_lte_only); + break; case TelephonyManagerConstants.NETWORK_MODE_LTE_WCDMA: if (!mIsGlobalCdma) { setSelectedEntry( @@ -826,6 +838,16 @@ private void add1xEntry(int value) { mEntriesValue.add(value); } + private void addLteOnlyEntry() { + if (mShow4gForLTE) { + mEntries.add(mContext.getString(R.string.network_4G_only)); + mEntriesValue.add(TelephonyManagerConstants.NETWORK_MODE_LTE_ONLY); + } else { + mEntries.add(mContext.getString(R.string.network_lte_only)); + mEntriesValue.add(TelephonyManagerConstants.NETWORK_MODE_LTE_ONLY); + } + } + private void addCustomEntry(String name, int value) { mEntries.add(name); mEntriesValue.add(value); diff --git a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java index bdfeace1be6..535645a8647 100644 --- a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java +++ b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java @@ -137,7 +137,7 @@ private int getPreferredNetworkModeSummaryResId(int NetworkMode) { case TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA: return R.string.preferred_network_mode_lte_tdscdma_summary; case TelephonyManagerConstants.NETWORK_MODE_LTE_ONLY: - return R.string.preferred_network_mode_lte_summary; + return R.string.preferred_network_mode_lte_only_summary; case TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA_GSM: return R.string.preferred_network_mode_lte_tdscdma_gsm_summary; case TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: diff --git a/src/com/android/settings/password/ChooseLockGenericController.java b/src/com/android/settings/password/ChooseLockGenericController.java index cd9eb2fd44d..48dc2094d50 100644 --- a/src/com/android/settings/password/ChooseLockGenericController.java +++ b/src/com/android/settings/password/ChooseLockGenericController.java @@ -175,8 +175,9 @@ public boolean isScreenLockVisible(ScreenLockType type) { && !managedProfile; // Swipe doesn't make sense for profiles. case MANAGED: return mManagedPasswordProvider.isManagedPasswordChoosable(); - case PIN: case PATTERN: + return false; + case PIN: case PASSWORD: // Hide the secure lock screen options if the device doesn't support the secure lock // screen feature. diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index ed155bcb10c..29988582a5e 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -976,9 +976,6 @@ private void setAutoPinConfirmOption(boolean enabled, int length) { if (enabled && !mIsAlphaMode && isAutoPinConfirmPossible(length)) { mAutoPinConfirmOption.setVisibility(View.VISIBLE); mAutoConfirmSecurityMessage.setVisibility(View.VISIBLE); - if (!mIsAutoPinConfirmOptionSetManually) { - mAutoPinConfirmOption.setChecked(length == MIN_AUTO_PIN_REQUIREMENT_LENGTH); - } } else { mAutoPinConfirmOption.setVisibility(View.GONE); mAutoConfirmSecurityMessage.setVisibility(View.GONE); diff --git a/src/com/android/settings/security/AutoRebootPrefController.java b/src/com/android/settings/security/AutoRebootPrefController.java new file mode 100644 index 00000000000..1204b29c550 --- /dev/null +++ b/src/com/android/settings/security/AutoRebootPrefController.java @@ -0,0 +1,44 @@ +package com.android.settings.security; + +import android.content.Context; +import android.ext.settings.ExtSettings; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; + +public class AutoRebootPrefController extends IntSettingPrefController { + + public AutoRebootPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.AUTO_REBOOT_TIMEOUT); + } + + @Override + public void addPrefsBeforeList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.auto_reboot_footer, + "https://grapheneos.org/features#auto-reboot"); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.switch_off_text, 0); + entries.add(3, DAYS); // default + entries.add(2, DAYS); + entries.add(36, HOURS); + entries.add(1, DAYS); + entries.add(18, HOURS); + entries.add(12, HOURS); + entries.add(8, HOURS); + entries.add(4, HOURS); + entries.add(2, HOURS); + entries.add(1, HOURS); + entries.add(30, MINUTES); + entries.add(10, MINUTES); + } +} diff --git a/src/com/android/settings/security/DenyNewUsbPrefController.java b/src/com/android/settings/security/DenyNewUsbPrefController.java new file mode 100644 index 00000000000..f7185773c74 --- /dev/null +++ b/src/com/android/settings/security/DenyNewUsbPrefController.java @@ -0,0 +1,77 @@ +package com.android.settings.security; + +import android.content.Context; +import android.ext.settings.ExtSettings; +import android.os.SystemProperties; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.AbstractListPreferenceController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +import java.util.Arrays; +import java.util.List; + +import static android.ext.settings.ExtSettings.DENY_NEW_USB_DISABLED; +import static android.ext.settings.ExtSettings.DENY_NEW_USB_DYNAMIC; +import static android.ext.settings.ExtSettings.DENY_NEW_USB_ENABLED; +import static com.android.settings.ext.ExtSettingControllerHelper.getGlobalSettingAvailability; + +public class DenyNewUsbPrefController extends AbstractListPreferenceController { + + private final List list = Arrays.asList( + DENY_NEW_USB_DISABLED, + DENY_NEW_USB_DYNAMIC, + DENY_NEW_USB_ENABLED + ); + + public DenyNewUsbPrefController(Context ctx, String key) { + super(ctx, key); + } + + @Override + public int getAvailabilityStatus() { + return getGlobalSettingAvailability(mContext); + } + + @Override + protected void getEntries(Entries entries) { + entries.add(R.string.deny_new_usb_val_enabled, list.indexOf(DENY_NEW_USB_ENABLED)); + entries.add(R.string.deny_new_usb_val_dynamic, list.indexOf(DENY_NEW_USB_DYNAMIC)); + entries.add(R.string.deny_new_usb_val_disabled, list.indexOf(DENY_NEW_USB_DISABLED)); + } + + @Override + public void addPrefsAfterList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.deny_new_usb_footer, + "https://grapheneos.org/usage#usb-peripherals"); + } + + @Override + protected int getCurrentValue() { + return list.indexOf(ExtSettings.DENY_NEW_USB.get()); + } + + @Override + protected boolean setValue(int val) { + String strVal = list.get(val); + boolean res = ExtSettings.DENY_NEW_USB.put(strVal); + if (!res) { + return false; + } + if (DENY_NEW_USB_DYNAMIC.equals(strVal)) { + // when "dynamic" is written to the sysprop, the following hook is triggered in + // system/core/rootdir/init.rc : + + // on property:persist.security.deny_new_usb=dynamic + // - write /proc/sys/kernel/deny_new_usb 1 + + // But the fact that the setting was set by the user implies that the device is unlocked, + // and deny_new_usb should be disabled until the device gets locked + SystemProperties.set(ExtSettings.DENY_NEW_USB_TRANSIENT_PROP, + ExtSettings.DENY_NEW_USB_TRANSIENT_DISABLE); + } + return true; + } +} diff --git a/src/com/android/settings/security/ExecSpawningFragment.java b/src/com/android/settings/security/ExecSpawningFragment.java new file mode 100644 index 00000000000..1ab1f744617 --- /dev/null +++ b/src/com/android/settings/security/ExecSpawningFragment.java @@ -0,0 +1,34 @@ +package com.android.settings.security; + +import android.ext.settings.BoolSetting; +import android.ext.settings.ExtSettings; +import android.net.Uri; + +import com.android.settings.R; +import com.android.settings.ext.BoolSettingFragment; +import com.android.settingslib.widget.FooterPreference; + +public class ExecSpawningFragment extends BoolSettingFragment { + + @Override + protected BoolSetting getSetting() { + return ExtSettings.EXEC_SPAWNING; + } + + @Override + protected CharSequence getTitle() { + return getText(R.string.exec_spawning_title); + } + + @Override + protected CharSequence getMainSwitchTitle() { + return getText(R.string.exec_spawning_title_inner); + } + + @Override + protected FooterPreference makeFooterPref(FooterPreference.Builder builder) { + FooterPreference p = builder.setTitle(R.string.exec_spawning_footer).build(); + setFooterPrefLearnMoreUri(p, Uri.parse("https://grapheneos.org/usage#exec-spawning")); + return p; + } +} diff --git a/src/com/android/settings/security/ExecSpawningPrefController.java b/src/com/android/settings/security/ExecSpawningPrefController.java new file mode 100644 index 00000000000..cd2be5cfc71 --- /dev/null +++ b/src/com/android/settings/security/ExecSpawningPrefController.java @@ -0,0 +1,13 @@ +package com.android.settings.security; + +import android.content.Context; +import android.ext.settings.ExtSettings; + +import com.android.settings.ext.BoolSettingFragmentPrefController; + +public class ExecSpawningPrefController extends BoolSettingFragmentPrefController { + + public ExecSpawningPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.EXEC_SPAWNING); + } +} diff --git a/src/com/android/settings/security/ForceAppMemtag.kt b/src/com/android/settings/security/ForceAppMemtag.kt new file mode 100644 index 00000000000..6d83bb9c050 --- /dev/null +++ b/src/com/android/settings/security/ForceAppMemtag.kt @@ -0,0 +1,45 @@ +package com.android.settings.security + +import android.content.Context +import android.ext.settings.ExtSettings +import com.android.internal.os.Zygote +import com.android.settings.R +import com.android.settings.ext.AppPrefUtils +import com.android.settings.ext.BoolSettingFragment +import com.android.settings.ext.BoolSettingFragmentPrefController +import com.android.settingslib.widget.FooterPreference + +class ForceAppMemtagPrefController(ctx: Context, key: String) : + BoolSettingFragmentPrefController(ctx, key, ExtSettings.FORCE_APP_MEMTAG_BY_DEFAULT) { + + private val isSupported = Zygote.nativeSupportsMemoryTagging() + + override fun getAvailabilityStatus(): Int { + var res = super.getAvailabilityStatus() + + if (res == AVAILABLE && !isSupported) { + res = UNSUPPORTED_ON_DEVICE + } + + return res + } + + override fun getSummaryOn() = resText(R.string.memtag_in_3p_apps_enabled_by_default) + override fun getSummaryOff() = resText(R.string.memtag_in_3p_apps_disabled_by_default) +} + +class ForceAppMemtagFragment : BoolSettingFragment() { + + override fun getSetting() = ExtSettings.FORCE_APP_MEMTAG_BY_DEFAULT + + override fun getTitle() = resText(R.string.memtag_in_3p_apps_title) + + override fun getMainSwitchTitle() = resText(R.string.memtag_in_3p_apps_main_switch_title) + override fun getMainSwitchSummary() = resText(R.string.memtag_in_3p_apps_main_switch_summary) + + override fun makeFooterPref(builder: FooterPreference.Builder): FooterPreference { + val text = AppPrefUtils.getFooterForDefaultHardeningSetting(requireContext(), + R.string.memtag_in_3p_apps_footer) + return builder.setTitle(text).build() + } +} diff --git a/src/com/android/settings/security/NativeDebuggingFragment.java b/src/com/android/settings/security/NativeDebuggingFragment.java new file mode 100644 index 00000000000..fc80e7556c8 --- /dev/null +++ b/src/com/android/settings/security/NativeDebuggingFragment.java @@ -0,0 +1,34 @@ +package com.android.settings.security; + +import android.ext.settings.BoolSetting; +import android.ext.settings.ExtSettings; + +import com.android.settings.R; +import com.android.settings.ext.AppPrefUtils; +import com.android.settings.ext.DefaultOfPerAppSettingFragment; +import com.android.settingslib.widget.FooterPreference; + +public class NativeDebuggingFragment extends DefaultOfPerAppSettingFragment { + + @Override + protected BoolSetting getSetting() { + invertSetting = true; + return ExtSettings.ALLOW_NATIVE_DEBUG_BY_DEFAULT; + } + + @Override + protected CharSequence getTitle() { + return getText(R.string.native_debugging_title); + } + + @Override + protected CharSequence getMainSwitchTitle() { + return getText(R.string.native_debugging_main_switch); + } + + @Override + protected FooterPreference makeFooterPref(FooterPreference.Builder builder) { + String text = AppPrefUtils.getFooterForDefaultHardeningSetting(requireContext(), R.string.native_debugging_footer); + return builder.setTitle(text).build(); + } +} diff --git a/src/com/android/settings/security/NativeDebuggingPrefController.java b/src/com/android/settings/security/NativeDebuggingPrefController.java new file mode 100644 index 00000000000..e78d7bcd5e0 --- /dev/null +++ b/src/com/android/settings/security/NativeDebuggingPrefController.java @@ -0,0 +1,20 @@ +package com.android.settings.security; + +import android.content.Context; +import android.ext.settings.ExtSettings; + +import com.android.settings.R; +import com.android.settings.ext.BoolSettingFragmentPrefController; + +public class NativeDebuggingPrefController extends BoolSettingFragmentPrefController { + + public NativeDebuggingPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.ALLOW_NATIVE_DEBUG_BY_DEFAULT); + } + + public CharSequence getSummary() { + return mContext.getText(setting.get(mContext) ? + R.string.native_debugging_summary_default_3p_allowed : + R.string.native_debugging_summary_default_blocked); + } +} diff --git a/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java b/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java index ce5533e5c64..0983648d47d 100644 --- a/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java +++ b/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java @@ -50,13 +50,7 @@ public void updateState(Preference preference) { @Override public int getAvailabilityStatus() { - if (!mUserCaps.isAdmin()) { - return DISABLED_FOR_USER; - } else if (mUserCaps.disallowAddUser() || mUserCaps.disallowAddUserSetByAdmin()) { - return DISABLED_FOR_USER; - } else { - return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; - } + return UNSUPPORTED_ON_DEVICE; } @Override diff --git a/src/com/android/settings/users/SendCensoredNotificationsToCurrentUserPreferenceController.java b/src/com/android/settings/users/SendCensoredNotificationsToCurrentUserPreferenceController.java new file mode 100644 index 00000000000..637d7bbb782 --- /dev/null +++ b/src/com/android/settings/users/SendCensoredNotificationsToCurrentUserPreferenceController.java @@ -0,0 +1,60 @@ +package com.android.settings.users; + +import android.content.Context; +import android.provider.Settings; +import android.widget.Toast; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +public class SendCensoredNotificationsToCurrentUserPreferenceController + extends TogglePreferenceController { + private final UserCapabilities mUserCaps; + + public SendCensoredNotificationsToCurrentUserPreferenceController(Context context, String key) { + super(context, key); + mUserCaps = UserCapabilities.create(context); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mUserCaps.updateAddUserCapabilities(mContext); + if (!isAvailable()) { + preference.setVisible(false); + } else { + preference.setVisible(mUserCaps.mUserSwitcherEnabled); + } + } + + @Override + public int getAvailabilityStatus() { + return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SEND_CENSORED_NOTIFICATIONS_TO_CURRENT_USER, 0) != 0; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SEND_CENSORED_NOTIFICATIONS_TO_CURRENT_USER, isChecked ? 1 : 0); + } + + @Override + public CharSequence getSummary() { + return mContext.getString( + R.string.user_settings_send_censored_notifications_to_current_summary); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_system; + } +} diff --git a/src/com/android/settings/users/UserAppsInstallSettings.java b/src/com/android/settings/users/UserAppsInstallSettings.java new file mode 100644 index 00000000000..52312d90f5c --- /dev/null +++ b/src/com/android/settings/users/UserAppsInstallSettings.java @@ -0,0 +1,173 @@ +package com.android.settings.users; + +import static com.android.settings.users.UserDetailsSettings.EXTRA_USER_ID; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.utils.CandidateInfoExtra; +import com.android.settings.widget.RadioButtonPickerFragment; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.widget.CandidateInfo; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class UserAppsInstallSettings extends RadioButtonPickerFragment { + + private static final String INSTALL_ENABLED = "install_apps_enabled"; + private static final String INSTALL_FIRST_PARTY_ENABLED = "install_apps_first_party_enabled"; + private static final String INSTALL_DISABLED = "install_apps_disabled"; + private UserRestrictions userRestrictions; + + static void launch(Preference preference, int userId) { + + Bundle extras = preference.getExtras(); + extras.putInt(EXTRA_USER_ID, userId); + + new SubSettingLauncher(preference.getContext()) + .setDestination(UserAppsInstallSettings.class.getName()) + .setSourceMetricsCategory(extras.getInt(DashboardFragment.CATEGORY, + SettingsEnums.PAGE_UNKNOWN)) + .setTitleText(preference.getTitle()) + .setArguments(extras) + .launch(); + } + + private static String getCurrentInstallRestriction(UserRestrictions userRestrictions) { + if (userRestrictions.isSet(UserManager.DISALLOW_INSTALL_APPS)) { + return INSTALL_DISABLED; + } + + if (userRestrictions.isSet(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)) { + return INSTALL_FIRST_PARTY_ENABLED; + } + + return INSTALL_ENABLED; + } + + static String getDescription(Context context, UserRestrictions userRestrictions) { + switch (getCurrentInstallRestriction(userRestrictions)) { + case INSTALL_ENABLED: + return context.getString(R.string.user_app_install_enabled); + case INSTALL_FIRST_PARTY_ENABLED: + return context.getString(R.string.user_app_install_enabled_first_party_sources); + case INSTALL_DISABLED: + return context.getString(R.string.user_app_install_disabled); + default: + throw new IllegalStateException(); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Bundle args = requireArguments(); + int userId = args.getInt(EXTRA_USER_ID, UserHandle.USER_NULL); + + Context ctx = requireContext(); + UserManager userManager = ctx.getSystemService(UserManager.class); + UserInfo userInfo = userManager.getUserInfo(userId); + userRestrictions = new UserRestrictions(userManager, userInfo); + + super.onCreate(savedInstanceState); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(requireContext()); + setPreferenceScreen(ps); + } + + @Override + protected int getPreferenceScreenResId() { + return -1; + } + + @Override + protected List getCandidates() { + Context ctx = requireContext(); + ArrayList candidates = new ArrayList<>(3); + if (!userRestrictions.userInfo.isGuest()) { + candidates.add(new CandidateInfoExtra(ctx.getString(R.string.user_app_install_enabled), + ctx.getString(R.string.user_app_install_enabled_desc), + INSTALL_ENABLED, true)); + } + candidates.add(new CandidateInfoExtra(ctx.getString(R.string.user_app_install_enabled_first_party_sources), + ctx.getString(R.string.user_app_install_enabled_first_party_sources_desc), + INSTALL_FIRST_PARTY_ENABLED, true)); + candidates.add(new CandidateInfoExtra(ctx.getString(R.string.user_app_install_disabled), + ctx.getString(R.string.user_app_install_disabled_desc), + INSTALL_DISABLED, true)); + return candidates; + } + + @Override + public void bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, + String defaultKey, String systemDefaultKey) { + pref.setSingleLineTitle(false); + + if (info instanceof CandidateInfoExtra) { + var cie = (CandidateInfoExtra) info; + pref.setSummary(cie.loadSummary()); + } + } + + @Override + protected String getDefaultKey() { + return getCurrentInstallRestriction(userRestrictions); + } + + @Override + protected boolean setDefaultKey(String key) { + if (key == null) { + return false; + } + + switch (key) { + case INSTALL_ENABLED: + if (userRestrictions.userInfo.isGuest()) { + return false; + } + userRestrictions.set(UserManager.DISALLOW_INSTALL_APPS, false); + userRestrictions.set(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, false); + return true; + case INSTALL_FIRST_PARTY_ENABLED: + userRestrictions.set(UserManager.DISALLOW_INSTALL_APPS, false); + if (!userRestrictions.userInfo.isGuest()) { + userRestrictions.set(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true); + } + return true; + case INSTALL_DISABLED: + userRestrictions.set(UserManager.DISALLOW_INSTALL_APPS, true); + if (!userRestrictions.userInfo.isGuest()) { + userRestrictions.set(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true); + } + return true; + default: + return false; + } + } + + @Override + public int getMetricsCategory() { + return METRICS_CATEGORY_UNKNOWN; + } + + @Override + public void onResume() { + super.onResume(); + updateCandidates(); + } +} diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 402d4b18dd4..86685839971 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -66,6 +66,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access"; private static final String KEY_APP_COPYING = "app_copying"; + private static final String KEY_APP_INSTALLS = "app_installs"; + private static final String KEY_RUN_IN_BACKGROUND = "allow_run_in_background"; + /** Integer extra containing the userId to manage */ static final String EXTRA_USER_ID = "user_id"; @@ -78,7 +81,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment private static final int DIALOG_CONFIRM_GRANT_ADMIN = 7; /** Whether to enable the app_copying fragment. */ - private static final boolean SHOW_APP_COPYING_PREF = false; + private static final boolean SHOW_APP_COPYING_PREF = true; private static final int MESSAGE_PADDING = 20; private UserManager mUserManager; @@ -98,10 +101,13 @@ public class UserDetailsSettings extends SettingsPreferenceFragment Preference mRemoveUserPref; @VisibleForTesting SwitchPreference mGrantAdminPref; + Preference mAppsInstallsPref; + private SwitchPreference mRunInBackgroundPref; @VisibleForTesting /** The user being studied (not the user doing the studying). */ UserInfo mUserInfo; + private UserRestrictions userRestrictions; @Override public int getMetricsCategory() { @@ -130,6 +136,9 @@ public void onResume() { if (mGuestUserAutoCreated) { mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0); } + mAppsInstallsPref.setSummary(UserAppsInstallSettings.getDescription( + requireContext(), userRestrictions)); + mAppCopyingPref.setEnabled(!userRestrictions.isSet(UserManager.DISALLOW_INSTALL_APPS)); } @Override @@ -170,6 +179,9 @@ public boolean onPreferenceClick(Preference preference) { } else if (preference == mAppCopyingPref) { openAppCopyingScreen(); return true; + } else if (preference == mAppsInstallsPref) { + UserAppsInstallSettings.launch(preference, mUserInfo.id); + return true; } return false; } @@ -197,7 +209,11 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { showDialog(DIALOG_CONFIRM_GRANT_ADMIN); } return false; + } else if (preference == mRunInBackgroundPref) { + userRestrictions.set(UserManager.DISALLOW_RUN_IN_BACKGROUND, !((boolean) newValue)); + return true; } + return true; } @@ -340,6 +356,7 @@ void initialize(Context context, Bundle arguments) { boolean isNewUser = arguments.getBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, false); mUserInfo = mUserManager.getUserInfo(userId); + userRestrictions = new UserRestrictions(mUserManager, mUserInfo); mSwitchUserPref = findPreference(KEY_SWITCH_USER); mPhonePref = findPreference(KEY_ENABLE_TELEPHONY); @@ -349,6 +366,8 @@ void initialize(Context context, Bundle arguments) { mGrantAdminPref = findPreference(KEY_GRANT_ADMIN); mGrantAdminPref.setChecked(mUserInfo.isAdmin()); + mAppsInstallsPref = findPreference(KEY_APP_INSTALLS); + mRunInBackgroundPref = findPreference(KEY_RUN_IN_BACKGROUND); mSwitchUserPref.setTitle( context.getString(com.android.settingslib.R.string.user_switch_to_user, @@ -372,6 +391,8 @@ void initialize(Context context, Bundle arguments) { removePreference(KEY_GRANT_ADMIN); removePreference(KEY_APP_AND_CONTENT_ACCESS); removePreference(KEY_APP_COPYING); + removePreference(KEY_APP_INSTALLS); + removePreference(KEY_RUN_IN_BACKGROUND); } else { if (!Utils.isVoiceCapable(context)) { // no telephony removePreference(KEY_ENABLE_TELEPHONY); @@ -388,6 +409,7 @@ void initialize(Context context, Bundle arguments) { removePreference(KEY_APP_AND_CONTENT_ACCESS); } + mPhonePref.setChecked(!userRestrictions.isSet(UserManager.DISALLOW_OUTGOING_CALLS)); if (mUserInfo.isGuest()) { removePreference(KEY_ENABLE_TELEPHONY); mRemoveUserPref.setTitle(mGuestUserAutoCreated @@ -399,11 +421,11 @@ void initialize(Context context, Bundle arguments) { if (!SHOW_APP_COPYING_PREF) { removePreference(KEY_APP_COPYING); } + removePreference(KEY_RUN_IN_BACKGROUND); } else { - mPhonePref.setChecked(!mUserManager.hasUserRestriction( - UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId))); mRemoveUserPref.setTitle(R.string.user_remove_user); - removePreference(KEY_APP_COPYING); + mRunInBackgroundPref.setChecked(!userRestrictions.isSet( + UserManager.DISALLOW_RUN_IN_BACKGROUND)); } // Remove preference KEY_REMOVE_USER if DISALLOW_REMOVE_USER restriction is set @@ -415,10 +437,12 @@ void initialize(Context context, Bundle arguments) { } mRemoveUserPref.setOnPreferenceClickListener(this); + mAppsInstallsPref.setOnPreferenceClickListener(this); mPhonePref.setOnPreferenceChangeListener(this); mGrantAdminPref.setOnPreferenceChangeListener(this); mAppAndContentAccessPref.setOnPreferenceClickListener(this); mAppCopyingPref.setOnPreferenceClickListener(this); + mRunInBackgroundPref.setOnPreferenceChangeListener(this); } } @@ -474,9 +498,9 @@ void switchUser() { private void enableCallsAndSms(boolean enabled) { mPhonePref.setChecked(enabled); - UserHandle userHandle = UserHandle.of(mUserInfo.id); - mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled, userHandle); - mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle); + userRestrictions.set(UserManager.DISALLOW_OUTGOING_CALLS, !enabled); + // SMS is always disabled for guest + userRestrictions.set(UserManager.DISALLOW_SMS, mUserInfo.isGuest() || !enabled); } /** diff --git a/src/com/android/settings/users/UserRestrictions.java b/src/com/android/settings/users/UserRestrictions.java new file mode 100644 index 00000000000..3daa2c453dc --- /dev/null +++ b/src/com/android/settings/users/UserRestrictions.java @@ -0,0 +1,49 @@ +package com.android.settings.users; + +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; + +import java.util.List; + +final class UserRestrictions { + + final UserManager userManager; + final UserInfo userInfo; + + UserRestrictions(UserManager userManager, UserInfo userInfo) { + this.userManager = userManager; + this.userInfo = userInfo; + } + + boolean isSet(String restrictionKey) { + final boolean isSetFromUser = userManager.hasUserRestriction(restrictionKey, userInfo.getUserHandle()); + if (userInfo.isGuest()) { + return isSetFromUser || userManager.getDefaultGuestRestrictions().getBoolean(restrictionKey); + } + + return isSetFromUser; + } + + void set(String restrictionKey, boolean enableRestriction) { + Bundle defaultGuestRestrictions = userManager.getDefaultGuestRestrictions(); + if (userInfo.isGuest()) { + defaultGuestRestrictions.putBoolean(restrictionKey, enableRestriction); + userManager.setDefaultGuestRestrictions(defaultGuestRestrictions); + + List users = userManager.getAliveUsers(); + for (UserInfo user : users) { + if (user.isGuest()) { + UserHandle userHandle = userInfo.getUserHandle(); + for (String key : defaultGuestRestrictions.keySet()) { + userManager.setUserRestriction( + key, defaultGuestRestrictions.getBoolean(key), userHandle); + } + } + } + } else { + userManager.setUserRestriction(restrictionKey, enableRestriction, userInfo.getUserHandle()); + } + } +} diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index b0816fdf4ad..018a267cdcf 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -137,6 +137,8 @@ public class UserSettings extends SettingsPreferenceFragment private static final String KEY_REMOVE_GUEST_ON_EXIT = "remove_guest_on_exit"; private static final String KEY_GUEST_USER_CATEGORY = "guest_user_category"; private static final String KEY_ALLOW_MULTIPLE_USERS = "allow_multiple_users"; + private static final String KEY_SEND_CENSORED_NOTIFICATIONS = + "user_settings_send_censored_notifications_to_current"; private static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in"; @@ -225,6 +227,7 @@ public class UserSettings extends SettingsPreferenceFragment private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController; private GuestTelephonyPreferenceController mGuestTelephonyPreferenceController; private RemoveGuestOnExitPreferenceController mRemoveGuestOnExitPreferenceController; + private SendCensoredNotificationsToCurrentUserPreferenceController mSendCensoredNotificationsToCurrentUserPreferenceController; private MultiUserTopIntroPreferenceController mMultiUserTopIntroPreferenceController; private TimeoutToDockUserPreferenceController mTimeoutToDockUserPreferenceController; private UserCreatingDialog mUserCreatingDialog; @@ -322,6 +325,10 @@ public void onCreate(Bundle icicle) { mRemoveGuestOnExitPreferenceController = new RemoveGuestOnExitPreferenceController( activity, KEY_REMOVE_GUEST_ON_EXIT, this, mHandler); + mSendCensoredNotificationsToCurrentUserPreferenceController = + new SendCensoredNotificationsToCurrentUserPreferenceController(activity, + KEY_SEND_CENSORED_NOTIFICATIONS); + mMultiUserTopIntroPreferenceController = new MultiUserTopIntroPreferenceController(activity, KEY_MULTIUSER_TOP_INTRO); @@ -332,11 +339,14 @@ public void onCreate(Bundle icicle) { mAddUserWhenLockedPreferenceController.displayPreference(screen); mGuestTelephonyPreferenceController.displayPreference(screen); mRemoveGuestOnExitPreferenceController.displayPreference(screen); + mSendCensoredNotificationsToCurrentUserPreferenceController.displayPreference(screen); mMultiUserTopIntroPreferenceController.displayPreference(screen); mTimeoutToDockUserPreferenceController.displayPreference(screen); screen.findPreference(mAddUserWhenLockedPreferenceController.getPreferenceKey()) .setOnPreferenceChangeListener(mAddUserWhenLockedPreferenceController); + screen.findPreference(mSendCensoredNotificationsToCurrentUserPreferenceController.getPreferenceKey()) + .setOnPreferenceChangeListener(mSendCensoredNotificationsToCurrentUserPreferenceController); screen.findPreference(mGuestTelephonyPreferenceController.getPreferenceKey()) .setOnPreferenceChangeListener(mGuestTelephonyPreferenceController); @@ -420,6 +430,9 @@ public void onResume() { mTimeoutToDockUserPreferenceController.getPreferenceKey())); mRemoveGuestOnExitPreferenceController.updateState(screen.findPreference( mRemoveGuestOnExitPreferenceController.getPreferenceKey())); + mSendCensoredNotificationsToCurrentUserPreferenceController.updateState(screen.findPreference( + mSendCensoredNotificationsToCurrentUserPreferenceController.getPreferenceKey())); + if (mShouldUpdateUserList) { updateUI(); } @@ -1303,6 +1316,10 @@ void updateUserList() { mGuestTelephonyPreferenceController.getPreferenceKey()); mGuestTelephonyPreferenceController.updateState(guestCallPreference); + final Preference sendCensoredNotifs = getPreferenceScreen().findPreference( + mSendCensoredNotificationsToCurrentUserPreferenceController.getPreferenceKey()); + mSendCensoredNotificationsToCurrentUserPreferenceController.updateState(sendCensoredNotifs); + final Preference multiUserTopIntroPreference = getPreferenceScreen().findPreference( mMultiUserTopIntroPreferenceController.getPreferenceKey()); mMultiUserTopIntroPreferenceController.updateState(multiUserTopIntroPreference); @@ -1744,6 +1761,8 @@ public List getNonIndexableKeysFromXml(Context context, int xmlResId, new AddUserWhenLockedPreferenceController( context, KEY_ADD_USER_WHEN_LOCKED); controller.updateNonIndexableKeys(niks); + new SendCensoredNotificationsToCurrentUserPreferenceController(context, + KEY_SEND_CENSORED_NOTIFICATIONS).updateNonIndexableKeys(niks); new AutoSyncDataPreferenceController(context, null /* parent */) .updateNonIndexableKeys(niks); new AutoSyncPersonalDataPreferenceController(context, null /* parent */) diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java index 9450bb93715..2cedbdaf42c 100644 --- a/src/com/android/settings/widget/RadioButtonPickerFragment.java +++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java @@ -53,7 +53,7 @@ public abstract class RadioButtonPickerFragment extends SettingsPreferenceFragme SelectorWithWidgetPreference.OnClickListener { @VisibleForTesting - static final String EXTRA_FOR_WORK = "for_work"; + protected static final String EXTRA_FOR_WORK = "for_work"; private static final String TAG = "RadioButtonPckrFrgmt"; @VisibleForTesting boolean mAppendStaticPreferences = false; @@ -157,6 +157,12 @@ public void bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { } + protected void addPrefsBeforeList(PreferenceScreen screen) { + if (!mAppendStaticPreferences) { + addStaticPreferences(screen); + } + } + public void updateCandidates() { mCandidates.clear(); final List candidateList = getCandidates(); @@ -172,9 +178,7 @@ public void updateCandidates() { if (mIllustrationId != 0) { addIllustration(screen); } - if (!mAppendStaticPreferences) { - addStaticPreferences(screen); - } + addPrefsBeforeList(screen); final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId(); if (shouldShowItemNone()) { @@ -202,6 +206,10 @@ public void updateCandidates() { } } mayCheckOnlyRadioButton(); + addPrefsAfterList(screen); + } + + protected void addPrefsAfterList(PreferenceScreen screen) { if (mAppendStaticPreferences) { addStaticPreferences(screen); } diff --git a/src/com/android/settings/wifi/WifiAutoOffPrefController.java b/src/com/android/settings/wifi/WifiAutoOffPrefController.java new file mode 100644 index 00000000000..b8ec16bd69a --- /dev/null +++ b/src/com/android/settings/wifi/WifiAutoOffPrefController.java @@ -0,0 +1,39 @@ +package com.android.settings.wifi; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.ext.settings.ExtSettings; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.ext.AutoOffSetting; +import com.android.settings.ext.IntSettingPrefController; +import com.android.settings.ext.RadioButtonPickerFragment2; + +public class WifiAutoOffPrefController extends IntSettingPrefController { + + public WifiAutoOffPrefController(Context ctx, String key) { + super(ctx, key, ExtSettings.WIFI_AUTO_OFF); + } + + @Override + public int getAvailabilityStatus() { + int r = super.getAvailabilityStatus(); + if (r == AVAILABLE) { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI) ? + AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + return r; + } + + @Override + public void addPrefsBeforeList(RadioButtonPickerFragment2 fragment, PreferenceScreen screen) { + addFooterPreference(screen, R.string.wifi_auto_off_footer); + } + + @Override + protected void getEntries(Entries entries) { + AutoOffSetting.getEntries(entries); + } +} diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java index 0647a770915..78e968d942e 100644 --- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java +++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java @@ -781,7 +781,7 @@ private void refreshWifiType() { } private int getMacAddressTitle() { - if (mWifiEntry.getPrivacy() == WifiEntry.PRIVACY_RANDOMIZED_MAC) { + if (mWifiEntry.getPrivacy() != WifiEntry.PRIVACY_DEVICE_MAC) { return mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED ? R.string.wifi_advanced_randomized_mac_address_title : R.string.wifi_advanced_randomized_mac_address_disconnected_title; diff --git a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java index 632a5624c4d..d695b8a825a 100644 --- a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java +++ b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java @@ -41,6 +41,10 @@ public class WifiPrivacyPreferenceController2 extends BasePreferenceController i private WifiEntry mWifiEntry; private Preference mPreference; + private static final int PREF_RANDOMIZATION_ALWAYS = 0; + private static final int PREF_RANDOMIZATION_PERSISTENT = 1; + private static final int PREF_RANDOMIZATION_NONE = 2; + public WifiPrivacyPreferenceController2(Context context) { super(context, KEY_WIFI_PRIVACY); @@ -98,8 +102,6 @@ int getRandomizationValue() { return mWifiEntry.getPrivacy(); } - private static final int PREF_RANDOMIZATION_PERSISTENT = 0; - private static final int PREF_RANDOMIZATION_NONE = 1; /** * Returns preference index value. @@ -108,8 +110,14 @@ int getRandomizationValue() { * @return index value of preference */ public static int translateMacRandomizedValueToPrefValue(int macRandomized) { - return (macRandomized == WifiEntry.PRIVACY_RANDOMIZED_MAC) - ? PREF_RANDOMIZATION_PERSISTENT : PREF_RANDOMIZATION_NONE; + switch (macRandomized) { + case WifiEntry.PRIVACY_RANDOMIZED_MAC: + return PREF_RANDOMIZATION_PERSISTENT; + case WifiEntry.PRIVACY_DEVICE_MAC: + return PREF_RANDOMIZATION_NONE; + default: + return PREF_RANDOMIZATION_ALWAYS; + } } /** @@ -119,8 +127,14 @@ public static int translateMacRandomizedValueToPrefValue(int macRandomized) { * @return mac randomized value */ public static int translatePrefValueToMacRandomizedValue(int prefMacRandomized) { - return (prefMacRandomized == PREF_RANDOMIZATION_PERSISTENT) - ? WifiEntry.PRIVACY_RANDOMIZED_MAC : WifiEntry.PRIVACY_DEVICE_MAC; + switch (prefMacRandomized) { + case PREF_RANDOMIZATION_PERSISTENT: + return WifiEntry.PRIVACY_RANDOMIZED_MAC; + case PREF_RANDOMIZATION_NONE: + return WifiEntry.PRIVACY_DEVICE_MAC; + default: + return WifiEntry.PRIVACY_RANDOMIZATION_ALWAYS; + } } private void updateSummary(ListPreference preference, int macRandomized) { @@ -150,6 +164,8 @@ private int getWifiEntryPrivacy(WifiConfiguration wifiConfiguration) { return WifiEntry.PRIVACY_DEVICE_MAC; case WifiConfiguration.RANDOMIZATION_PERSISTENT: return WifiEntry.PRIVACY_RANDOMIZED_MAC; + case WifiConfiguration.RANDOMIZATION_ALWAYS: + return WifiEntry.PRIVACY_RANDOMIZATION_ALWAYS; default: return WifiEntry.PRIVACY_UNKNOWN; }