From bf1452afb2178d8c096208bfacd384e91a3c92da Mon Sep 17 00:00:00 2001 From: Marcello Galhardo Date: Fri, 24 Mar 2023 18:36:46 +0000 Subject: [PATCH 01/10] Add Note Task Shortcut to Settings Test: manual Fixes: b/272757160 Change-Id: I9ce69aa674a774e9150d16718b9558bea7a5560e --- AndroidManifest.xml | 16 +++ res/drawable/ic_note_task_shortcut_widget.xml | 31 +++++ res/values/strings.xml | 3 + .../CreateNoteTaskShortcutActivity.kt | 113 ++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 res/drawable/ic_note_task_shortcut_widget.xml create mode 100644 src/com/android/settings/notetask/shortcut/CreateNoteTaskShortcutActivity.kt diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ccf002a8e1d..cb57321daf7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4824,6 +4824,22 @@ + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index f23fb5f7437..05248790e83 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12019,4 +12019,7 @@ "This app can only be opened in 1 window" + + + Notetaking diff --git a/src/com/android/settings/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/src/com/android/settings/notetask/shortcut/CreateNoteTaskShortcutActivity.kt new file mode 100644 index 00000000000..b9846226df6 --- /dev/null +++ b/src/com/android/settings/notetask/shortcut/CreateNoteTaskShortcutActivity.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 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.notetask.shortcut + +import android.app.Activity +import android.app.role.RoleManager +import android.app.role.RoleManager.ROLE_NOTES +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.Icon +import android.os.Bundle +import android.os.PersistableBundle +import android.os.UserHandle +import androidx.activity.ComponentActivity +import androidx.core.content.getSystemService +import com.android.settings.R + +/** + * Activity responsible for create a shortcut for notes action. If the shortcut is enabled, a new + * shortcut will appear in the widget picker. If the shortcut is selected, the Activity here will be + * launched, creating a new shortcut for [CreateNoteTaskShortcutActivity], and will finish. + * + * @see Creating + * a custom shortcut activity + */ +internal class CreateNoteTaskShortcutActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + val roleManager = requireNotNull(getSystemService()) + val shortcutManager = requireNotNull(getSystemService()) + + super.onCreate(savedInstanceState) + + val shortcutInfo = roleManager.createNoteShortcutInfoAsUser(context = this, user) + val shortcutIntent = shortcutManager.createShortcutResultIntent(shortcutInfo) + setResult(Activity.RESULT_OK, shortcutIntent) + + finish() + } + + private companion object { + + private const val SHORTCUT_ID = "note_task_shortcut_id" + private const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = + "extra_shortcut_badge_override_package" + private const val ACTION_LAUNCH_NOTE_TASK = "com.android.systemui.action.LAUNCH_NOTE_TASK" + + private fun RoleManager.createNoteShortcutInfoAsUser( + context: Context, + user: UserHandle, + ): ShortcutInfo? { + val systemUiComponent = context.getSystemUiComponent() ?: return null + + val extras = PersistableBundle() + getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName -> + // Set custom app badge using the icon from ROLES_NOTES default app. + extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName) + } + + val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + + val intent = Intent(ACTION_LAUNCH_NOTE_TASK).apply { + setPackage(systemUiComponent.packageName) + } + + return ShortcutInfo.Builder(context, SHORTCUT_ID) + .setIntent(intent) + .setShortLabel(context.getString(R.string.note_task_button_label)) + .setLongLived(true) + .setIcon(icon) + .setExtras(extras) + .build() + } + + private fun RoleManager.getDefaultRoleHolderAsUser( + role: String, + user: UserHandle, + ): String? = getRoleHoldersAsUser(role, user).firstOrNull() + + private fun Context.getSystemUiComponent(): ComponentName? { + val flattenName = getString( + com.android.internal.R.string.config_systemUIServiceComponent) + check(flattenName.isNotEmpty()) { + "No 'com.android.internal.R.string.config_systemUIServiceComponent' resource" + } + return try { + ComponentName.unflattenFromString(flattenName) + } catch (e: RuntimeException) { + val message = "Invalid component name defined by 'com.android.internal.R.string." + + "config_systemUIServiceComponent' resource: $flattenName" + throw IllegalStateException(message, e) + } + } + } +} From c688f93ed94c4a86a43f1e507d63975ca4564640 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 10 Apr 2023 01:32:35 +0800 Subject: [PATCH 02/10] Display App installed in other user in All Apps Only for admin user. Also clean up unused getInstallationStatus(). Fix: 277299765 Test: Manually with All Apps when multiple users is on Test: Unit test Change-Id: I4de681c101a605e3517dcd8765bf7a95d1b76417 --- src/com/android/settings/Utils.java | 11 --------- .../android/settings/spa/app/AllAppList.kt | 11 +++++++-- .../app/appinfo/AppNotificationPreference.kt | 3 +++ .../spa/app/appinfo/PackageInfoPresenter.kt | 4 +++- .../spa/app/specialaccess/PictureInPicture.kt | 8 ++++--- .../src/com/android/settings/UtilsTest.java | 24 ------------------- .../settings/spa/app/AllAppListTest.kt | 19 +++++++++++++++ .../appinfo/AppNotificationPreferenceTest.kt | 23 ++++++++++++++---- .../app/specialaccess/PictureInPictureTest.kt | 1 + 9 files changed, 58 insertions(+), 46 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index aadca5c4be5..388a510211f 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -986,17 +986,6 @@ public final class Utils extends com.android.settingslib.Utils { return false; } - /** - * Return the resource id to represent the install status for an app - */ - @StringRes - public static int getInstallationStatus(ApplicationInfo info) { - if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - return R.string.not_installed; - } - return info.enabled ? R.string.installed : R.string.disabled; - } - private static boolean isVolumeValid(VolumeInfo volume) { return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE) && volume.isMountedReadable(); diff --git a/src/com/android/settings/spa/app/AllAppList.kt b/src/com/android/settings/spa/app/AllAppList.kt index d3572996402..8bd18844998 100644 --- a/src/com/android/settings/spa/app/AllAppList.kt +++ b/src/com/android/settings/spa/app/AllAppList.kt @@ -38,6 +38,7 @@ import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.installed import com.android.settingslib.spaprivileged.template.app.AppList import com.android.settingslib.spaprivileged.template.app.AppListInput import com.android.settingslib.spaprivileged.template.app.AppListItem @@ -75,6 +76,7 @@ fun AllAppListPage( title = stringResource(R.string.all_apps), listModel = rememberContext(::AllAppListModel), showInstantApps = true, + matchAnyUserForAdmin = true, moreOptions = { ResetAppPreferences(resetAppDialogPresenter::open) }, appList = appList, ) @@ -133,8 +135,13 @@ class AllAppListModel( return remember { derivedStateOf { storageSummary.value + - when (isDisabled(record)) { - true -> System.lineSeparator() + context.getString(R.string.disabled) + when { + !record.app.installed -> { + System.lineSeparator() + context.getString(R.string.not_installed) + } + isDisabled(record) -> { + System.lineSeparator() + context.getString(R.string.disabled) + } else -> "" } } diff --git a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt index e1792a915ba..490a98c795e 100644 --- a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt +++ b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt @@ -30,8 +30,10 @@ import com.android.settings.notification.app.AppNotificationSettings import com.android.settings.spa.notification.AppNotificationRepository import com.android.settings.spa.notification.IAppNotificationRepository import com.android.settingslib.spa.framework.compose.rememberContext +import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.app.installed import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @@ -53,6 +55,7 @@ fun AppNotificationPreference( override val summary = summaryFlow.collectAsStateWithLifecycle( initialValue = stringResource(R.string.summary_placeholder) ) + override val enabled = stateOf(app.installed) override val onClick = { navigateToAppNotificationSettings(context, app) } }) } diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt index a03fec727f4..52c8ad7b160 100644 --- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt +++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt @@ -141,7 +141,9 @@ class PackageInfoPresenter( private fun getPackageInfo() = packageManagers.getPackageInfoAsUser( packageName = packageName, - flags = PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.GET_PERMISSIONS, + flags = PackageManager.MATCH_ANY_USER or + PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.GET_PERMISSIONS, userId = userId, ) } diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt index 56c16df1dae..9fc358b5cf8 100644 --- a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt +++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.livedata.observeAsState import com.android.settings.R import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.installed import com.android.settingslib.spaprivileged.model.app.userId import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -67,11 +68,12 @@ class PictureInPictureListModel(private val context: Context) : } override fun transformItem(app: ApplicationInfo): PictureInPictureRecord { - val packageInfo = - packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) return createPictureInPictureRecord( app = app, - isSupport = packageInfo.supportsPictureInPicture(), + isSupport = app.installed && + packageManager + .getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) + .supportsPictureInPicture(), ) } diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index 7303b74772a..f0a18ec5565 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -160,30 +160,6 @@ public class UtilsTest { Utils.maybeInitializeVolume(storageManager, new Bundle()); } - @Test - public void getInstallationStatus_notInstalled_shouldReturnUninstalled() { - assertThat(Utils.getInstallationStatus(new ApplicationInfo())) - .isEqualTo(R.string.not_installed); - } - - @Test - public void getInstallationStatus_enabled_shouldReturnInstalled() { - final ApplicationInfo info = new ApplicationInfo(); - info.flags = ApplicationInfo.FLAG_INSTALLED; - info.enabled = true; - - assertThat(Utils.getInstallationStatus(info)).isEqualTo(R.string.installed); - } - - @Test - public void getInstallationStatus_disabled_shouldReturnDisabled() { - final ApplicationInfo info = new ApplicationInfo(); - info.flags = ApplicationInfo.FLAG_INSTALLED; - info.enabled = false; - - assertThat(Utils.getInstallationStatus(info)).isEqualTo(R.string.disabled); - } - @Test public void isProfileOrDeviceOwner_deviceOwnerApp_returnTrue() { when(mDevicePolicyManager.isDeviceOwnerAppOnAnyUser(PACKAGE_NAME)).thenReturn(true); diff --git a/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt index b5dfddcb5bd..2e7752e4964 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt @@ -147,6 +147,7 @@ class AllAppListTest { val listModel = AllAppListModel(context) { stateOf(SUMMARY) } val disabledApp = ApplicationInfo().apply { packageName = PACKAGE_NAME + flags = ApplicationInfo.FLAG_INSTALLED enabled = false } @@ -159,6 +160,23 @@ class AllAppListTest { assertThat(summaryState.value).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled") } + @Test + fun allAppListModel_getSummaryWhenNotInstalled() { + val listModel = AllAppListModel(context) { stateOf(SUMMARY) } + val notInstalledApp = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + + lateinit var summaryState: State + composeTestRule.setContent { + summaryState = + listModel.getSummary(option = 0, record = AppRecordWithSize(app = notInstalledApp)) + } + + assertThat(summaryState.value) + .isEqualTo("$SUMMARY${System.lineSeparator()}Not installed for this user") + } + private fun getAppListInput(): AppListInput { lateinit var input: AppListInput composeTestRule.setContent { @@ -192,6 +210,7 @@ class AllAppListTest { const val SUMMARY = "Summary" val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME + flags = ApplicationInfo.FLAG_INSTALLED } } } diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt index c54d35f3c62..37f3a119d0a 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt @@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onRoot @@ -72,7 +73,7 @@ class AppNotificationPreferenceTest { @Test fun title_displayed() { - setContent() + setContent(APP) composeTestRule.onNodeWithText(context.getString(R.string.notifications_label)) .assertIsDisplayed() @@ -80,14 +81,25 @@ class AppNotificationPreferenceTest { @Test fun summary_displayed() { - setContent() + setContent(APP) composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed() } + @Test + fun whenNotInstalled_disable() { + setContent(ApplicationInfo().apply { + packageName = PACKAGE_NAME + uid = UID + }) + + composeTestRule.onNodeWithText(context.getString(R.string.notifications_label)) + .assertIsNotEnabled() + } + @Test fun onClick_startActivity() { - setContent() + setContent(APP) composeTestRule.onRoot().performClick() composeTestRule.delay() @@ -102,10 +114,10 @@ class AppNotificationPreferenceTest { } } - private fun setContent() { + private fun setContent(app: ApplicationInfo) { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - AppNotificationPreference(app = APP, repository = repository) + AppNotificationPreference(app = app, repository = repository) } } } @@ -116,6 +128,7 @@ class AppNotificationPreferenceTest { val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME uid = UID + flags = ApplicationInfo.FLAG_INSTALLED } const val SUMMARY = "Summary" } diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt index 5e2b8c6e4cd..f90d63947d0 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt @@ -167,6 +167,7 @@ class PictureInPictureTest { const val PICTURE_IN_PICTURE_PACKAGE_NAME = "picture.in.picture.package.name" val PICTURE_IN_PICTURE_APP = ApplicationInfo().apply { packageName = PICTURE_IN_PICTURE_PACKAGE_NAME + flags = ApplicationInfo.FLAG_INSTALLED } val PICTURE_IN_PICTURE_PACKAGE_INFO = PackageInfo().apply { packageName = PICTURE_IN_PICTURE_PACKAGE_NAME From 0eeae4f8d0b22eba69a807167b191341761ad6c9 Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Mon, 10 Apr 2023 08:34:56 +0000 Subject: [PATCH 03/10] Fixes Flash Notification preview button alignment issue in RTL layout The preview button text is left-aligned in right to left language. Makes it to be right-aligned with adding layout_gravity=start on the view. Bug: 276608857 Test: checks the UI manually Change-Id: I1322c1fcc433670863603c2d0b5438f39ecef998 --- res/layout/flash_notification_preview_preference.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layout/flash_notification_preview_preference.xml b/res/layout/flash_notification_preview_preference.xml index ecc6f4f01e4..b4be0f62df1 100644 --- a/res/layout/flash_notification_preview_preference.xml +++ b/res/layout/flash_notification_preview_preference.xml @@ -34,7 +34,7 @@ android:id="@android:id/title" android:layout_height="wrap_content" android:layout_width="wrap_content" - android:layout_gravity="center_vertical" + android:layout_gravity="start|center_vertical" android:paddingVertical="@dimen/settingslib_switch_title_margin" android:ellipsize="end" android:textAppearance="?android:attr/textAppearanceListItem" From 795d2574fc2814cdbf701d002dea8f59af92bad4 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Mon, 10 Apr 2023 14:53:16 +0000 Subject: [PATCH 04/10] [Regional Pref] Fix the line break in the temperature Bug: 276023859 Test: local test Change-Id: Ib2c454cbf301295ef4baf423d2aae618742a0438 --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 5a5bc27b3ec..074245b8214 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -410,9 +410,9 @@ Use app default - Celsius(\u00B0C) + Celsius (\u00B0C) - Fahrenheit(\u00B0F) + Fahrenheit (\u00B0F) Sunday From 20cb8478d47156f2546169d72ddf4729bf889a3f Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Wed, 29 Mar 2023 18:24:47 +0000 Subject: [PATCH 05/10] Fix broken settings intent CTS test We originally added this data field to the SYNC_SETTINGS intent as a backup but since we have our own and this is breaking CTS. This reverts that change. Test: make & CTS Bug: 275385380 Change-Id: I0da949e4adea9c3c8dfdb61017afe6908e72147c --- AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ccf002a8e1d..657dcb1f4a7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4202,7 +4202,6 @@ - From b5237e60d89268ec3c4e6094cb305baf3760c6e7 Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Mon, 10 Apr 2023 22:53:23 +0000 Subject: [PATCH 06/10] Update Settings owners list Change-Id: If950f63c65ecc6313f7096c94c3a5a9bb48be529 --- OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS b/OWNERS index 881e2c7dedc..8938b643aeb 100644 --- a/OWNERS +++ b/OWNERS @@ -3,6 +3,7 @@ android-settings-core-eng+gerrit@google.com # People who can approve changes for submission arcwang@google.com +cantol@google.com chaohuiw@google.com chiujason@google.com cyl@google.com From b5774a8e2e66eed1a1dc5a83108ceabe1411cb2d Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Tue, 11 Apr 2023 13:23:46 +0800 Subject: [PATCH 07/10] Disable developer options when development_settings_enabled is disabled Bug: 270966670 Test: manual Change-Id: If4296c564ee64804b35a3357f40f8d1d49cd284a --- .../DevelopmentSettingsDashboardFragment.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 68fe08e35ee..acc0e7c40fc 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -16,6 +16,7 @@ package com.android.settings.development; +import static android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED; import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES; import android.app.Activity; @@ -27,12 +28,18 @@ import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.SystemProperties; import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -173,10 +180,47 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra } }; + private final Uri mDevelopEnabled = Settings.Global.getUriFor(DEVELOPMENT_SETTINGS_ENABLED); + private final ContentObserver mDeveloperSettingsObserver = new ContentObserver(new Handler( + Looper.getMainLooper())) { + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + final boolean developmentEnabledState = + DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext()); + final boolean switchState = mSwitchBar.isChecked(); + + // when developer options is enabled, but it is disabled by other privilege apps like: + // adb command, we should disable all items and finish the activity. + if (developmentEnabledState != switchState) { + if (developmentEnabledState) { + return; + } + disableDeveloperOptions(); + getActivity().runOnUiThread(() -> finishFragment()); + } + } + }; + public DevelopmentSettingsDashboardFragment() { super(UserManager.DISALLOW_DEBUGGING_FEATURES); } + @Override + public void onStart() { + super.onStart(); + final ContentResolver cr = getContext().getContentResolver(); + cr.registerContentObserver(mDevelopEnabled, false, mDeveloperSettingsObserver); + } + + @Override + public void onStop() { + super.onStop(); + final ContentResolver cr = getContext().getContentResolver(); + cr.unregisterContentObserver(mDeveloperSettingsObserver); + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); From 94d5d00a1a1ce64174a0763022c82751a3d8bb8e Mon Sep 17 00:00:00 2001 From: danielwbhuang Date: Tue, 11 Apr 2023 16:27:54 +0800 Subject: [PATCH 08/10] The dialog should persist after rotation. 1. Use DialogFragment 2. Override onSaveInstanceState 3. Refresh the UI after key remapping Demo: https://screencast.googleplex.com/cast/NjMzMzcxNjc2Mzc3MDg4MHxlMjM0M2FiMi0zOA Bug: 277148566 Test: manual Change-Id: I0dc6678bfa45a4f84f38bf810433dd1e4432ba4a --- .../ModifierKeysPickerDialogFragment.java | 83 ++++++++++--------- .../ModifierKeysPreferenceController.java | 47 +++++------ .../ModifierKeysResetDialogFragment.java | 54 +++--------- ...difierKeysRestorePreferenceController.java | 22 +---- 4 files changed, 83 insertions(+), 123 deletions(-) diff --git a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java index f1d7e7f28ec..14578afbaa7 100644 --- a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java +++ b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java @@ -18,6 +18,7 @@ package com.android.settings.inputmethod; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; @@ -52,11 +53,13 @@ import java.util.Map; public class ModifierKeysPickerDialogFragment extends DialogFragment { + static final String DEFAULT_KEY = "default_key"; + static final String SELECTION_KEY = "delection_key"; + private Preference mPreference; private String mKeyDefaultName; private String mKeyFocus; - private Context mContext; - private InputManager mIm; + private Activity mActivity; private List mRemappableKeyList = new ArrayList<>(Arrays.asList( @@ -67,36 +70,41 @@ public class ModifierKeysPickerDialogFragment extends DialogFragment { private Map mRemappableKeyMap = new HashMap<>(); - public ModifierKeysPickerDialogFragment() { - } + public ModifierKeysPickerDialogFragment() {} - public ModifierKeysPickerDialogFragment(Preference preference, InputManager inputManager) { - mPreference = preference; - mKeyDefaultName = preference.getTitle().toString(); - mKeyFocus = preference.getSummary().toString(); - mIm = inputManager; + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putString(SELECTION_KEY, mKeyFocus); + super.onSaveInstanceState(savedInstanceState); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreateDialog(savedInstanceState); - mContext = getActivity(); + + mActivity = getActivity(); + InputManager inputManager = mActivity.getSystemService(InputManager.class); + mKeyDefaultName = getArguments().getString(DEFAULT_KEY); + mKeyFocus = getArguments().getString(SELECTION_KEY); + if (savedInstanceState != null) { + mKeyFocus = savedInstanceState.getString(SELECTION_KEY); + } List modifierKeys = new ArrayList(Arrays.asList( - mContext.getString(R.string.modifier_keys_caps_lock), - mContext.getString(R.string.modifier_keys_ctrl), - mContext.getString(R.string.modifier_keys_meta), - mContext.getString(R.string.modifier_keys_alt))); + mActivity.getString(R.string.modifier_keys_caps_lock), + mActivity.getString(R.string.modifier_keys_ctrl), + mActivity.getString(R.string.modifier_keys_meta), + mActivity.getString(R.string.modifier_keys_alt))); for (int i = 0; i < modifierKeys.size(); i++) { mRemappableKeyMap.put(modifierKeys.get(i), mRemappableKeyList.get(i)); } View dialoglayout = - LayoutInflater.from(mContext).inflate(R.layout.modifier_key_picker_dialog, null); - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext); + LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_picker_dialog, null); + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mActivity); dialogBuilder.setView(dialoglayout); TextView summary = dialoglayout.findViewById(R.id.modifier_key_picker_summary); - CharSequence summaryText = mContext.getString( + CharSequence summaryText = mActivity.getString( R.string.modifier_keys_picker_summary, mKeyDefaultName); summary.setText(summaryText); @@ -119,14 +127,14 @@ public class ModifierKeysPickerDialogFragment extends DialogFragment { Spannable itemSummary; if (selectedItem.equals(mKeyDefaultName)) { itemSummary = new SpannableString( - mContext.getString(R.string.modifier_keys_default_summary)); + mActivity.getString(R.string.modifier_keys_default_summary)); itemSummary.setSpan( new ForegroundColorSpan(getColorOfTextColorSecondary()), 0, itemSummary.length(), 0); // Set keys to default. int[] keys = mRemappableKeyMap.get(mKeyDefaultName); for (int i = 0; i < keys.length; i++) { - mIm.remapModifierKey(keys[i], keys[i]); + inputManager.remapModifierKey(keys[i], keys[i]); } } else { itemSummary = new SpannableString(selectedItem); @@ -136,29 +144,29 @@ public class ModifierKeysPickerDialogFragment extends DialogFragment { int[] fromKeys = mRemappableKeyMap.get(mKeyDefaultName); int[] toKeys = mRemappableKeyMap.get(selectedItem); // CAPS_LOCK only one key, so always choose the left key for remapping. - if (isKeyCapsLock(mContext, mKeyDefaultName)) { - mIm.remapModifierKey(fromKeys[0], toKeys[0]); + if (isKeyCapsLock(mActivity, mKeyDefaultName)) { + inputManager.remapModifierKey(fromKeys[0], toKeys[0]); } // Remap KEY_LEFT and KEY_RIGHT to CAPS_LOCK. - if (!isKeyCapsLock(mContext, mKeyDefaultName) - && isKeyCapsLock(mContext, selectedItem)) { - mIm.remapModifierKey(fromKeys[0], toKeys[0]); - mIm.remapModifierKey(fromKeys[1], toKeys[0]); + if (!isKeyCapsLock(mActivity, mKeyDefaultName) + && isKeyCapsLock(mActivity, selectedItem)) { + inputManager.remapModifierKey(fromKeys[0], toKeys[0]); + inputManager.remapModifierKey(fromKeys[1], toKeys[0]); } // Auto handle left and right keys remapping. - if (!isKeyCapsLock(mContext, mKeyDefaultName) - && !isKeyCapsLock(mContext, selectedItem)) { - mIm.remapModifierKey(fromKeys[0], toKeys[0]); - mIm.remapModifierKey(fromKeys[1], toKeys[1]); + if (!isKeyCapsLock(mActivity, mKeyDefaultName) + && !isKeyCapsLock(mActivity, selectedItem)) { + inputManager.remapModifierKey(fromKeys[0], toKeys[0]); + inputManager.remapModifierKey(fromKeys[1], toKeys[1]); } } - mPreference.setSummary(itemSummary); - modifierKeyDialog.dismiss(); + dismiss(); + mActivity.recreate(); }); Button cancelButton = dialoglayout.findViewById(R.id.modifier_key_cancel_button); cancelButton.setOnClickListener(v -> { - modifierKeyDialog.dismiss(); + dismiss(); }); final Window window = modifierKeyDialog.getWindow(); @@ -207,16 +215,17 @@ public class ModifierKeysPickerDialogFragment extends DialogFragment { @Override public View getView(int i, View view, ViewGroup viewGroup) { if (view == null) { - view = LayoutInflater.from(mContext).inflate(R.layout.modifier_key_item, null); + view = LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_item, null); } TextView textView = view.findViewById(R.id.modifier_key_text); ImageView checkIcon = view.findViewById(R.id.modifier_key_check_icon); textView.setText(mList.get(i)); if (mCurrentItem == i) { + mKeyFocus = mList.get(i); textView.setTextColor(getColorOfColorAccentPrimaryVariant()); checkIcon.setImageAlpha(255); view.setBackground( - mContext.getDrawable(R.drawable.modifier_key_lisetview_background)); + mActivity.getDrawable(R.drawable.modifier_key_lisetview_background)); } else { textView.setTextColor(getColorOfTextColorPrimary()); checkIcon.setImageAlpha(0); @@ -235,15 +244,15 @@ public class ModifierKeysPickerDialogFragment extends DialogFragment { } private int getColorOfTextColorPrimary() { - return Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); + return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorPrimary); } private int getColorOfTextColorSecondary() { - return Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary); + return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorSecondary); } private int getColorOfColorAccentPrimaryVariant() { return Utils.getColorAttrDefaultColor( - mContext, com.android.internal.R.attr.materialColorPrimaryContainer); + mActivity, com.android.internal.R.attr.materialColorPrimaryContainer); } } diff --git a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java index 780a980917a..91caf2937f5 100644 --- a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java +++ b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java @@ -18,12 +18,12 @@ package com.android.settings.inputmethod; import android.content.Context; import android.hardware.input.InputManager; +import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.view.KeyEvent; -import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; @@ -41,8 +41,8 @@ import java.util.Objects; public class ModifierKeysPreferenceController extends BasePreferenceController { - private static String KEY_TAG = "modifier_keys_dialog_tag"; - private static String KEY_RESTORE_PREFERENCE = "modifier_keys_restore"; + private static final String KEY_TAG = "modifier_keys_dialog_tag"; + private static final String KEY_RESTORE_PREFERENCE = "modifier_keys_restore"; private static final String KEY_PREFERENCE_CAPS_LOCK = "modifier_keys_caps_lock"; private static final String KEY_PREFERENCE_CTRL = "modifier_keys_ctrl"; @@ -52,6 +52,7 @@ public class ModifierKeysPreferenceController extends BasePreferenceController { private Fragment mParent; private FragmentManager mFragmentManager; private final InputManager mIm; + private PreferenceScreen mScreen; private final List mRemappableKeys = new ArrayList<>( Arrays.asList( @@ -82,40 +83,39 @@ public class ModifierKeysPreferenceController extends BasePreferenceController { @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - if (mParent == null) { return; } + mScreen = screen; + refreshUi(); + } + private void refreshUi() { for (Map.Entry entry : mIm.getModifierKeyRemapping().entrySet()) { int fromKey = entry.getKey(); int toKey = entry.getValue(); int index = mRemappableKeys.indexOf(toKey); if (isCtrl(fromKey) && mRemappableKeys.contains(toKey)) { - Preference preference = screen.findPreference(KEY_PREFERENCE_CTRL); + Preference preference = mScreen.findPreference(KEY_PREFERENCE_CTRL); preference.setSummary(changeSummaryColor(mKeyNames[index])); } if (isMeta(fromKey) && mRemappableKeys.contains(toKey)) { - Preference preference = screen.findPreference(KEY_PREFERENCE_META); + Preference preference = mScreen.findPreference(KEY_PREFERENCE_META); preference.setSummary(changeSummaryColor(mKeyNames[index])); } if (isAlt(fromKey) && mRemappableKeys.contains(toKey)) { - Preference preference = screen.findPreference(KEY_PREFERENCE_ALT); + Preference preference = mScreen.findPreference(KEY_PREFERENCE_ALT); preference.setSummary(changeSummaryColor(mKeyNames[index])); } if (isCapLock(fromKey) && mRemappableKeys.contains(toKey)) { - Preference preference = screen.findPreference(KEY_PREFERENCE_CAPS_LOCK); + Preference preference = mScreen.findPreference(KEY_PREFERENCE_CAPS_LOCK); preference.setSummary(changeSummaryColor(mKeyNames[index])); } } - - // The dialog screen depends on the previous selected key's fragment. - // In the rotation scenario, we should remove the previous dialog screen first. - clearPreviousDialog(); } @Override @@ -133,19 +133,18 @@ public class ModifierKeysPreferenceController extends BasePreferenceController { } private void showModifierKeysDialog(Preference preference) { - ModifierKeysPickerDialogFragment fragment = - new ModifierKeysPickerDialogFragment(preference, mIm); - fragment.setTargetFragment(mParent, 0); - fragment.show(mFragmentManager, KEY_TAG); - } - - private void clearPreviousDialog() { mFragmentManager = mParent.getFragmentManager(); - DialogFragment preKeysDialogFragment = - (DialogFragment) mFragmentManager.findFragmentByTag(KEY_TAG); - if (preKeysDialogFragment != null) { - preKeysDialogFragment.dismiss(); - } + ModifierKeysPickerDialogFragment fragment = new ModifierKeysPickerDialogFragment(); + fragment.setTargetFragment(mParent, 0); + Bundle bundle = new Bundle(); + bundle.putString( + ModifierKeysPickerDialogFragment.DEFAULT_KEY, + preference.getTitle().toString()); + bundle.putString( + ModifierKeysPickerDialogFragment.SELECTION_KEY, + preference.getSummary().toString()); + fragment.setArguments(bundle); + fragment.show(mFragmentManager, KEY_TAG); } private Spannable changeSummaryColor(String summary) { diff --git a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java index 4ca5ddde6c8..755e9dddedf 100644 --- a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java +++ b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java @@ -18,25 +18,19 @@ package com.android.settings.inputmethod; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.content.Context; import android.hardware.input.InputManager; import android.os.Bundle; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.widget.Button; import androidx.fragment.app.DialogFragment; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settingslib.Utils; public class ModifierKeysResetDialogFragment extends DialogFragment { private static final String MODIFIER_KEYS_CAPS_LOCK = "modifier_keys_caps_lock"; @@ -44,41 +38,36 @@ public class ModifierKeysResetDialogFragment extends DialogFragment { private static final String MODIFIER_KEYS_META = "modifier_keys_meta"; private static final String MODIFIER_KEYS_ALT = "modifier_keys_alt"; - private PreferenceScreen mScreen; - private InputManager mIm; private String[] mKeys = { MODIFIER_KEYS_CAPS_LOCK, MODIFIER_KEYS_CTRL, MODIFIER_KEYS_META, MODIFIER_KEYS_ALT}; - public ModifierKeysResetDialogFragment() { - } - - public ModifierKeysResetDialogFragment(PreferenceScreen screen, InputManager inputManager) { - mScreen = screen; - mIm = inputManager; - } + public ModifierKeysResetDialogFragment() {} @Override public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreateDialog(savedInstanceState); - Context mContext = getActivity(); + + Activity activity = getActivity(); + InputManager inputManager = activity.getSystemService(InputManager.class); View dialoglayout = - LayoutInflater.from(mContext).inflate(R.layout.modifier_key_reset_dialog, null); - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext); + LayoutInflater.from(activity).inflate(R.layout.modifier_key_reset_dialog, null); + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity); dialogBuilder.setView(dialoglayout); AlertDialog modifierKeyResetDialog = dialogBuilder.create(); Button restoreButton = dialoglayout.findViewById(R.id.modifier_key_reset_restore_button); restoreButton.setOnClickListener(v -> { - resetToDefault(); - modifierKeyResetDialog.dismiss(); + inputManager.clearAllModifierKeyRemappings(); + dismiss(); + activity.recreate(); }); Button cancelButton = dialoglayout.findViewById(R.id.modifier_key_reset_cancel_button); cancelButton.setOnClickListener(v -> { - modifierKeyResetDialog.dismiss(); + dismiss(); }); final Window window = modifierKeyResetDialog.getWindow(); @@ -86,25 +75,4 @@ public class ModifierKeysResetDialogFragment extends DialogFragment { return modifierKeyResetDialog; } - - private void resetToDefault() { - Context mContext = getActivity(); - for (int i = 0; i < mKeys.length; i++) { - Preference preference = mScreen.findPreference(mKeys[i]); - Spannable title = new SpannableString( - mContext.getString(R.string.modifier_keys_default_summary)); - title.setSpan( - new ForegroundColorSpan(getColorOfTextColorSecondary()), - 0, title.length(), 0); - preference.setSummary(title); - } - - if (mIm != null) { - mIm.clearAllModifierKeyRemappings(); - } - } - - private int getColorOfTextColorSecondary() { - return Utils.getColorAttrDefaultColor(getActivity(), android.R.attr.textColorSecondary); - } } diff --git a/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java index 4e72fbdff83..3171487382e 100644 --- a/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java +++ b/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java @@ -17,12 +17,10 @@ package com.android.settings.inputmethod; import android.content.Context; -import android.hardware.input.InputManager; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; -import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; @@ -34,16 +32,14 @@ import com.android.settingslib.Utils; public class ModifierKeysRestorePreferenceController extends BasePreferenceController { - private static String KEY_TAG = "modifier_keys_dialog_tag"; + private static final String KEY_TAG = "modifier_keys_restore_dialog_tag"; private Fragment mParent; private FragmentManager mFragmentManager; private PreferenceScreen mScreen; - private final InputManager mIm; public ModifierKeysRestorePreferenceController(Context context, String key) { super(context, key); - mIm = context.getSystemService(InputManager.class); } public void setFragment(Fragment parent) { @@ -57,9 +53,6 @@ public class ModifierKeysRestorePreferenceController extends BasePreferenceContr return; } mScreen = screen; - // The dialog screen depends on the previous selected key's fragment. - // In the rotation scenario, we should remove the previous dialog first. - clearPreviousDialog(); setResetKeyColor(); } @@ -78,8 +71,8 @@ public class ModifierKeysRestorePreferenceController extends BasePreferenceContr } private void showResetDialog() { - ModifierKeysResetDialogFragment fragment = - new ModifierKeysResetDialogFragment(mScreen, mIm); + mFragmentManager = mParent.getFragmentManager(); + ModifierKeysResetDialogFragment fragment = new ModifierKeysResetDialogFragment(); fragment.setTargetFragment(mParent, 0); fragment.show(mFragmentManager, KEY_TAG); } @@ -98,13 +91,4 @@ public class ModifierKeysRestorePreferenceController extends BasePreferenceContr return Utils.getColorAttrDefaultColor( mParent.getActivity(), com.android.internal.R.attr.materialColorPrimaryContainer); } - - private void clearPreviousDialog() { - mFragmentManager = mParent.getFragmentManager(); - DialogFragment preResetDialogFragment = - (DialogFragment) mFragmentManager.findFragmentByTag(KEY_TAG); - if (preResetDialogFragment != null) { - preResetDialogFragment.dismiss(); - } - } } From 5eecd8698273b39e10b0054a9c3228186be92538 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Mon, 10 Apr 2023 09:12:33 +0000 Subject: [PATCH 09/10] [Regional Pref] Add footer in Regional preference page Bug: 277436632 Test: local test Change-Id: I596f440696830374225d9b56fb8c4f0d5d5d5d61 --- res/values/strings.xml | 6 ++ res/xml/regional_preference_main_page.xml | 7 ++ .../LocaleHelperPreferenceController.java | 16 ++-- .../RegionalFooterPreferenceController.java | 74 +++++++++++++++++++ ...egionalFooterPreferenceControllerTest.java | 62 ++++++++++++++++ 5 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 src/com/android/settings/regionalpreferences/RegionalFooterPreferenceController.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 844d8cea6fb..a67c713bfdc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -427,6 +427,12 @@ Friday Saturday + + If an app doesn’t support regional preferences, the app will use its default locale settings. + + Learn more about language preferences. + + https://support.google.com/android {count, plural, diff --git a/res/xml/regional_preference_main_page.xml b/res/xml/regional_preference_main_page.xml index de9d3d8e00d..27a1d83818c 100644 --- a/res/xml/regional_preference_main_page.xml +++ b/res/xml/regional_preference_main_page.xml @@ -66,4 +66,11 @@ android:value="arg_value_language_select" /> + + diff --git a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java index 05c740139cc..a639c9d297a 100644 --- a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java +++ b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java @@ -17,6 +17,8 @@ package com.android.settings.localepicker; import android.content.Context; +import android.content.Intent; +import android.util.Log; import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceScreen; @@ -65,10 +67,14 @@ public class LocaleHelperPreferenceController extends AbstractPreferenceControll } private void openLocaleLearnMoreLink() { - mContext.startActivity( - HelpUtils.getHelpIntent( - mContext, - mContext.getString(R.string.link_locale_picker_footer_learn_more), - /*backupContext=*/"")); + Intent intent = HelpUtils.getHelpIntent( + mContext, + mContext.getString(R.string.link_locale_picker_footer_learn_more), + mContext.getClass().getName()); + if (intent != null) { + mContext.startActivity(intent); + } else { + Log.w(TAG, "HelpIntent is null"); + } } } diff --git a/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceController.java b/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceController.java new file mode 100644 index 00000000000..6ba9d187032 --- /dev/null +++ b/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceController.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2023 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.regionalpreferences; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.HelpUtils; +import com.android.settingslib.widget.FooterPreference; + +/** + * Preference controller for regional preference footer. + */ +public class RegionalFooterPreferenceController extends BasePreferenceController { + + private static final String TAG = "RegionalFooterPreferenceController"; + + public RegionalFooterPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + FooterPreference footerPreference = screen.findPreference(getPreferenceKey()); + setupFooterPreference(footerPreference); + } + + @VisibleForTesting + void setupFooterPreference(FooterPreference footerPreference) { + if (footerPreference != null) { + footerPreference.setLearnMoreAction(v -> openLocaleLearnMoreLink()); + footerPreference.setLearnMoreText(mContext.getString( + R.string.desc_regional_pref_footer_learn_more)); + } + } + + private void openLocaleLearnMoreLink() { + Intent intent = HelpUtils.getHelpIntent( + mContext, + mContext.getString(R.string.regional_pref_footer_learn_more_link), + mContext.getClass().getName()); + if (intent != null) { + mContext.startActivity(intent); + } else { + Log.w(TAG, "HelpIntent is null"); + } + } +} diff --git a/tests/unit/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceControllerTest.java new file mode 100644 index 00000000000..f0197931f04 --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/RegionalFooterPreferenceControllerTest.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2023 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.regionalpreferences; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Looper; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settingslib.widget.FooterPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class RegionalFooterPreferenceControllerTest { + + private static String KEY_FOOTER_PREFERENCE = "regional_pref_footer"; + private Context mContext; + private RegionalFooterPreferenceController mRegionalFooterPreferenceController; + + @Mock + private FooterPreference mMockFooterPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = ApplicationProvider.getApplicationContext(); + mRegionalFooterPreferenceController = new RegionalFooterPreferenceController(mContext, + KEY_FOOTER_PREFERENCE); + } + + @Test + public void setupFooterPreference_shouldSetAsTextInLearnMore() { + mRegionalFooterPreferenceController.setupFooterPreference(mMockFooterPreference); + verify(mMockFooterPreference).setLearnMoreText(anyString()); + } +} From 948eb573557bfbae1a19480822979ac34260c4c2 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Tue, 11 Apr 2023 10:53:08 +0000 Subject: [PATCH 10/10] [Panlingual] set direction and textAlignment for the locale dialog Bug: 276867490 Test: local test Change-Id: I96b7d30bb9b7cba509275f1afef90ab8fc1c248b --- res/layout/locale_dialog.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/res/layout/locale_dialog.xml b/res/layout/locale_dialog.xml index cbdb37eb23e..480c0e293a9 100644 --- a/res/layout/locale_dialog.xml +++ b/res/layout/locale_dialog.xml @@ -21,7 +21,9 @@ android:paddingStart="@dimen/admin_details_dialog_padding" android:paddingEnd="@dimen/admin_details_dialog_padding" android:paddingBottom="@dimen/admin_details_dialog_padding_bottom" - android:orientation="vertical"> + android:orientation="vertical" + android:layoutDirection="locale" + android:textDirection="locale"> + android:textAppearance="@style/TextAppearance.AdminDialogTitle" + android:textAlignment="textStart"/> + android:textColor="?android:attr/textColorSecondary" + android:textAlignment="textStart"/>