diff --git a/res/values/strings.xml b/res/values/strings.xml index 54b7f67b338..6c79d60a2c7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10429,6 +10429,10 @@ Allow this app to show notifications that take up the full screen when the device is locked. Apps may use these to highlight alarms, incoming calls, or other urgent notifications. + Write system preferences + Allow this app to modify system preferences on your behalf + This permission allows an app to modify core system preferences. + Media management apps diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml index d0fe980d82b..f3a5f3216c7 100644 --- a/res/xml/special_access.xml +++ b/res/xml/special_access.xml @@ -106,6 +106,11 @@ android:title="@string/full_screen_intent_title" settings:controller="com.android.settings.spa.app.specialaccess.UseFullScreenIntentPreferenceController" /> + + credentials = new ArrayList<>(); credentials.addAll(getCredentialsForUid(processKeystore, systemUid).values()); - if (wifiKeystore != null) { - credentials.addAll(getCredentialsForUid(wifiKeystore, wifiUid).values()); + + UserManager userManager = getContext().getSystemService(UserManager.class); + if (userManager.isAdminUser()) { + wifiKeystore = KeyStore.getInstance(KEYSTORE_PROVIDER); + wifiKeystore.load( + new AndroidKeyStoreLoadStoreParameter(KeyProperties.NAMESPACE_WIFI)); + credentials.addAll( + getCredentialsForUid(wifiKeystore, Process.WIFI_UID).values()); } + return credentials; } catch (Exception e) { throw new RuntimeException("Failed to load credentials from Keystore.", e); diff --git a/src/com/android/settings/accessibility/PaletteListPreference.java b/src/com/android/settings/accessibility/PaletteListPreference.java index eec8b5a6cde..c29113cd116 100644 --- a/src/com/android/settings/accessibility/PaletteListPreference.java +++ b/src/com/android/settings/accessibility/PaletteListPreference.java @@ -24,6 +24,7 @@ import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidt import static com.google.common.primitives.Ints.max; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Paint.FontMetrics; import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; @@ -39,6 +40,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.android.settingslib.Utils; import com.google.common.primitives.Floats; import com.google.common.primitives.Ints; @@ -128,6 +130,8 @@ public final class PaletteListPreference extends Preference { final List paletteColors = getPaletteColors(context); final List paletteData = getPaletteData(context); + final ColorStateList textColor = + Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary); final float textPadding = context.getResources().getDimension(R.dimen.accessibility_layout_margin_start_end); final String maxLengthData = @@ -143,6 +147,7 @@ public final class PaletteListPreference extends Preference { for (int i = 0; i < paletteData.size(); ++i) { final TextView textView = new TextView(context); textView.setText(paletteData.get(i)); + textView.setTextColor(textColor); textView.setHeight(paletteItemHeight); textView.setPaddingRelative(Math.round(textPadding), 0, 0, 0); textView.setGravity(Gravity.CENTER_VERTICAL); diff --git a/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java b/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java new file mode 100644 index 00000000000..171df6a3d39 --- /dev/null +++ b/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2024 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.localepicker; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceCategory; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.app.LocaleHelper; +import com.android.internal.app.LocaleStore; +import com.android.internal.app.SystemLocaleCollector; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.widget.PreferenceCategoryController; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import com.google.android.material.appbar.AppBarLayout; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * A locale picker fragment to show region country and numbering system. + * + *

It shows suggestions at the top, then the rest of the locales. + * Allows the user to search for locales using both their native name and their name in the + * default locale.

+ */ +public class RegionAndNumberingSystemPickerFragment extends DashboardFragment { + private static final String TAG = "RegionAndNumberingSystemPickerFragment"; + + private RecyclerView mRecyclerView; + private AppBarLayout mAppBarLayout; + private Activity mActivity; + + @Override + public void onCreate(@NonNull Bundle icicle) { + super.onCreate(icicle); + mActivity = getActivity(); + if (mActivity.isFinishing()) { + return; + } + } + + @Override + public @NonNull View onCreateView(@NonNull LayoutInflater inflater, + @NonNull ViewGroup container, @NonNull Bundle savedInstanceState) { + mAppBarLayout = mActivity.findViewById(R.id.app_bar); + mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recycler_view); + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.system_language_picker; + } + + @Override + protected List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getSettingsLifecycle()); + } + + private List buildPreferenceControllers( + @NonNull Context context, @Nullable Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + + // TODO: b/30358431 - Add preference of region locales. + + return controllers; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.system_language_picker); + + @Override + public int getMetricsCategory() { + return 0; + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeBlurbPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeBlurbPreferenceController.java index 097b40db3d3..4275f221332 100644 --- a/src/com/android/settings/notification/modes/ZenModeBlurbPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeBlurbPreferenceController.java @@ -40,6 +40,11 @@ class ZenModeBlurbPreferenceController extends AbstractZenModePreferenceControll super(context, key); } + @Override + public boolean isAvailable(@NonNull ZenMode zenMode) { + return !zenMode.isCustomManual(); + } + @Override void updateState(Preference preference, @NonNull ZenMode zenMode) { preference.setTitle(getModeBlurb(zenMode)); diff --git a/src/com/android/settings/security/CredentialStorage.java b/src/com/android/settings/security/CredentialStorage.java index b62aeae9382..b1c65a7c3c0 100644 --- a/src/com/android/settings/security/CredentialStorage.java +++ b/src/com/android/settings/security/CredentialStorage.java @@ -128,22 +128,12 @@ public final class CredentialStorage extends FragmentActivity { final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyProperties.UID_SELF); - if (uid != KeyProperties.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) { - final int dstUserId = UserHandle.getUserId(uid); - - // Restrict install target to the wifi uid. - if (uid != Process.WIFI_UID) { + if (uid != KeyProperties.UID_SELF && uid != Process.WIFI_UID) { + if (!UserHandle.isSameUser(uid, Process.myUid())) { Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs" + " may only target wifi uids"); return true; } - - final Intent installIntent = new Intent(ACTION_INSTALL) - .setPackage(getPackageName()) - .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) - .putExtras(bundle); - startActivityAsUser(installIntent, new UserHandle(dstUserId)); - return true; } String alias = bundle.getString(Credentials.EXTRA_USER_KEY_ALIAS, null); diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index 8b481797907..7702db6bcde 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -42,6 +42,7 @@ import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider import com.android.settings.spa.app.specialaccess.TurnScreenOnAppsAppListProvider import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider +import com.android.settings.spa.app.specialaccess.WriteSystemPreferencesAppListProvider import com.android.settings.spa.app.storage.StorageAppListPageProvider import com.android.settings.spa.core.instrumentation.SpaLogMetricsProvider import com.android.settings.spa.core.instrumentation.SpaLogProvider @@ -80,6 +81,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { NfcTagAppsSettingsProvider, LongBackgroundTasksAppListProvider, TurnScreenOnAppsAppListProvider, + WriteSystemPreferencesAppListProvider, ) } diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt index f7e53ec568b..ddddd8c1406 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt @@ -42,6 +42,7 @@ import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListPro import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider +import com.android.settings.spa.app.specialaccess.WriteSystemPreferencesAppListProvider import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.widget.scaffold.RegularScaffold @@ -167,6 +168,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) { InstallUnknownAppsListProvider.InfoPageEntryItem(app) InteractAcrossProfilesDetailsPreference(app) AlarmsAndRemindersAppListProvider.InfoPageEntryItem(app) + WriteSystemPreferencesAppListProvider.InfoPageEntryItem(app) } Category(title = stringResource(R.string.app_install_details_group_title)) { diff --git a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt index 41e5c849eed..c9ff8a05618 100644 --- a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt +++ b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt @@ -70,6 +70,7 @@ object SpecialAppAccessPageProvider : SettingsPageProvider { WifiControlAppListProvider, LongBackgroundTasksAppListProvider, TurnScreenOnAppsAppListProvider, + WriteSystemPreferencesAppListProvider, ) .map { it.buildAppListInjectEntry().setLink(fromPage = owner).build() } } diff --git a/src/com/android/settings/spa/app/specialaccess/WriteSystemPreferences.kt b/src/com/android/settings/spa/app/specialaccess/WriteSystemPreferences.kt new file mode 100644 index 00000000000..e4b9ef146d7 --- /dev/null +++ b/src/com/android/settings/spa/app/specialaccess/WriteSystemPreferences.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 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.spa.app.specialaccess + +import android.Manifest +import android.app.AppOpsManager +import android.content.Context +import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps +import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel +import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord +import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +object WriteSystemPreferencesAppListProvider : TogglePermissionAppListProvider { + override val permissionType = "WriteSystemPreferences" + override fun createModel(context: Context) = WriteSystemPreferencesAppListModel(context) +} + +class WriteSystemPreferencesAppListModel(context: Context) : AppOpPermissionListModel(context) { + override val pageTitleResId = R.string.write_system_preferences_page_title + override val switchTitleResId = R.string.write_system_preferences_switch_title + override val footerResId = R.string.write_system_preferences_footer_description + override val appOps = AppOps( + op = AppOpsManager.OP_WRITE_SYSTEM_PREFERENCES, + setModeByUid = true, + ) + override val permission = Manifest.permission.WRITE_SYSTEM_PREFERENCES + + override fun filter(userIdFlow: Flow, recordListFlow: Flow>): + Flow> { + return super.filter(userIdFlow, recordListFlow).map { recordList -> + recordList.filter { app -> + // Only apps that have READ_SYSTEM_PREFERENCES can utilize WRITE_SYSTEM_PREFERENCES. + // This write permission is (currently) non-functionality without the corresponding + // read permission, and the read permission can only be granted via pre-grant or + // role. As such, we don't show apps that are "requesting" this permission without + // holding the read permission, as it would create confusion and not provide them + // any functionality. + with (PackageManagers) { + app.app.hasGrantPermission(Manifest.permission.READ_SYSTEM_PREFERENCES) + } + } + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/spa/app/specialaccess/WriteSystemPreferencesPreferenceController.kt b/src/com/android/settings/spa/app/specialaccess/WriteSystemPreferencesPreferenceController.kt new file mode 100644 index 00000000000..fb808fa8199 --- /dev/null +++ b/src/com/android/settings/spa/app/specialaccess/WriteSystemPreferencesPreferenceController.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 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.spa.app.specialaccess + +import android.content.Context +import androidx.preference.Preference +import com.android.settings.core.BasePreferenceController +import com.android.settings.spa.SpaActivity.Companion.startSpaActivity +import com.android.settingslib.flags.Flags + +class WriteSystemPreferencesPreferenceController( + context: Context, key: String +) : BasePreferenceController(context, key) { + override fun getAvailabilityStatus(): Int { + return if (Flags.settingsCatalyst() && Flags.writeSystemPreferencePermissionEnabled()) { + AVAILABLE + } else { + UNSUPPORTED_ON_DEVICE + } + } + + override fun handlePreferenceTreeClick(preference: Preference?): Boolean { + return if (preference?.key == mPreferenceKey) { + mContext.startSpaActivity(WriteSystemPreferencesAppListProvider.getAppListRoute()) + true + } else { + false + } + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java index 759b180206f..8ff3a0e6639 100644 --- a/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java @@ -16,17 +16,20 @@ package com.android.settings.accessibility; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.content.res.ColorStateList; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settingslib.Utils; import org.junit.Before; import org.junit.Test; @@ -53,12 +56,20 @@ public final class PaletteListPreferenceTest { @Test public void initPaletteView_success() { + final int expectedCount = + mContext.getResources().getStringArray(R.array.setting_palette_data).length; + final ColorStateList expectedTextColor = + Utils.getColorAttr(mContext, android.R.attr.textColorPrimary); + mPaletteListPreference.onBindViewHolder(mPreferenceViewHolder); final ViewGroup viewGroup = mPreferenceViewHolder.itemView.findViewById(R.id.palette_view); - final int expectedCount = - mContext.getResources().getStringArray(R.array.setting_palette_data).length; - assertEquals(expectedCount, viewGroup.getChildCount()); + final int childCount = viewGroup.getChildCount(); + assertThat(childCount).isEqualTo(expectedCount); + for (int i = 0; i < childCount; i++) { + final TextView textView = (TextView) viewGroup.getChildAt(i); + assertThat(textView.getTextColors()).isEqualTo(expectedTextColor); + } } }