From 4b367aba781782f6ad00f228bfcab1d1817f12c0 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Wed, 10 Jan 2018 15:42:56 -0800 Subject: [PATCH 1/4] DO NOT MERGE Add developer menu to select max number of connected audio devices The new developer option modifies the system property "persist.bluetooth.maxconnectedaudiodevices"; it allows testing multiple connected devices for A2DP, AVRCP, HFP. Bug: 71603731 Test: Robolectric test Change-Id: If8c2c26ad4c9a2b7458a2d16a0c29d5f9b4df5c7 --- res/values/arrays.xml | 18 +++++++++++++ res/values/strings.xml | 5 ++++ res/xml/development_prefs.xml | 7 +++++ .../development/DevelopmentSettings.java | 26 +++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 5e1a468f87b..3984c214bf7 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -316,6 +316,24 @@ Never time out + + + 1 (Default) + 2 + 3 + 4 + 5 + + + + + 1 + 2 + 3 + 4 + 5 + + diff --git a/res/values/strings.xml b/res/values/strings.xml index a05fbfcddaf..8a685e6a3f0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1606,6 +1606,11 @@ Don’t play custom phone ringtones on Bluetooth headsets + + Maximum number of connected Bluetooth audio devices + + Select maximum number of connected Bluetooth audio devices + Cast diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml index bf83a5e90d8..fb73ec41d46 100644 --- a/res/xml/development_prefs.xml +++ b/res/xml/development_prefs.xml @@ -270,6 +270,13 @@ android:entries="@array/bluetooth_a2dp_codec_ldac_playback_quality_titles" android:entryValues="@array/bluetooth_a2dp_codec_ldac_playback_quality_values" /> + + = 0) { + String[] titles = getResources().getStringArray(R.array.bluetooth_max_connected_audio_devices); + mBluetoothSelectMaxConnectedAudioDevices.setSummary(titles[index]); + } + } + private void writeBluetoothConfigurationOption(Preference preference, Object newValue) { String[] summaries; @@ -2621,6 +2644,9 @@ public class DevelopmentSettings extends RestrictedSettingsFragment (preference == mBluetoothSelectA2dpLdacPlaybackQuality)) { writeBluetoothConfigurationOption(preference, newValue); return true; + } else if (preference == mBluetoothSelectMaxConnectedAudioDevices) { + writeBluetoothMaxConnectedAudioDevices(newValue); + return true; } else if (preference == mLogdSize) { writeLogdSizeOption(newValue); return true; From 7a0ad2b30f72f8f4b423491efe4f5afa2660f0ac Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Fri, 12 Jan 2018 13:00:24 -0800 Subject: [PATCH 2/4] Initial screens for Storage Access. This new settings will be accessed through Apps -> Special Apps and it will let users change the status of granted (or denied) Scoped Access Permissions. This initial CL implements the landing activity that shows all packages and an initial version of the detailed activity for a specific page, althouth the detailed activity is not ready yet. Test: manual verification Test: export ROBOTEST_FILTER=com.android.settings.core.codeinspection.CodeInspectionTest && make RunSettingsRoboTests -j40 Bug: 63720392 Change-Id: Id0419c2c4aaaec365803218d6947e230e4776f13 --- res/values/strings.xml | 5 + res/xml/special_access.xml | 12 ++ src/com/android/settings/Settings.java | 1 + .../AppStateStorageAccessBridge.java | 88 ++++++++++++++ .../applications/StorageAccessDetails.java | 110 ++++++++++++++++++ .../manageapplications/AppFilterRegistry.java | 14 ++- .../ManageApplications.java | 17 +++ .../core/gateway/SettingsGateway.java | 4 +- .../grandfather_not_implementing_indexable | 3 +- 9 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 src/com/android/settings/applications/AppStateStorageAccessBridge.java create mode 100644 src/com/android/settings/applications/StorageAccessDetails.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 8c7b6f4789f..0ec72e598c9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9120,4 +9120,9 @@ Track all GNSS constellations and frequencies with no duty cycling + + Storage access + + storage access scoped directory + diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml index 829bc53c6c1..9980eb60bc6 100644 --- a/res/xml/special_access.xml +++ b/res/xml/special_access.xml @@ -111,4 +111,16 @@ android:value="com.android.settings.Settings$VrListenersSettingsActivity" /> + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 505977d9686..0269bb2de4b 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -120,6 +120,7 @@ public class Settings extends SettingsActivity { public static class PhotosStorageActivity extends SettingsActivity { /* empty */ } + public static class StorageAccessSettingsActivity extends SettingsActivity { /* empty */ } public static class TopLevelSettings extends SettingsActivity { /* empty */ } public static class ApnSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/applications/AppStateStorageAccessBridge.java b/src/com/android/settings/applications/AppStateStorageAccessBridge.java new file mode 100644 index 00000000000..4839fd6baab --- /dev/null +++ b/src/com/android/settings/applications/AppStateStorageAccessBridge.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 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 static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COLUMNS; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COL_PACKAGE; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.ArraySet; +import android.util.Log; + +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.ApplicationsState.AppFilter; + +import java.util.Set; + +// TODO(b/63720392): add unit tests +public class AppStateStorageAccessBridge extends AppStateBaseBridge { + + private static final String TAG = "StorageAccessBridge"; + + public AppStateStorageAccessBridge(ApplicationsState appState, Callback callback) { + super(appState, callback); + } + + @Override + protected void loadAllExtraInfo() { } + + @Override + protected void updateExtraInfo(AppEntry app, String pkg, int uid) { } + + public static final AppFilter FILTER_APP_HAS_STORAGE_ACCESS = new AppFilter() { + + private Set mPackages; + + @Override + public void init() { + throw new UnsupportedOperationException("Need to call constructor that takes context"); + } + + @Override + public void init(Context context) { + try (Cursor cursor = context.getContentResolver().query( + new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY).appendPath(TABLE_PACKAGES).appendPath("*") + .build(), TABLE_PACKAGES_COLUMNS, null, null)) { + if (cursor == null) { + Log.w(TAG, "didn't get cursor"); + return; + } + final int count = cursor.getCount(); + if (count == 0) { + Log.d(TAG, "no packages"); + return; + } + mPackages = new ArraySet<>(count); + while (cursor.moveToNext()) { + mPackages.add(cursor.getString(TABLE_PACKAGES_COL_PACKAGE)); + } + Log.v(TAG, "init(): " + mPackages); + } + } + + @Override + public boolean filterApp(AppEntry info) { + return mPackages != null && mPackages.contains(info.info.packageName); + } + }; +} diff --git a/src/com/android/settings/applications/StorageAccessDetails.java b/src/com/android/settings/applications/StorageAccessDetails.java new file mode 100644 index 00000000000..41729e664c5 --- /dev/null +++ b/src/com/android/settings/applications/StorageAccessDetails.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2018 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 static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COLUMNS; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_DIRECTORY; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_GRANTED; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_PACKAGE; +import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID; + +import android.app.AlertDialog; +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; +import android.support.v7.preference.Preference.OnPreferenceClickListener; +import android.util.Log; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; + +/** + * Detailed settings for an app's storage access permissions (A.K.A Scoped Directory Access). + */ +// TODO(b/63720392): explain its layout +// TODO(b/63720392): add unit tests +public class StorageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener, + OnPreferenceClickListener { + private static final String MY_TAG = "StorageAccessDetails"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (true) { + // TODO(b/63720392): temporarily hack so the screen doesn't crash.. + addPreferencesFromResource(R.xml.app_ops_permissions_details); + // ... we need to dynamically create the preferences by calling the provider instead: + try (Cursor cursor = getContentResolver().query( + new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY).appendPath(TABLE_PERMISSIONS).appendPath("*") + .build(), + TABLE_PERMISSIONS_COLUMNS, null, null)) { + if (cursor == null) { + Log.w(TAG, "didn't get cursor"); + return; + } + final int count = cursor.getCount(); + if (count == 0) { + Log.d(TAG, "no permissions"); + return; + } + while (cursor.moveToNext()) { + final String pkg = cursor.getString(TABLE_PERMISSIONS_COL_PACKAGE); + final String uuid = cursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID); + final String dir = cursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY); + final boolean granted = cursor.getInt(TABLE_PERMISSIONS_COL_GRANTED) == 1; + Log.v(MY_TAG, "pkg:" + pkg + " uuid: " + uuid + " dir: " + dir + "> " + + granted); + } + } + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + // TODO(b/63720392): implement or remove listener + return false; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + // TODO(b/63720392): implement or remove listener + return false; + } + + @Override + protected boolean refreshUi() { + // TODO(b/63720392): implement + return true; + } + + @Override + protected AlertDialog createDialog(int id, int errorCode) { + return null; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.APPLICATIONS_USAGE_ACCESS_DETAIL; + } +} diff --git a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java index 01d2cb83f5d..82b3d86a70e 100644 --- a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java +++ b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java @@ -23,6 +23,7 @@ import com.android.settings.applications.AppStateInstallAppsBridge; import com.android.settings.applications.AppStateNotificationBridge; import com.android.settings.applications.AppStateOverlayBridge; import com.android.settings.applications.AppStatePowerBridge; +import com.android.settings.applications.AppStateStorageAccessBridge; import com.android.settings.applications.AppStateUsageBridge; import com.android.settings.applications.AppStateWriteSettingsBridge; import com.android.settingslib.applications.ApplicationsState; @@ -65,14 +66,15 @@ public class AppFilterRegistry { public static final int FILTER_APPS_WITH_OVERLAY = 10; public static final int FILTER_APPS_WRITE_SETTINGS = 11; public static final int FILTER_APPS_INSTALL_SOURCES = 12; - // Next id: 13 + public static final int FILTER_APP_HAS_STORAGE_ACCESS = 13; + // Next id: 14 private static AppFilterRegistry sRegistry; private final AppFilterItem[] mFilters; private AppFilterRegistry() { - mFilters = new AppFilterItem[13]; + mFilters = new AppFilterItem[14]; // High power whitelist, on mFilters[FILTER_APPS_POWER_WHITELIST] = new AppFilterItem( @@ -155,6 +157,12 @@ public class AppFilterRegistry { AppStateInstallAppsBridge.FILTER_APP_SOURCES, FILTER_APPS_INSTALL_SOURCES, R.string.filter_install_sources_apps); + + // Apps that interacted with storage access permissions (A.K.A. Scoped Directory Access) + mFilters[FILTER_APP_HAS_STORAGE_ACCESS] = new AppFilterItem( + AppStateStorageAccessBridge.FILTER_APP_HAS_STORAGE_ACCESS, + FILTER_APP_HAS_STORAGE_ACCESS, + R.string.filter_install_sources_apps); } public static AppFilterRegistry getInstance() { @@ -177,6 +185,8 @@ public class AppFilterRegistry { return FILTER_APPS_WRITE_SETTINGS; case ManageApplications.LIST_TYPE_MANAGE_SOURCES: return FILTER_APPS_INSTALL_SOURCES; + case ManageApplications.LIST_TYPE_STORAGE_ACCESS: + return FILTER_APP_HAS_STORAGE_ACCESS; default: return FILTER_APPS_ALL; } diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index d19c4722b9f..8957c90f0de 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -84,6 +84,7 @@ import com.android.settings.applications.AppStateInstallAppsBridge; import com.android.settings.applications.AppStateNotificationBridge; import com.android.settings.applications.AppStateOverlayBridge; import com.android.settings.applications.AppStatePowerBridge; +import com.android.settings.applications.AppStateStorageAccessBridge; import com.android.settings.applications.AppStateUsageBridge; import com.android.settings.applications.AppStateUsageBridge.UsageState; import com.android.settings.applications.AppStateWriteSettingsBridge; @@ -92,6 +93,7 @@ import com.android.settings.applications.DefaultAppSettings; import com.android.settings.applications.InstalledAppCounter; import com.android.settings.applications.InstalledAppDetails; import com.android.settings.applications.NotificationApps; +import com.android.settings.applications.StorageAccessDetails; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.appinfo.AppInfoDashboardFragment; import com.android.settings.applications.appinfo.DrawOverlayDetails; @@ -204,6 +206,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment public static final int LIST_TYPE_GAMES = 9; public static final int LIST_TYPE_MOVIES = 10; public static final int LIST_TYPE_PHOTOGRAPHY = 11; + public static final int LIST_TYPE_STORAGE_ACCESS = 12; // List types that should show instant apps. public static final Set LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList( @@ -279,6 +282,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment mListType = LIST_TYPE_PHOTOGRAPHY; mSortOrder = R.id.sort_order_size; mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT); + } else if (className.equals(Settings.StorageAccessSettingsActivity.class.getName())) { + mListType = LIST_TYPE_STORAGE_ACCESS; + screenTitle = R.string.storage_access; } else { mListType = LIST_TYPE_MAIN; } @@ -443,6 +449,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS; case LIST_TYPE_MANAGE_SOURCES: return MetricsEvent.MANAGE_EXTERNAL_SOURCES; + case LIST_TYPE_STORAGE_ACCESS: + return MetricsEvent.STORAGE_ACCESS; default: return MetricsEvent.VIEW_UNKNOWN; } @@ -537,6 +545,10 @@ public class ManageApplications extends InstrumentedPreferenceFragment case LIST_TYPE_PHOTOGRAPHY: startAppInfoFragment(AppStorageSettings.class, R.string.storage_photos_videos); break; + case LIST_TYPE_STORAGE_ACCESS: + startAppInfoFragment(StorageAccessDetails.class, R.string.storage_access); + break; + // TODO: Figure out if there is a way where we can spin up the profile's settings // process ahead of time, to avoid a long load of data when user clicks on a managed // app. Maybe when they load the list of apps that contains managed profile apps. @@ -840,6 +852,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this); } else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) { mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this); + } else if (mManageApplications.mListType == LIST_TYPE_STORAGE_ACCESS) { + mExtraInfoBridge = new AppStateStorageAccessBridge(mState, this); } else { mExtraInfoBridge = null; } @@ -1241,6 +1255,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment case LIST_TYPE_MANAGE_SOURCES: holder.setSummary(ExternalSourcesDetails.getPreferenceSummary(mContext, entry)); break; + case LIST_TYPE_STORAGE_ACCESS: + holder.setSummary(null); + break; default: holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize); break; diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 664dda85282..e973ef15cbd 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -45,6 +45,7 @@ import com.android.settings.applications.ManageDomainUrls; import com.android.settings.applications.NotificationApps; import com.android.settings.applications.ProcessStatsSummary; import com.android.settings.applications.ProcessStatsUi; +import com.android.settings.applications.StorageAccessDetails; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.VrListenerSettings; import com.android.settings.applications.appinfo.AppInfoDashboardFragment; @@ -252,7 +253,8 @@ public class SettingsGateway { WebViewAppPicker.class.getName(), LockscreenDashboardFragment.class.getName(), BluetoothDeviceDetailsFragment.class.getName(), - DataUsageList.class.getName() + DataUsageList.class.getName(), + StorageAccessDetails.class.getName() }; public static final String[] SETTINGS_FOR_RESTRICTED = { diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index a82c9efc996..eb06125b3b8 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -72,4 +72,5 @@ com.android.settings.IccLockSettings com.android.settings.TetherSettings com.android.settings.ApnEditor com.android.settings.UserCredentialsSettings -com.android.settings.TestingSettings \ No newline at end of file +com.android.settings.TestingSettings +com.android.settings.applications.StorageAccessDetails \ No newline at end of file From 4d8f51531f7f01e2d08898906a393cb606b86353 Mon Sep 17 00:00:00 2001 From: Wu Qiang Date: Fri, 7 Jul 2017 18:18:16 +0800 Subject: [PATCH 3/4] Fix plus position in about phone Settings has phone number in about phone, "+" is shown with incorrect position in Bidi layout. Then it needs to use BidiFormatter Fixes: 70700094 Test: manual Change-Id: I1d63f773926136c38ce1ac811869476dc6a89eac --- .../settings/deviceinfo/PhoneNumberPreferenceController.java | 4 +++- .../deviceinfo/simstatus/SimStatusDialogController.java | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java index 802d774d7b6..93f75bfb327 100644 --- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java @@ -23,6 +23,8 @@ import android.support.v7.preference.PreferenceScreen; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; import android.text.TextUtils; import com.android.settings.R; @@ -120,7 +122,7 @@ public class PhoneNumberPreferenceController extends AbstractPreferenceControlle final String phoneNumber = DeviceInfoUtils.getFormattedPhoneNumber(mContext, subscriptionInfo); return TextUtils.isEmpty(phoneNumber) ? mContext.getString(R.string.device_info_default) - : phoneNumber; + : BidiFormatter.getInstance().unicodeWrap(phoneNumber, TextDirectionHeuristics.LTR); } @VisibleForTesting diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java index 35b8bd1576a..91440f7d871 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java +++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java @@ -39,6 +39,8 @@ import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.util.Log; @@ -195,7 +197,8 @@ public class SimStatusDialogController implements LifecycleObserver, OnResume, O private void updatePhoneNumber() { // If formattedNumber is null or empty, it'll display as "Unknown". - mDialog.setText(PHONE_NUMBER_VALUE_ID, getPhoneNumber()); + mDialog.setText(PHONE_NUMBER_VALUE_ID, BidiFormatter.getInstance().unicodeWrap( + getPhoneNumber(), TextDirectionHeuristics.LTR)); } private void updateDataState(int state) { From e5e8f0368fb57d278c4d1988096d7ea8d5155348 Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Wed, 10 Jan 2018 15:55:40 -0800 Subject: [PATCH 4/4] Add ability to show/hide airplane toggle. This introduces a new boolean flag: config_show_toggle_airplane Which when set to false, will hide the airplane mode toggle in network & internet. Bug: 69813881 Test: make RunSettingsRoboTests ROBOTEST_FILTER=AirplaneModePreferenceControllerTest Change-Id: I2cf682bf78231040eba5573fbcb55a65be2a13df --- res/values/bools.xml | 3 ++ .../AirplaneModePreferenceController.java | 4 ++- tests/robotests/res/values-mcc999/config.xml | 1 + .../AirplaneModePreferenceControllerTest.java | 36 +++++++++++++------ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/res/values/bools.xml b/res/values/bools.xml index e733d5f2c2c..4469cc40521 100644 --- a/res/values/bools.xml +++ b/res/values/bools.xml @@ -54,6 +54,9 @@ surface in search results or not.--> true + + true + true diff --git a/src/com/android/settings/network/AirplaneModePreferenceController.java b/src/com/android/settings/network/AirplaneModePreferenceController.java index d86f7f7980f..17cf211e9dd 100644 --- a/src/com/android/settings/network/AirplaneModePreferenceController.java +++ b/src/com/android/settings/network/AirplaneModePreferenceController.java @@ -30,6 +30,7 @@ import com.android.settings.AirplaneModeEnabler; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.R; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; @@ -91,7 +92,8 @@ public class AirplaneModePreferenceController extends AbstractPreferenceControll } public static boolean isAvailable(Context context) { - return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + return context.getResources().getBoolean(R.bool.config_show_toggle_airplane) + && !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } @Override diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index c3ae9117d35..639e1a2a329 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -21,6 +21,7 @@ false false false + false false false false diff --git a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java index b1072b16ef7..320ae9ee800 100644 --- a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java @@ -16,7 +16,7 @@ package com.android.settings.network; -import static junit.framework.Assert.assertFalse; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -38,24 +38,20 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class AirplaneModePreferenceControllerTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Context mContext; - - @Mock - private Resources mResources; - @Mock private PreferenceScreen mScreen; @Mock private PackageManager mPackageManager; + private Context mContext; private AirplaneModePreferenceController mController; private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; @@ -65,7 +61,7 @@ public class AirplaneModePreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mFactory = FakeFeatureFactory.setupForTest(); - doReturn(mResources).when(mContext).getResources(); + mContext = spy(RuntimeEnvironment.application); doReturn(mPackageManager).when(mContext).getPackageManager(); mController = spy(new AirplaneModePreferenceController(mContext, null)); mLifecycleOwner = () -> mLifecycle; @@ -74,9 +70,9 @@ public class AirplaneModePreferenceControllerTest { } @Test - public void airplaneModePreference_shouldNotBeAvailable_ifHasLeanbackFeature() { - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(true); - assertFalse(mController.isAvailable()); + @Config(qualifiers = "mcc999") + public void airplaneModePreference_shouldNotBeAvailable_ifSetToNotVisible() { + assertThat(mController.isAvailable()).isFalse(); mController.displayPreference(mScreen); @@ -84,4 +80,22 @@ public class AirplaneModePreferenceControllerTest { mController.onResume(); mController.onPause(); } + + @Test + public void airplaneModePreference_shouldNotBeAvailable_ifHasLeanbackFeature() { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(true); + assertThat(mController.isAvailable()).isFalse(); + + mController.displayPreference(mScreen); + + // This should not crash + mController.onResume(); + mController.onPause(); + } + + @Test + public void airplaneModePreference_shouldBeAvailable_ifNoLeanbackFeature() { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(false); + assertThat(mController.isAvailable()).isTrue(); + } }