From cf663511db245a9f590ad49564496bcae9b38f0a Mon Sep 17 00:00:00 2001 From: Alexey Kuzmin Date: Tue, 24 Apr 2018 20:26:02 +0100 Subject: [PATCH 01/12] Add vibration preview Test: Open vibration intensity settings, tap on any option. The preview effect of selected intensity should be played back. Bug: 74504389 Merged-In: Ie22ecb830752edb7a7515a3a6475587e72274e51 Change-Id: Ie22ecb830752edb7a7515a3a6475587e72274e51 --- ...tificationVibrationPreferenceFragment.java | 7 +++++++ .../TouchVibrationPreferenceFragment.java | 7 +++++++ .../VibrationPreferenceFragment.java | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/com/android/settings/accessibility/NotificationVibrationPreferenceFragment.java b/src/com/android/settings/accessibility/NotificationVibrationPreferenceFragment.java index 6340bb1417e..5f43c2d346f 100644 --- a/src/com/android/settings/accessibility/NotificationVibrationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/NotificationVibrationPreferenceFragment.java @@ -15,7 +15,9 @@ */ package com.android.settings.accessibility; +import android.media.AudioAttributes; import android.os.Vibrator; +import android.os.VibrationEffect; import android.provider.Settings; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -43,6 +45,11 @@ public class NotificationVibrationPreferenceFragment extends VibrationPreference return Settings.System.NOTIFICATION_VIBRATION_INTENSITY; } + @Override + protected int getPreviewVibrationAudioAttributesUsage() { + return AudioAttributes.USAGE_NOTIFICATION; + } + @Override protected int getDefaultVibrationIntensity() { Vibrator vibrator = getContext().getSystemService(Vibrator.class); diff --git a/src/com/android/settings/accessibility/TouchVibrationPreferenceFragment.java b/src/com/android/settings/accessibility/TouchVibrationPreferenceFragment.java index ea36833db54..1d2012452a6 100644 --- a/src/com/android/settings/accessibility/TouchVibrationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/TouchVibrationPreferenceFragment.java @@ -16,7 +16,9 @@ package com.android.settings.accessibility; import android.graphics.drawable.Drawable; +import android.media.AudioAttributes; import android.os.Vibrator; +import android.os.VibrationEffect; import android.provider.Settings; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -50,6 +52,11 @@ public class TouchVibrationPreferenceFragment extends VibrationPreferenceFragmen return vibrator.getDefaultHapticFeedbackIntensity(); } + @Override + protected int getPreviewVibrationAudioAttributesUsage() { + return AudioAttributes.USAGE_ASSISTANCE_SONIFICATION; + } + @Override public void onVibrationIntensitySelected(int intensity) { // We want to keep HAPTIC_FEEDBACK_ENABLED consistent with this setting since some diff --git a/src/com/android/settings/accessibility/VibrationPreferenceFragment.java b/src/com/android/settings/accessibility/VibrationPreferenceFragment.java index e911b612e37..93fd7f5c8da 100644 --- a/src/com/android/settings/accessibility/VibrationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/VibrationPreferenceFragment.java @@ -21,8 +21,10 @@ import android.support.annotation.VisibleForTesting; import android.content.Context; import android.database.ContentObserver; import android.graphics.drawable.Drawable; +import android.media.AudioAttributes; import android.net.Uri; import android.os.Handler; +import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; import android.util.ArrayMap; @@ -105,6 +107,24 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm */ protected void onVibrationIntensitySelected(int intensity) { } + /** + * Play a vibration effect with intensity just selected by user + */ + protected void playVibrationPreview() { + Vibrator vibrator = getContext().getSystemService(Vibrator.class); + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + builder.setUsage(getPreviewVibrationAudioAttributesUsage()); + vibrator.vibrate(effect, builder.build()); + } + + /** + * Get the AudioAttributes usage for vibration preview. + */ + protected int getPreviewVibrationAudioAttributesUsage() { + return AudioAttributes.USAGE_UNKNOWN; + } + @Override protected List getCandidates() { List candidates = new ArrayList<>(mCandidates.values()); @@ -189,6 +209,7 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm @Override public void onChange(boolean selfChange, Uri uri) { updateCandidates(); + playVibrationPreview(); } } } From 0cb62643d631326e1fe04b079e172ff32a171164 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Wed, 23 May 2018 14:55:11 -0700 Subject: [PATCH 02/12] Add remaining whitelisted controllers Because I missed them in the long whitelist the first time... Bug: 79779103 Test: robotests Change-Id: I01c8c80fe306667c1d3ac007b16fad546c5a5f40 --- res/xml/smart_battery_detail.xml | 1 + ...MagnificationGesturesPreferenceController.java | 8 ++++++++ .../MagnificationNavbarPreferenceController.java | 8 ++++++++ .../SmartBatteryPreferenceController.java | 7 +++++++ .../AlarmVolumePreferenceController.java | 6 ++++++ ...ificationGesturesPreferenceControllerTest.java | 15 +++++++++++++++ ...gnificationNavbarPreferenceControllerTest.java | 15 +++++++++++++++ .../SmartBatteryPreferenceControllerTest.java | 7 +++++++ .../AlarmVolumePreferenceControllerTest.java | 5 +++++ 9 files changed, 72 insertions(+) diff --git a/res/xml/smart_battery_detail.xml b/res/xml/smart_battery_detail.xml index e1246edd980..8af3e96d759 100644 --- a/res/xml/smart_battery_detail.xml +++ b/res/xml/smart_battery_detail.xml @@ -30,6 +30,7 @@ android:key="smart_battery" android:title="@string/smart_battery_title" android:summary="@string/smart_battery_summary" + settings:controller="com.android.settings.fuelgauge.SmartBatteryPreferenceController" settings:allowDividerAbove="true"/> Date: Wed, 23 May 2018 14:55:11 -0700 Subject: [PATCH 03/12] Add remaining whitelisted controllers Because I missed them in the long whitelist the first time... Change-Id: I9fbd7b33e06b3f2f6e5e5778f78abfdb1a52006a Merged-In: I01c8c80fe306667c1d3ac007b16fad546c5a5f40 Fixes: 79779103 Test: robotests --- res/xml/smart_battery_detail.xml | 1 + ...MagnificationGesturesPreferenceController.java | 7 +++++++ .../MagnificationNavbarPreferenceController.java | 7 +++++++ .../SmartBatteryPreferenceController.java | 6 ++++++ .../AlarmVolumePreferenceController.java | 6 ++++++ ...ificationGesturesPreferenceControllerTest.java | 15 +++++++++++++++ ...gnificationNavbarPreferenceControllerTest.java | 15 +++++++++++++++ .../SmartBatteryPreferenceControllerTest.java | 7 +++++++ .../AlarmVolumePreferenceControllerTest.java | 5 +++++ 9 files changed, 69 insertions(+) diff --git a/res/xml/smart_battery_detail.xml b/res/xml/smart_battery_detail.xml index e1246edd980..8af3e96d759 100644 --- a/res/xml/smart_battery_detail.xml +++ b/res/xml/smart_battery_detail.xml @@ -30,6 +30,7 @@ android:key="smart_battery" android:title="@string/smart_battery_title" android:summary="@string/smart_battery_summary" + settings:controller="com.android.settings.fuelgauge.SmartBatteryPreferenceController" settings:allowDividerAbove="true"/> Date: Thu, 24 May 2018 09:45:24 -0700 Subject: [PATCH 04/12] Update storage header background to colorPrimary Change-Id: I07f78cc6452846829b92d25fe9c41c6efe40a459 Merged-In: If13d5cbd2bd7db1c6d9c9f840281601ab78ba267 Fixes: 79773433 Test: visual --- res/layout/storage_summary_donut.xml | 2 +- .../android/settings/deviceinfo/StorageDashboardFragment.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/res/layout/storage_summary_donut.xml b/res/layout/storage_summary_donut.xml index ce7e272a85e..b183b2e9917 100644 --- a/res/layout/storage_summary_donut.xml +++ b/res/layout/storage_summary_donut.xml @@ -17,7 +17,7 @@ diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index 8e56b12ec8f..e8f9771898a 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -57,14 +57,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class StorageDashboardFragment extends DashboardFragment - implements +public class StorageDashboardFragment extends DashboardFragment implements LoaderManager.LoaderCallbacks> { private static final String TAG = "StorageDashboardFrag"; private static final int STORAGE_JOB_ID = 0; private static final int ICON_JOB_ID = 1; private static final int VOLUME_SIZE_JOB_ID = 2; - private static final int OPTIONS_MENU_MIGRATE_DATA = 100; private VolumeInfo mVolume; private PrivateStorageInfo mStorageInfo; From 06600041bca8157a81c7899c9f3159759fa07a9c Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Wed, 23 May 2018 13:03:27 -0700 Subject: [PATCH 05/12] Make summary text update after settings changes The uri's being pinged when changes happened were incorrect, because of a hard coded '/'. Bug: 79779837 Test: robotest Change-Id: I6735c5a60dc7df6894bd17e67d7702a7ec6c07d4 --- .../slices/SliceBroadcastReceiver.java | 13 +++++- .../slices/SliceBroadcastReceiverTest.java | 44 ++++++++++++++++--- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index 213bf00c028..d81734a974d 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -27,6 +27,7 @@ import static com.android.settings.wifi.WifiSliceBuilder.ACTION_WIFI_SLICE_CHANG import android.app.slice.Slice; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -155,6 +156,7 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { sliderController.setSliderPosition(newPosition); logSliceValueChange(context, key, newPosition); + updateUri(context, key, isPlatformSlice); } /** @@ -177,8 +179,15 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { } private void updateUri(Context context, String key, boolean isPlatformDefined) { - final String path = SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key; - final Uri uri = SliceBuilderUtils.getUri(path, isPlatformDefined); + final String authority = isPlatformDefined + ? SettingsSlicesContract.AUTHORITY + : SettingsSliceProvider.SLICE_AUTHORITY; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); context.getContentResolver().notifyChange(uri, null /* observer */); } } diff --git a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java index 5f0bc962476..6d18449e5f8 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java @@ -96,8 +96,16 @@ public class SliceBroadcastReceiverTest { @Test public void onReceive_toggleChanged() { final String key = "key"; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); insertSpecialCase(key); + final ContentResolver resolver = mock(ContentResolver.class); + doReturn(resolver).when(mContext).getContentResolver(); // Turn on toggle setting FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); fakeToggleController.setChecked(true); @@ -121,6 +129,7 @@ public class SliceBroadcastReceiverTest { assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(namePair.second).isEqualTo(fakeToggleController.getPreferenceKey()); + verify(resolver).notifyChange(uri, null); assertThat(valuePair.first) .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); assertThat(valuePair.second).isEqualTo(0); @@ -150,9 +159,13 @@ public class SliceBroadcastReceiverTest { assertThat(fakeToggleController.isChecked()).isFalse(); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); - verify(resolver).notifyChange(eq(expectedUri), eq(null)); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); + verify(resolver).notifyChange(expectedUri, null); } @Test @@ -183,6 +196,14 @@ public class SliceBroadcastReceiverTest { @Test public void onReceive_sliderChanged() { final String key = "key"; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); + final ContentResolver resolver = mock(ContentResolver.class); + doReturn(resolver).when(mContext).getContentResolver(); final int position = FakeSliderController.MAX_STEPS - 1; final int oldPosition = FakeSliderController.MAX_STEPS; mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); @@ -213,6 +234,7 @@ public class SliceBroadcastReceiverTest { assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(namePair.second).isEqualTo(key); + verify(resolver).notifyChange(uri, null); assertThat(valuePair.first) .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); assertThat(valuePair.second).isEqualTo(position); @@ -286,8 +308,12 @@ public class SliceBroadcastReceiverTest { // Check the value is the same and the Uri has been notified. assertThat(fakeToggleController.isChecked()).isTrue(); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); verify(resolver).notifyChange(eq(expectedUri), eq(null)); } @@ -323,8 +349,12 @@ public class SliceBroadcastReceiverTest { // Check position is the same and the Uri has been notified. assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); verify(resolver).notifyChange(eq(expectedUri), eq(null)); } From a2ea9911010e14fe60bef262291a46425ed71cea Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Wed, 23 May 2018 13:03:27 -0700 Subject: [PATCH 06/12] Make summary text update after settings changes The uri's being pinged when changes happened were incorrect, because of a hard coded '/'. Change-Id: I4d04642bc80b97c5191bf0e1e7a5e15a2c58d6c9 Merged-In: I6735c5a60dc7df6894bd17e67d7702a7ec6c07d4 Fixes: 79779837 Test: robotest --- .../slices/SliceBroadcastReceiver.java | 13 +++++-- .../slices/SliceBroadcastReceiverTest.java | 34 ++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index 80e3e3c1ef1..1e413938ec9 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -27,6 +27,7 @@ import static com.android.settings.wifi.WifiSliceBuilder.ACTION_WIFI_SLICE_CHANG import android.app.slice.Slice; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -151,6 +152,7 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { sliderController.setSliderPosition(newPosition); logSliceValueChange(context, key, newPosition); + updateUri(context, key, isPlatformSlice); } /** @@ -173,8 +175,15 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { } private void updateUri(Context context, String key, boolean isPlatformDefined) { - final String path = SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key; - final Uri uri = SliceBuilderUtils.getUri(path, isPlatformDefined); + final String authority = isPlatformDefined + ? SettingsSlicesContract.AUTHORITY + : SettingsSliceProvider.SLICE_AUTHORITY; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); context.getContentResolver().notifyChange(uri, null /* observer */); } } diff --git a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java index f03f88ed99a..21a02062855 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java @@ -95,8 +95,16 @@ public class SliceBroadcastReceiverTest { @Test public void onReceive_toggleChanged() { final String key = "key"; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); insertSpecialCase(key); + final ContentResolver resolver = mock(ContentResolver.class); + doReturn(resolver).when(mContext).getContentResolver(); // Turn on toggle setting FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); fakeToggleController.setChecked(true); @@ -120,6 +128,7 @@ public class SliceBroadcastReceiverTest { assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(namePair.second).isEqualTo(fakeToggleController.getPreferenceKey()); + verify(resolver).notifyChange(uri, null); assertThat(valuePair.first) .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); assertThat(valuePair.second).isEqualTo(0); @@ -128,6 +137,14 @@ public class SliceBroadcastReceiverTest { @Test public void onReceive_sliderChanged() { final String key = "key"; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); + final ContentResolver resolver = mock(ContentResolver.class); + doReturn(resolver).when(mContext).getContentResolver(); final int position = FakeSliderController.MAX_STEPS - 1; final int oldPosition = FakeSliderController.MAX_STEPS; mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); @@ -158,6 +175,7 @@ public class SliceBroadcastReceiverTest { assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(namePair.second).isEqualTo(key); + verify(resolver).notifyChange(uri, null); assertThat(valuePair.first) .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); assertThat(valuePair.second).isEqualTo(position); @@ -231,8 +249,12 @@ public class SliceBroadcastReceiverTest { // Check the value is the same and the Uri has been notified. assertThat(fakeToggleController.isChecked()).isTrue(); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); verify(resolver).notifyChange(eq(expectedUri), eq(null)); } @@ -268,8 +290,12 @@ public class SliceBroadcastReceiverTest { // Check position is the same and the Uri has been notified. assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); verify(resolver).notifyChange(eq(expectedUri), eq(null)); } From 2313b2421596b01a36bd2db0a171bbd050f1ddbc Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 24 May 2018 10:10:02 -0700 Subject: [PATCH 07/12] 2nd attempt to fix Slice strict mode. 1. Use real BluetoothAdapter instead of settingslib version. The settingslib version contains calls that violates strictmode rules. 2. Override StrictMode rules in SettingsSliceProvider when it's called in background thread. When in background, the enforcement from Slice framework (StrictMode#ThreadPolicy) is not useful and can be safely ignored. Change-Id: I68523148f4c1dc88a54e207447d21ec439478cdf Merged-In: I68523148f4c1dc88a54e207447d21ec439478cdf Fixes: 79985175 Test: robotests --- .../bluetooth/BluetoothSliceBuilder.java | 15 ++-- .../slices/SettingsSliceProvider.java | 84 +++++++++++-------- .../slices/SettingsSliceProviderTest.java | 51 +++++++++-- .../testutils/shadow/ShadowThreadUtils.java | 18 ++++ 4 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java index da759a33e5e..9a9f45588e7 100644 --- a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java +++ b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java @@ -17,8 +17,6 @@ package com.android.settings.bluetooth; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; -import static androidx.slice.builders.ListBuilder.ICON_IMAGE; - import android.annotation.ColorInt; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -28,6 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.provider.SettingsSlicesContract; +import android.support.v4.graphics.drawable.IconCompat; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; @@ -42,8 +41,6 @@ import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; import androidx.slice.builders.SliceAction; -import android.support.v4.graphics.drawable.IconCompat; - public class BluetoothSliceBuilder { private static final String TAG = "BluetoothSliceBuilder"; @@ -71,7 +68,8 @@ public class BluetoothSliceBuilder { INTENT_FILTER.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); } - private BluetoothSliceBuilder() {} + private BluetoothSliceBuilder() { + } /** * Return a Bluetooth Slice bound to {@link #BLUETOOTH_URI}. @@ -80,7 +78,7 @@ public class BluetoothSliceBuilder { * Bluetooth. */ public static Slice getSlice(Context context) { - final boolean isBluetoothEnabled = isBluetoothEnabled(context); + final boolean isBluetoothEnabled = isBluetoothEnabled(); final CharSequence title = context.getText(R.string.bluetooth_settings); final IconCompat icon = IconCompat.createWithResource(context, R.drawable.ic_settings_bluetooth); @@ -115,9 +113,8 @@ public class BluetoothSliceBuilder { // handle it. } - private static boolean isBluetoothEnabled(Context context) { - final LocalBluetoothAdapter adapter = LocalBluetoothManager.getInstance(context, - null /* callback */).getBluetoothAdapter(); + private static boolean isBluetoothEnabled() { + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); return adapter.isEnabled(); } diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 47736dfac2e..841247b1d64 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -23,23 +23,23 @@ import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; +import android.os.StrictMode; import android.provider.Settings; import android.provider.SettingsSlicesContract; import android.support.annotation.VisibleForTesting; -import android.support.v4.graphics.drawable.IconCompat; import android.text.TextUtils; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; import android.util.Pair; -import com.android.settings.location.LocationSliceBuilder; -import com.android.settings.overlay.FeatureFactory; +import com.android.settings.bluetooth.BluetoothSliceBuilder; import com.android.settings.core.BasePreferenceController; +import com.android.settings.location.LocationSliceBuilder; +import com.android.settings.notification.ZenModeSliceBuilder; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.wifi.calling.WifiCallingSliceHelper; -import com.android.settings.bluetooth.BluetoothSliceBuilder; -import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settingslib.SliceBroadcastRelay; import com.android.settingslib.utils.ThreadUtils; @@ -150,7 +150,7 @@ public class SettingsSliceProvider extends SliceProvider { @Override public void onSlicePinned(Uri sliceUri) { if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { - registerIntentToUri(WifiSliceBuilder.INTENT_FILTER , sliceUri); + registerIntentToUri(WifiSliceBuilder.INTENT_FILTER, sliceUri); return; } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { registerIntentToUri(ZenModeSliceBuilder.INTENT_FILTER, sliceUri); @@ -176,41 +176,51 @@ public class SettingsSliceProvider extends SliceProvider { @Override public Slice onBindSlice(Uri sliceUri) { - final Set blockedKeys = getBlockedKeys(); - final String key = sliceUri.getLastPathSegment(); - if (blockedKeys.contains(key)) { - Log.e(TAG, "Requested blocked slice with Uri: " + sliceUri); - return null; - } + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (!ThreadUtils.isMainThread()) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .permitAll() + .build()); + } + final Set blockedKeys = getBlockedKeys(); + final String key = sliceUri.getLastPathSegment(); + if (blockedKeys.contains(key)) { + Log.e(TAG, "Requested blocked slice with Uri: " + sliceUri); + return null; + } - // If adding a new Slice, do not directly match Slice URIs. - // Use {@link SlicesDatabaseAccessor}. - if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { - return FeatureFactory.getFactory(getContext()) - .getSlicesFeatureProvider() - .getNewWifiCallingSliceHelper(getContext()) - .createWifiCallingSlice(sliceUri); - } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { - return WifiSliceBuilder.getSlice(getContext()); - } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { - return ZenModeSliceBuilder.getSlice(getContext()); - } else if (BluetoothSliceBuilder.BLUETOOTH_URI.equals(sliceUri)) { - return BluetoothSliceBuilder.getSlice(getContext()); - } else if (LocationSliceBuilder.LOCATION_URI.equals(sliceUri)) { - return LocationSliceBuilder.getSlice(getContext()); - } + // If adding a new Slice, do not directly match Slice URIs. + // Use {@link SlicesDatabaseAccessor}. + if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { + return FeatureFactory.getFactory(getContext()) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(getContext()) + .createWifiCallingSlice(sliceUri); + } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { + return WifiSliceBuilder.getSlice(getContext()); + } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { + return ZenModeSliceBuilder.getSlice(getContext()); + } else if (BluetoothSliceBuilder.BLUETOOTH_URI.equals(sliceUri)) { + return BluetoothSliceBuilder.getSlice(getContext()); + } else if (LocationSliceBuilder.LOCATION_URI.equals(sliceUri)) { + return LocationSliceBuilder.getSlice(getContext()); + } - SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); - if (cachedSliceData == null) { - loadSliceInBackground(sliceUri); - return getSliceStub(sliceUri); - } + SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); + if (cachedSliceData == null) { + loadSliceInBackground(sliceUri); + return getSliceStub(sliceUri); + } - // Remove the SliceData from the cache after it has been used to prevent a memory-leak. - if (!mSliceDataCache.containsKey(sliceUri)) { - mSliceWeakDataCache.remove(sliceUri); + // Remove the SliceData from the cache after it has been used to prevent a memory-leak. + if (!mSliceDataCache.containsKey(sliceUri)) { + mSliceWeakDataCache.remove(sliceUri); + } + return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } - return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData); } /** diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index df960d8a912..21bef6131c9 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -18,9 +18,7 @@ package com.android.settings.slices; import static android.content.ContentResolver.SCHEME_CONTENT; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -38,19 +36,24 @@ import android.os.StrictMode; import android.provider.SettingsSlicesContract; import android.util.ArraySet; -import com.android.settings.location.LocationSliceBuilder; -import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.bluetooth.BluetoothSliceBuilder; +import com.android.settings.location.LocationSliceBuilder; import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.FakeToggleController; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowThreadUtils; +import com.android.settings.wifi.WifiSliceBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import java.util.Arrays; import java.util.Collection; @@ -66,6 +69,7 @@ import androidx.slice.Slice; * TODO Investigate using ShadowContentResolver.registerProviderInternal(String, ContentProvider) */ @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowThreadUtils.class) public class SettingsSliceProviderTest { private static final String KEY = "KEY"; @@ -98,6 +102,7 @@ public class SettingsSliceProviderTest { public void setUp() { mContext = spy(RuntimeEnvironment.application); mProvider = spy(new SettingsSliceProvider()); + ShadowStrictMode.reset(); mProvider.mSliceWeakDataCache = new HashMap<>(); mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); @@ -112,6 +117,7 @@ public class SettingsSliceProviderTest { @After public void cleanUp() { + ShadowThreadUtils.reset(); DatabaseTestUtils.clearDb(mContext); } @@ -184,7 +190,8 @@ public class SettingsSliceProviderTest { } @Test - public void onBindSlice_shouldNotOverrideStrictMode() { + public void onBindSlice_mainThread_shouldNotOverrideStrictMode() { + ShadowThreadUtils.setIsMainThread(true); final StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy(); SliceData data = getDummyData(); mProvider.mSliceWeakDataCache.put(data.getUri(), data); @@ -195,6 +202,18 @@ public class SettingsSliceProviderTest { assertThat(newThreadPolicy.toString()).isEqualTo(oldThreadPolicy.toString()); } + @Test + @Config(shadows = ShadowStrictMode.class) + public void onBindSlice_backgroundThread_shouldOverrideStrictMode() { + ShadowThreadUtils.setIsMainThread(false); + + SliceData data = getDummyData(); + mProvider.mSliceWeakDataCache.put(data.getUri(), data); + mProvider.onBindSlice(data.getUri()); + + assertThat(ShadowStrictMode.isThreadPolicyOverridden()).isTrue(); + } + @Test public void onBindSlice_requestsBlockedSlice_retunsNull() { final String blockedKey = "blocked_key"; @@ -456,7 +475,7 @@ public class SettingsSliceProviderTest { mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values); } - private SliceData getDummyData() { + private static SliceData getDummyData() { return new SliceData.Builder() .setKey(KEY) .setTitle(TITLE) @@ -468,4 +487,24 @@ public class SettingsSliceProviderTest { .setPreferenceControllerClassName(PREF_CONTROLLER) .build(); } + + @Implements(value = StrictMode.class, inheritImplementationMethods = true) + public static class ShadowStrictMode { + + private static int sSetThreadPolicyCount; + + @Resetter + public static void reset() { + sSetThreadPolicyCount = 0; + } + + @Implementation + public static void setThreadPolicy(final StrictMode.ThreadPolicy policy) { + sSetThreadPolicyCount++; + } + + public static boolean isThreadPolicyOverridden() { + return sSetThreadPolicyCount != 0; + } + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java index 6b0411e9ab0..9513098e862 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java @@ -20,10 +20,18 @@ import com.android.settingslib.utils.ThreadUtils; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; @Implements(ThreadUtils.class) public class ShadowThreadUtils { + private static boolean sIsMainThread = true; + + @Resetter + public static void reset() { + sIsMainThread = true; + } + @Implementation public static void postOnBackgroundThread(Runnable runnable) { runnable.run(); @@ -33,4 +41,14 @@ public class ShadowThreadUtils { public static void postOnMainThread(Runnable runnable) { runnable.run(); } + + @Implementation + public static boolean isMainThread() { + return sIsMainThread; + } + + public static void setIsMainThread(boolean isMainThread) { + sIsMainThread = isMainThread; + } + } From 934de2262291167b943257692fc9fd111cb181c0 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 24 May 2018 10:10:02 -0700 Subject: [PATCH 08/12] 2nd attempt to fix Slice strict mode. 1. Use real BluetoothAdapter instead of settingslib version. The settingslib version contains calls that violates strictmode rules. 2. Override StrictMode rules in SettingsSliceProvider when it's called in background thread. When in background, the enforcement from Slice framework (StrictMode#ThreadPolicy) is not useful and can be safely ignored. Change-Id: I68523148f4c1dc88a54e207447d21ec439478cdf Bug: 79985175 Test: robotests --- .../bluetooth/BluetoothSliceBuilder.java | 9 +- .../slices/SettingsSliceProvider.java | 88 +++++++++++-------- .../slices/SettingsSliceProviderTest.java | 53 +++++++++-- .../testutils/shadow/ShadowThreadUtils.java | 18 ++++ 4 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java index 5ec193c372a..cdc54a6ad58 100644 --- a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java +++ b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java @@ -17,8 +17,6 @@ package com.android.settings.bluetooth; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; -import static androidx.slice.builders.ListBuilder.ICON_IMAGE; - import android.annotation.ColorInt; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -83,7 +81,7 @@ public class BluetoothSliceBuilder { * Bluetooth. */ public static Slice getSlice(Context context) { - final boolean isBluetoothEnabled = isBluetoothEnabled(context); + final boolean isBluetoothEnabled = isBluetoothEnabled(); final CharSequence title = context.getText(R.string.bluetooth_settings); final IconCompat icon = IconCompat.createWithResource(context, R.drawable.ic_settings_bluetooth); @@ -119,9 +117,8 @@ public class BluetoothSliceBuilder { // handle it. } - private static boolean isBluetoothEnabled(Context context) { - final LocalBluetoothAdapter adapter = LocalBluetoothManager.getInstance(context, - null /* callback */).getBluetoothAdapter(); + private static boolean isBluetoothEnabled() { + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); return adapter.isEnabled(); } diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 52888b0e698..f4dfd6ee71d 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -23,6 +23,7 @@ import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; +import android.os.StrictMode; import android.provider.Settings; import android.provider.SettingsSlicesContract; import android.text.TextUtils; @@ -31,13 +32,13 @@ import android.util.KeyValueListParser; import android.util.Log; import android.util.Pair; -import com.android.settings.location.LocationSliceBuilder; -import com.android.settings.overlay.FeatureFactory; +import com.android.settings.bluetooth.BluetoothSliceBuilder; import com.android.settings.core.BasePreferenceController; +import com.android.settings.location.LocationSliceBuilder; +import com.android.settings.notification.ZenModeSliceBuilder; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.wifi.calling.WifiCallingSliceHelper; -import com.android.settings.bluetooth.BluetoothSliceBuilder; -import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settingslib.SliceBroadcastRelay; import com.android.settingslib.utils.ThreadUtils; @@ -53,7 +54,6 @@ import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import androidx.annotation.VisibleForTesting; -import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.SliceProvider; @@ -150,7 +150,7 @@ public class SettingsSliceProvider extends SliceProvider { @Override public void onSlicePinned(Uri sliceUri) { if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { - registerIntentToUri(WifiSliceBuilder.INTENT_FILTER , sliceUri); + registerIntentToUri(WifiSliceBuilder.INTENT_FILTER, sliceUri); mRegisteredUris.add(sliceUri); return; } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { @@ -177,41 +177,51 @@ public class SettingsSliceProvider extends SliceProvider { @Override public Slice onBindSlice(Uri sliceUri) { - final Set blockedKeys = getBlockedKeys(); - final String key = sliceUri.getLastPathSegment(); - if (blockedKeys.contains(key)) { - Log.e(TAG, "Requested blocked slice with Uri: " + sliceUri); - return null; - } + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (!ThreadUtils.isMainThread()) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .permitAll() + .build()); + } + final Set blockedKeys = getBlockedKeys(); + final String key = sliceUri.getLastPathSegment(); + if (blockedKeys.contains(key)) { + Log.e(TAG, "Requested blocked slice with Uri: " + sliceUri); + return null; + } - // If adding a new Slice, do not directly match Slice URIs. - // Use {@link SlicesDatabaseAccessor}. - if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { - return FeatureFactory.getFactory(getContext()) - .getSlicesFeatureProvider() - .getNewWifiCallingSliceHelper(getContext()) - .createWifiCallingSlice(sliceUri); - } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { - return WifiSliceBuilder.getSlice(getContext()); - } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { - return ZenModeSliceBuilder.getSlice(getContext()); - } else if (BluetoothSliceBuilder.BLUETOOTH_URI.equals(sliceUri)) { - return BluetoothSliceBuilder.getSlice(getContext()); - } else if (LocationSliceBuilder.LOCATION_URI.equals(sliceUri)) { - return LocationSliceBuilder.getSlice(getContext()); - } + // If adding a new Slice, do not directly match Slice URIs. + // Use {@link SlicesDatabaseAccessor}. + if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { + return FeatureFactory.getFactory(getContext()) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(getContext()) + .createWifiCallingSlice(sliceUri); + } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { + return WifiSliceBuilder.getSlice(getContext()); + } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { + return ZenModeSliceBuilder.getSlice(getContext()); + } else if (BluetoothSliceBuilder.BLUETOOTH_URI.equals(sliceUri)) { + return BluetoothSliceBuilder.getSlice(getContext()); + } else if (LocationSliceBuilder.LOCATION_URI.equals(sliceUri)) { + return LocationSliceBuilder.getSlice(getContext()); + } - SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); - if (cachedSliceData == null) { - loadSliceInBackground(sliceUri); - return getSliceStub(sliceUri); - } + SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); + if (cachedSliceData == null) { + loadSliceInBackground(sliceUri); + return getSliceStub(sliceUri); + } - // Remove the SliceData from the cache after it has been used to prevent a memory-leak. - if (!mSliceDataCache.containsKey(sliceUri)) { - mSliceWeakDataCache.remove(sliceUri); + // Remove the SliceData from the cache after it has been used to prevent a memory-leak. + if (!mSliceDataCache.containsKey(sliceUri)) { + mSliceWeakDataCache.remove(sliceUri); + } + return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } - return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData); } /** @@ -302,14 +312,14 @@ public class SettingsSliceProvider extends SliceProvider { final SliceData sliceData; try { - sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); + sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); } catch (IllegalStateException e) { Log.d(TAG, "Could not create slicedata for uri: " + uri); return; } final BasePreferenceController controller = SliceBuilderUtils.getPreferenceController( - getContext(), sliceData); + getContext(), sliceData); final IntentFilter filter = controller.getIntentFilter(); if (filter != null) { diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index df960d8a912..6b1262a1e2c 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -18,9 +18,7 @@ package com.android.settings.slices; import static android.content.ContentResolver.SCHEME_CONTENT; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -38,19 +36,24 @@ import android.os.StrictMode; import android.provider.SettingsSlicesContract; import android.util.ArraySet; -import com.android.settings.location.LocationSliceBuilder; -import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.bluetooth.BluetoothSliceBuilder; +import com.android.settings.location.LocationSliceBuilder; import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.FakeToggleController; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowThreadUtils; +import com.android.settings.wifi.WifiSliceBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import java.util.Arrays; import java.util.Collection; @@ -66,6 +69,7 @@ import androidx.slice.Slice; * TODO Investigate using ShadowContentResolver.registerProviderInternal(String, ContentProvider) */ @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowThreadUtils.class) public class SettingsSliceProviderTest { private static final String KEY = "KEY"; @@ -98,6 +102,7 @@ public class SettingsSliceProviderTest { public void setUp() { mContext = spy(RuntimeEnvironment.application); mProvider = spy(new SettingsSliceProvider()); + ShadowStrictMode.reset(); mProvider.mSliceWeakDataCache = new HashMap<>(); mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); @@ -112,6 +117,7 @@ public class SettingsSliceProviderTest { @After public void cleanUp() { + ShadowThreadUtils.reset(); DatabaseTestUtils.clearDb(mContext); } @@ -184,7 +190,8 @@ public class SettingsSliceProviderTest { } @Test - public void onBindSlice_shouldNotOverrideStrictMode() { + public void onBindSlice_mainThread_shouldNotOverrideStrictMode() { + ShadowThreadUtils.setIsMainThread(true); final StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy(); SliceData data = getDummyData(); mProvider.mSliceWeakDataCache.put(data.getUri(), data); @@ -196,7 +203,19 @@ public class SettingsSliceProviderTest { } @Test - public void onBindSlice_requestsBlockedSlice_retunsNull() { + @Config(shadows = ShadowStrictMode.class) + public void onBindSlice_backgroundThread_shouldOverrideStrictMode() { + ShadowThreadUtils.setIsMainThread(false); + + SliceData data = getDummyData(); + mProvider.mSliceWeakDataCache.put(data.getUri(), data); + mProvider.onBindSlice(data.getUri()); + + assertThat(ShadowStrictMode.isThreadPolicyOverridden()).isTrue(); + } + + @Test + public void onBindSlice_requestsBlockedSlice_returnsNull() { final String blockedKey = "blocked_key"; final Set blockedSet = new ArraySet<>(); blockedSet.add(blockedKey); @@ -456,7 +475,7 @@ public class SettingsSliceProviderTest { mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values); } - private SliceData getDummyData() { + private static SliceData getDummyData() { return new SliceData.Builder() .setKey(KEY) .setTitle(TITLE) @@ -468,4 +487,24 @@ public class SettingsSliceProviderTest { .setPreferenceControllerClassName(PREF_CONTROLLER) .build(); } + + @Implements(value = StrictMode.class, inheritImplementationMethods = true) + public static class ShadowStrictMode { + + private static int sSetThreadPolicyCount; + + @Resetter + public static void reset() { + sSetThreadPolicyCount = 0; + } + + @Implementation + public static void setThreadPolicy(final StrictMode.ThreadPolicy policy) { + sSetThreadPolicyCount++; + } + + public static boolean isThreadPolicyOverridden() { + return sSetThreadPolicyCount != 0; + } + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java index 6b0411e9ab0..9513098e862 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java @@ -20,10 +20,18 @@ import com.android.settingslib.utils.ThreadUtils; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; @Implements(ThreadUtils.class) public class ShadowThreadUtils { + private static boolean sIsMainThread = true; + + @Resetter + public static void reset() { + sIsMainThread = true; + } + @Implementation public static void postOnBackgroundThread(Runnable runnable) { runnable.run(); @@ -33,4 +41,14 @@ public class ShadowThreadUtils { public static void postOnMainThread(Runnable runnable) { runnable.run(); } + + @Implementation + public static boolean isMainThread() { + return sIsMainThread; + } + + public static void setIsMainThread(boolean isMainThread) { + sIsMainThread = isMainThread; + } + } From feae5d79cff4a3454c277d6d1d7ba4196cd51995 Mon Sep 17 00:00:00 2001 From: Ching-Sung Li Date: Fri, 18 May 2018 11:56:49 +0800 Subject: [PATCH 09/12] Remove OverlayManagerWrapper class out from Settings Remove OverlayManagerWrapper and use IOverlayManager instead. Based on comment from reviewer to refactor ThemePreferenceController. Bug: 76167422 Test: RunSettingsRoboTests Change-Id: I0bddf50d8ea7721fe258a9a9b51f8c56dd5afcce --- ...lateDisplayCutoutPreferenceController.java | 46 +++++--- .../display/ThemePreferenceController.java | 75 +++++++------ .../wrapper/OverlayManagerWrapper.java | 102 ------------------ ...DisplayCutoutPreferenceControllerTest.java | 28 +++-- .../ThemePreferenceControllerTest.java | 69 ++++++++++-- .../ThemePreferenceControllerTest.java | 43 +++----- 6 files changed, 167 insertions(+), 196 deletions(-) delete mode 100644 src/com/android/settings/wrapper/OverlayManagerWrapper.java diff --git a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java index 3d551cdefaf..46b493fe046 100644 --- a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java +++ b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java @@ -19,20 +19,23 @@ package com.android.settings.development; import static android.os.UserHandle.USER_SYSTEM; import android.content.Context; +import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.TextUtils; +import android.view.DisplayCutout; import androidx.annotation.VisibleForTesting; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import android.text.TextUtils; -import android.view.DisplayCutout; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.wrapper.OverlayManagerWrapper; -import com.android.settings.wrapper.OverlayManagerWrapper.OverlayInfo; import com.android.settingslib.development.DeveloperOptionsPreferenceController; +import java.util.ArrayList; import java.util.List; public class EmulateDisplayCutoutPreferenceController extends @@ -41,7 +44,7 @@ public class EmulateDisplayCutoutPreferenceController extends private static final String KEY = "display_cutout_emulation"; - private final OverlayManagerWrapper mOverlayManager; + private final IOverlayManager mOverlayManager; private final boolean mAvailable; private ListPreference mPreference; @@ -49,7 +52,7 @@ public class EmulateDisplayCutoutPreferenceController extends @VisibleForTesting EmulateDisplayCutoutPreferenceController(Context context, PackageManager packageManager, - OverlayManagerWrapper overlayManager) { + IOverlayManager overlayManager) { super(context); mOverlayManager = overlayManager; mPackageManager = packageManager; @@ -57,7 +60,8 @@ public class EmulateDisplayCutoutPreferenceController extends } public EmulateDisplayCutoutPreferenceController(Context context) { - this(context, context.getPackageManager(), new OverlayManagerWrapper()); + this(context, context.getPackageManager(), IOverlayManager.Stub + .asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE))); } @Override @@ -102,10 +106,14 @@ public class EmulateDisplayCutoutPreferenceController extends } final boolean result; - if (TextUtils.isEmpty(packageName)) { - result = mOverlayManager.setEnabled(currentPackageName, false, USER_SYSTEM); - } else { - result = mOverlayManager.setEnabledExclusiveInCategory(packageName, USER_SYSTEM); + try { + if (TextUtils.isEmpty(packageName)) { + result = mOverlayManager.setEnabled(currentPackageName, false, USER_SYSTEM); + } else { + result = mOverlayManager.setEnabledExclusiveInCategory(packageName, USER_SYSTEM); + } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } updateState(mPreference); return result; @@ -145,13 +153,17 @@ public class EmulateDisplayCutoutPreferenceController extends } private OverlayInfo[] getOverlayInfos() { - @SuppressWarnings("unchecked") List overlayInfos = - mOverlayManager.getOverlayInfosForTarget("android", USER_SYSTEM); - for (int i = overlayInfos.size() - 1; i >= 0; i--) { - if (!DisplayCutout.EMULATION_OVERLAY_CATEGORY.equals( - overlayInfos.get(i).category)) { - overlayInfos.remove(i); + List overlayInfos; + try { + overlayInfos = mOverlayManager.getOverlayInfosForTarget("android", USER_SYSTEM); + for (int i = overlayInfos.size() - 1; i >= 0; i--) { + if (!DisplayCutout.EMULATION_OVERLAY_CATEGORY.equals( + overlayInfos.get(i).category)) { + overlayInfos.remove(i); + } } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } return overlayInfos.toArray(new OverlayInfo[overlayInfos.size()]); } diff --git a/src/com/android/settings/display/ThemePreferenceController.java b/src/com/android/settings/display/ThemePreferenceController.java index 2cdf7e21c30..c71a7abf498 100644 --- a/src/com/android/settings/display/ThemePreferenceController.java +++ b/src/com/android/settings/display/ThemePreferenceController.java @@ -13,23 +13,22 @@ */ package com.android.settings.display; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_THEME; + import android.content.Context; +import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import androidx.annotation.VisibleForTesting; -import androidx.preference.ListPreference; -import androidx.preference.Preference; import android.text.TextUtils; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.wrapper.OverlayManagerWrapper; -import com.android.settings.wrapper.OverlayManagerWrapper.OverlayInfo; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -37,7 +36,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_THEME; +import androidx.annotation.VisibleForTesting; +import androidx.preference.ListPreference; +import androidx.preference.Preference; public class ThemePreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { @@ -45,16 +46,16 @@ public class ThemePreferenceController extends AbstractPreferenceController impl private static final String KEY_THEME = "theme"; private final MetricsFeatureProvider mMetricsFeatureProvider; - private final OverlayManagerWrapper mOverlayService; + private final IOverlayManager mOverlayService; private final PackageManager mPackageManager; public ThemePreferenceController(Context context) { - this(context, ServiceManager.getService(Context.OVERLAY_SERVICE) != null - ? new OverlayManagerWrapper() : null); + this(context, IOverlayManager.Stub + .asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE))); } @VisibleForTesting - ThemePreferenceController(Context context, OverlayManagerWrapper overlayManager) { + ThemePreferenceController(Context context, IOverlayManager overlayManager) { super(context); mOverlayService = overlayManager; mPackageManager = context.getPackageManager(); @@ -77,7 +78,7 @@ public class ThemePreferenceController extends AbstractPreferenceController impl @Override public void updateState(Preference preference) { ListPreference pref = (ListPreference) preference; - String[] pkgs = getAvailableThemes(); + String[] pkgs = getAvailableThemes(false /* currentThemeOnly */); CharSequence[] labels = new CharSequence[pkgs.length]; for (int i = 0; i < pkgs.length; i++) { try { @@ -109,11 +110,15 @@ public class ThemePreferenceController extends AbstractPreferenceController impl @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - String current = getTheme(); + String current = getCurrentTheme(); if (Objects.equals(newValue, current)) { return true; } - mOverlayService.setEnabledExclusiveInCategory((String) newValue, UserHandle.myUserId()); + try { + mOverlayService.setEnabledExclusiveInCategory((String) newValue, UserHandle.myUserId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } return true; } @@ -129,39 +134,43 @@ public class ThemePreferenceController extends AbstractPreferenceController impl } } - private String getTheme() { - List infos = mOverlayService.getOverlayInfosForTarget("android", - UserHandle.myUserId()); - for (int i = 0, size = infos.size(); i < size; i++) { - if (infos.get(i).isEnabled() && isTheme(infos.get(i))) { - return infos.get(i).packageName; - } - } - return null; - } - @Override public boolean isAvailable() { if (mOverlayService == null) return false; - String[] themes = getAvailableThemes(); + String[] themes = getAvailableThemes(false /* currentThemeOnly */); return themes != null && themes.length > 1; } @VisibleForTesting String getCurrentTheme() { - return getTheme(); + String[] themePackages = getAvailableThemes(true /* currentThemeOnly */); + return themePackages.length < 1 ? null : themePackages[0]; } @VisibleForTesting - String[] getAvailableThemes() { - List infos = mOverlayService.getOverlayInfosForTarget("android", - UserHandle.myUserId()); - List pkgs = new ArrayList<>(infos.size()); - for (int i = 0, size = infos.size(); i < size; i++) { - if (isTheme(infos.get(i))) { - pkgs.add(infos.get(i).packageName); + String[] getAvailableThemes(boolean currentThemeOnly) { + List infos; + List pkgs; + try { + infos = mOverlayService.getOverlayInfosForTarget("android", UserHandle.myUserId()); + pkgs = new ArrayList<>(infos.size()); + for (int i = 0, size = infos.size(); i < size; i++) { + if (isTheme(infos.get(i))) { + if (infos.get(i).isEnabled() && currentThemeOnly) { + return new String[] {infos.get(i).packageName}; + } else { + pkgs.add(infos.get(i).packageName); + } + } } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + + // Current enabled theme is not found. + if (currentThemeOnly) { + return new String[0]; } return pkgs.toArray(new String[pkgs.size()]); } diff --git a/src/com/android/settings/wrapper/OverlayManagerWrapper.java b/src/com/android/settings/wrapper/OverlayManagerWrapper.java deleted file mode 100644 index 6e3c2348857..00000000000 --- a/src/com/android/settings/wrapper/OverlayManagerWrapper.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.wrapper; - -import android.content.Context; -import android.content.om.IOverlayManager; -import android.content.om.OverlayInfo; -import android.os.RemoteException; -import android.os.ServiceManager; - -import java.util.ArrayList; -import java.util.List; - -public class OverlayManagerWrapper { - - private final IOverlayManager mOverlayManager; - - public OverlayManagerWrapper(IOverlayManager overlayManager) { - mOverlayManager = overlayManager; - } - - public OverlayManagerWrapper() { - this(IOverlayManager.Stub.asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE))); - } - - public List getOverlayInfosForTarget(String overlay, int userId) { - if (mOverlayManager == null) { - return new ArrayList<>(); - } - try { - List infos - = mOverlayManager.getOverlayInfosForTarget(overlay, userId); - ArrayList result = new ArrayList<>(infos.size()); - for (int i = 0; i < infos.size(); i++) { - result.add(new OverlayInfo(infos.get(i))); - } - return result; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - public boolean setEnabled(String overlay, boolean enabled, int userId) { - if (mOverlayManager == null) { - return false; - } - try { - return mOverlayManager.setEnabled(overlay, enabled, userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - public boolean setEnabledExclusiveInCategory(String overlay, int userId) { - if (mOverlayManager == null) { - return false; - } - try { - return mOverlayManager.setEnabledExclusiveInCategory(overlay, userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - public static class OverlayInfo { - - public static final String CATEGORY_THEME = android.content.om.OverlayInfo.CATEGORY_THEME; - public final String packageName; - public final String category; - private final boolean mEnabled; - - public OverlayInfo(String packageName, String category, boolean enabled) { - this.packageName = packageName; - this.category = category; - mEnabled = enabled; - } - - public OverlayInfo(android.content.om.OverlayInfo info) { - mEnabled = info.isEnabled(); - category = info.category; - packageName = info.packageName; - } - - public boolean isEnabled() { - return mEnabled; - } - } -} diff --git a/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java index 34a0581e582..fd063cb37b6 100644 --- a/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java @@ -26,14 +26,15 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; import android.content.pm.PackageManager; +import android.os.RemoteException; import androidx.preference.ListPreference; import androidx.preference.PreferenceScreen; import android.view.DisplayCutout; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.wrapper.OverlayManagerWrapper; -import com.android.settings.wrapper.OverlayManagerWrapper.OverlayInfo; import org.junit.Before; import org.junit.Test; @@ -41,6 +42,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Arrays; @RunWith(SettingsRobolectricTestRunner.class) @@ -54,7 +56,7 @@ public class EmulateDisplayCutoutPreferenceControllerTest { @Mock private Context mContext; @Mock - private OverlayManagerWrapper mOverlayManager; + private IOverlayManager mOverlayManager; @Mock private PackageManager mPackageManager; @Mock @@ -64,6 +66,7 @@ public class EmulateDisplayCutoutPreferenceControllerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.OVERLAY_SERVICE)).thenReturn(mOverlayManager); mockCurrentOverlays(); when(mPackageManager.getApplicationInfo(any(), anyInt())) .thenThrow(PackageManager.NameNotFoundException.class); @@ -72,8 +75,12 @@ public class EmulateDisplayCutoutPreferenceControllerTest { } Object mockCurrentOverlays(OverlayInfo... overlays) { - return when(mOverlayManager.getOverlayInfosForTarget(eq("android"), anyInt())) - .thenReturn(Arrays.asList(overlays)); + try { + return when(mOverlayManager.getOverlayInfosForTarget(eq("android"), anyInt())) + .thenReturn(Arrays.asList(overlays)); + } catch (RemoteException re) { + return new ArrayList(); + } } @Test @@ -146,6 +153,15 @@ public class EmulateDisplayCutoutPreferenceControllerTest { } private static OverlayInfo createFakeOverlay(String pkg, boolean enabled) { - return new OverlayInfo(pkg, DisplayCutout.EMULATION_OVERLAY_CATEGORY, enabled); + final int state = (enabled) ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED; + + return new OverlayInfo(pkg /* packageName */, + pkg + ".target" /* targetPackageName */, + DisplayCutout.EMULATION_OVERLAY_CATEGORY /* category */, + pkg + ".baseCodePath" /* baseCodePath */, + state /* state */, + 0 /* userId */, + 0 /* priority */, + true /* isStatic */); } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java index 47cdd35f6f6..19ffdc6d731 100644 --- a/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java @@ -16,25 +16,26 @@ package com.android.settings.display; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import androidx.preference.ListPreference; import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.wrapper.OverlayManagerWrapper; import org.junit.Before; import org.junit.Test; @@ -44,6 +45,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import java.util.Arrays; + +import androidx.preference.ListPreference; + @RunWith(SettingsRobolectricTestRunner.class) public class ThemePreferenceControllerTest { @@ -55,6 +60,8 @@ public class ThemePreferenceControllerTest { private ApplicationInfo mApplicationInfo; @Mock private ListPreference mPreference; + @Mock + private IOverlayManager mOverlayManager; private ThemePreferenceController mController; @@ -67,8 +74,28 @@ public class ThemePreferenceControllerTest { when(mContext.getString(R.string.default_theme)) .thenReturn(RuntimeEnvironment.application.getString(R.string.default_theme)); - mController = - spy(new ThemePreferenceController(mContext, mock(OverlayManagerWrapper.class))); + when(mContext.getSystemService(Context.OVERLAY_SERVICE)).thenReturn(mOverlayManager); + mController = spy(new ThemePreferenceController(mContext, mOverlayManager)); + } + + @Test + public void testAvailable_false() throws Exception { + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn( + new PackageInfo()); + when(mOverlayManager.getOverlayInfosForTarget(any(), anyInt())) + .thenReturn(Arrays.asList(new OverlayInfo("", "", "", "", 0, 0, 0, false))); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void testAvailable_true() throws Exception { + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn( + new PackageInfo()); + when(mOverlayManager.getOverlayInfosForTarget(any(), anyInt())) + .thenReturn(Arrays.asList( + new OverlayInfo("", "", OverlayInfo.CATEGORY_THEME, "", 0, 0, 0, true), + new OverlayInfo("", "", OverlayInfo.CATEGORY_THEME, "", 0, 0, 0, true))); + assertThat(mController.isAvailable()).isTrue(); } @Test @@ -79,7 +106,7 @@ public class ThemePreferenceControllerTest { final String themeLabel2 = "Theme2"; final String[] themes = {pkg1, pkg2}; doReturn("pkg1.theme1").when(mController).getCurrentTheme(); - doReturn(themes).when(mController).getAvailableThemes(); + doReturn(themes).when(mController).getAvailableThemes(false /* currentThemeOnly */); when(mPackageManager.getApplicationInfo(anyString(), anyInt()).loadLabel(mPackageManager)) .thenReturn(themeLabel1) .thenReturn(themeLabel2); @@ -98,7 +125,7 @@ public class ThemePreferenceControllerTest { final String themeLabel2 = "Theme2"; final String[] themes = {pkg1, pkg2}; doReturn(null).when(mController).getCurrentTheme(); - doReturn(themes).when(mController).getAvailableThemes(); + doReturn(themes).when(mController).getAvailableThemes(false /* currentThemeOnly */); when(mPackageManager.getApplicationInfo(anyString(), anyInt()).loadLabel(mPackageManager)) .thenReturn(themeLabel1) .thenReturn(themeLabel2); @@ -109,4 +136,32 @@ public class ThemePreferenceControllerTest { .setSummary(RuntimeEnvironment.application.getString(R.string.default_theme)); verify(mPreference).setValue(null); } + + @Test + public void getCurrentTheme_withEnabledState() throws Exception { + OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android", + OverlayInfo.CATEGORY_THEME, "", OverlayInfo.STATE_ENABLED, 0, 0, true); + OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android", + OverlayInfo.CATEGORY_THEME, "", 0, 0, 0, true); + when(mOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn( + Arrays.asList(info1, info2)); + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn( + new PackageInfo()); + + assertThat(mController.getCurrentTheme()).isEqualTo(info1.packageName); + } + + @Test + public void testGetCurrentTheme_withoutEnabledState() throws Exception { + OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android", + OverlayInfo.CATEGORY_THEME, "", OverlayInfo.STATE_DISABLED, 0, 0, true); + OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android", + OverlayInfo.CATEGORY_THEME, "", 0, 0, 0, true); + when(mOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn( + Arrays.asList(info1, info2)); + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn( + new PackageInfo()); + + assertThat(mController.getCurrentTheme()).isNull(); + } } diff --git a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java index 56552132515..96ffe23fd39 100644 --- a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContextWrapper; +import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -33,9 +34,6 @@ import android.content.pm.PackageManager; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import androidx.preference.ListPreference; - -import com.android.settings.wrapper.OverlayManagerWrapper; import org.junit.Before; import org.junit.Test; @@ -44,18 +42,20 @@ import org.mockito.ArgumentCaptor; import java.util.ArrayList; +import androidx.preference.ListPreference; + @SmallTest @RunWith(AndroidJUnit4.class) public class ThemePreferenceControllerTest { - private OverlayManagerWrapper mMockOverlayManager; + private IOverlayManager mMockOverlayManager; private ContextWrapper mContext; private ThemePreferenceController mPreferenceController; private PackageManager mMockPackageManager; @Before public void setup() { - mMockOverlayManager = mock(OverlayManagerWrapper.class); + mMockOverlayManager = mock(IOverlayManager.class); mMockPackageManager = mock(PackageManager.class); mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) { @Override @@ -69,9 +69,9 @@ public class ThemePreferenceControllerTest { @Test public void testUpdateState() throws Exception { OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android", - "", "", OverlayInfo.STATE_ENABLED, 0, 0, true); + OverlayInfo.CATEGORY_THEME, "", OverlayInfo.STATE_ENABLED, 0, 0, true); OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android", - "", "", 0, 0, 0, true); + OverlayInfo.CATEGORY_THEME, "", 0, 0, 0, true); when(mMockPackageManager.getApplicationInfo(any(), anyInt())).thenAnswer(inv -> { ApplicationInfo info = mock(ApplicationInfo.class); if ("com.android.Theme1".equals(inv.getArguments()[0])) { @@ -105,9 +105,9 @@ public class ThemePreferenceControllerTest { @Test public void testUpdateState_withStaticOverlay() throws Exception { OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android", - "", "", OverlayInfo.STATE_ENABLED, 0, 0, true); + OverlayInfo.CATEGORY_THEME, "", OverlayInfo.STATE_ENABLED, 0, 0, true); OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android", - "", "", OverlayInfo.STATE_ENABLED, 0, 0, true); + OverlayInfo.CATEGORY_THEME, "", OverlayInfo.STATE_ENABLED, 0, 0, true); when(mMockPackageManager.getApplicationInfo(any(), anyInt())).thenAnswer(inv -> { ApplicationInfo info = mock(ApplicationInfo.class); if ("com.android.Theme1".equals(inv.getArguments()[0])) { @@ -140,29 +140,10 @@ public class ThemePreferenceControllerTest { verify(pref).setValue(eq("com.android.Theme2")); } - @Test - public void testAvailable_false() throws Exception { - when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn( - new PackageInfo()); - when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())) - .thenReturn(list(new OverlayInfo("", "", "", "", 0, 0, 0, false))); - assertThat(mPreferenceController.isAvailable()).isFalse(); - } - - @Test - public void testAvailable_true() throws Exception { - when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn( - new PackageInfo()); - when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())) - .thenReturn(list(new OverlayInfo("", "", "", "", 0, 0, 0, true), - new OverlayInfo("", "", "", "", 0, 0, 0, true))); - assertThat(mPreferenceController.isAvailable()).isTrue(); - } - - private ArrayList list(OverlayInfo... infos) { - ArrayList list = new ArrayList<>(); + private ArrayList list(OverlayInfo... infos) { + ArrayList list = new ArrayList<>(); for (OverlayInfo info : infos) { - list.add(new OverlayManagerWrapper.OverlayInfo(info)); + list.add(info); } return list; } From 24283d3076a0e1e86bb649c4e4fdc842ec96afc8 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Fri, 25 May 2018 12:52:24 -0700 Subject: [PATCH 10/12] Remove Advanced dropdown from "About phone" screen All the items will appear in the list instead of hiding some under an Advanced dropdown. Bug: 80302533 Test: manual (Settings->System->About phone) Change-Id: I82857ec8e027b00762c1191d9450d30dfe47016f --- res/xml/my_device_info.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml index 213ff1c1b7b..3dac8b879f2 100644 --- a/res/xml/my_device_info.xml +++ b/res/xml/my_device_info.xml @@ -19,8 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="my_device_info_pref_screen" - android:title="@string/about_settings" - settings:initialExpandedChildrenCount="7"> + android:title="@string/about_settings"> Date: Fri, 25 May 2018 22:57:03 +0100 Subject: [PATCH 11/12] Hide haptic intensity settings for devices without enough dynamic range. Despite our hope, devices prior to P18 just don't have the dynamic range to give three distinct vibration intensity settings. Given this, only show an on / off toggle for devices by default unless they explicitly opt-in to multiple intensity levels. Test: m -j RunSettingsRoboTests Bug: 80243632 Change-Id: I14ece855cf20f9fa44b0648d28f89c0053be5c5f --- res/values/config.xml | 5 ++ .../accessibility/AccessibilitySettings.java | 32 +++++++---- ...ibrationIntensityPreferenceController.java | 32 +++++++---- .../VibrationPreferenceFragment.java | 57 +++++++++++++------ ...tionIntensityPreferenceControllerTest.java | 54 +++++++++++++++++- .../VibrationPreferenceFragmentTest.java | 53 ++++++++++++++--- 6 files changed, 184 insertions(+), 49 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index 8e55ea95e36..f46134f301b 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -134,4 +134,9 @@ true + + false diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 72e485e7c81..4ddcc31bf27 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -779,17 +779,27 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } private String getVibrationSummary(Context context, @VibrationIntensity int intensity) { - switch (intensity) { - case Vibrator.VIBRATION_INTENSITY_OFF: - return context.getString(R.string.accessibility_vibration_summary_off); - case Vibrator.VIBRATION_INTENSITY_LOW: - return context.getString(R.string.accessibility_vibration_summary_low); - case Vibrator.VIBRATION_INTENSITY_MEDIUM: - return context.getString(R.string.accessibility_vibration_summary_medium); - case Vibrator.VIBRATION_INTENSITY_HIGH: - return context.getString(R.string.accessibility_vibration_summary_high); - default: - return ""; + final boolean supportsMultipleIntensities = context.getResources().getBoolean( + R.bool.config_vibration_supports_multiple_intensities); + if (supportsMultipleIntensities) { + switch (intensity) { + case Vibrator.VIBRATION_INTENSITY_OFF: + return context.getString(R.string.accessibility_vibration_summary_off); + case Vibrator.VIBRATION_INTENSITY_LOW: + return context.getString(R.string.accessibility_vibration_summary_low); + case Vibrator.VIBRATION_INTENSITY_MEDIUM: + return context.getString(R.string.accessibility_vibration_summary_medium); + case Vibrator.VIBRATION_INTENSITY_HIGH: + return context.getString(R.string.accessibility_vibration_summary_high); + default: + return ""; + } + } else { + if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { + return context.getString(R.string.switch_on_text); + } else { + return context.getString(R.string.switch_off_text); + } } } diff --git a/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java b/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java index 9c81afa2f16..3fbb1230a50 100644 --- a/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java +++ b/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java @@ -81,17 +81,27 @@ public abstract class VibrationIntensityPreferenceController extends BasePrefere } public static CharSequence getIntensityString(Context context, int intensity) { - switch (intensity) { - case Vibrator.VIBRATION_INTENSITY_OFF: - return context.getText(R.string.accessibility_vibration_intensity_off); - case Vibrator.VIBRATION_INTENSITY_LOW: - return context.getText(R.string.accessibility_vibration_intensity_low); - case Vibrator.VIBRATION_INTENSITY_MEDIUM: - return context.getText(R.string.accessibility_vibration_intensity_medium); - case Vibrator.VIBRATION_INTENSITY_HIGH: - return context.getText(R.string.accessibility_vibration_intensity_high); - default: - return ""; + final boolean supportsMultipleIntensities = context.getResources().getBoolean( + R.bool.config_vibration_supports_multiple_intensities); + if (supportsMultipleIntensities) { + switch (intensity) { + case Vibrator.VIBRATION_INTENSITY_OFF: + return context.getString(R.string.accessibility_vibration_intensity_off); + case Vibrator.VIBRATION_INTENSITY_LOW: + return context.getString(R.string.accessibility_vibration_intensity_low); + case Vibrator.VIBRATION_INTENSITY_MEDIUM: + return context.getString(R.string.accessibility_vibration_intensity_medium); + case Vibrator.VIBRATION_INTENSITY_HIGH: + return context.getString(R.string.accessibility_vibration_intensity_high); + default: + return ""; + } + } else { + if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { + return context.getString(R.string.switch_off_text); + } else { + return context.getString(R.string.switch_on_text); + } } } diff --git a/src/com/android/settings/accessibility/VibrationPreferenceFragment.java b/src/com/android/settings/accessibility/VibrationPreferenceFragment.java index e911b612e37..f02c4747c96 100644 --- a/src/com/android/settings/accessibility/VibrationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/VibrationPreferenceFragment.java @@ -53,28 +53,15 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm final static String KEY_INTENSITY_MEDIUM = "intensity_medium"; @VisibleForTesting final static String KEY_INTENSITY_HIGH = "intensity_high"; + // KEY_INTENSITY_ON is only used when the device doesn't support multiple intensity levels. + @VisibleForTesting + final static String KEY_INTENSITY_ON = "intensity_on"; private final Map mCandidates; private final SettingsObserver mSettingsObserver; public VibrationPreferenceFragment() { mCandidates = new ArrayMap<>(); - mCandidates.put(KEY_INTENSITY_OFF, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, - R.string.accessibility_vibration_intensity_off, - Vibrator.VIBRATION_INTENSITY_OFF)); - mCandidates.put(KEY_INTENSITY_LOW, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_LOW, - R.string.accessibility_vibration_intensity_low, - Vibrator.VIBRATION_INTENSITY_LOW)); - mCandidates.put(KEY_INTENSITY_MEDIUM, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_MEDIUM, - R.string.accessibility_vibration_intensity_medium, - Vibrator.VIBRATION_INTENSITY_MEDIUM)); - mCandidates.put(KEY_INTENSITY_HIGH, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_HIGH, - R.string.accessibility_vibration_intensity_high, - Vibrator.VIBRATION_INTENSITY_HIGH)); mSettingsObserver = new SettingsObserver(); } @@ -82,6 +69,39 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm public void onAttach(Context context) { super.onAttach(context); mSettingsObserver.register(); + if (mCandidates.isEmpty()) { + loadCandidates(context); + } + } + + private void loadCandidates(Context context) { + final boolean supportsMultipleIntensities = context.getResources().getBoolean( + R.bool.config_vibration_supports_multiple_intensities); + if (supportsMultipleIntensities) { + mCandidates.put(KEY_INTENSITY_OFF, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, + R.string.accessibility_vibration_intensity_off, + Vibrator.VIBRATION_INTENSITY_OFF)); + mCandidates.put(KEY_INTENSITY_LOW, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_LOW, + R.string.accessibility_vibration_intensity_low, + Vibrator.VIBRATION_INTENSITY_LOW)); + mCandidates.put(KEY_INTENSITY_MEDIUM, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_MEDIUM, + R.string.accessibility_vibration_intensity_medium, + Vibrator.VIBRATION_INTENSITY_MEDIUM)); + mCandidates.put(KEY_INTENSITY_HIGH, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_HIGH, + R.string.accessibility_vibration_intensity_high, + Vibrator.VIBRATION_INTENSITY_HIGH)); + } else { + mCandidates.put(KEY_INTENSITY_OFF, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, + R.string.switch_off_text, Vibrator.VIBRATION_INTENSITY_OFF)); + mCandidates.put(KEY_INTENSITY_ON, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_ON, + R.string.switch_on_text, getDefaultVibrationIntensity())); + } } @Override @@ -118,7 +138,10 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm final int vibrationIntensity = Settings.System.getInt(getContext().getContentResolver(), getVibrationIntensitySetting(), getDefaultVibrationIntensity()); for (VibrationIntensityCandidateInfo candidate : mCandidates.values()) { - if (candidate.getIntensity() == vibrationIntensity) { + final boolean matchesIntensity = candidate.getIntensity() == vibrationIntensity; + final boolean matchesOn = candidate.getKey().equals(KEY_INTENSITY_ON) + && vibrationIntensity != Vibrator.VIBRATION_INTENSITY_OFF; + if (matchesIntensity || matchesOn) { return candidate.getKey(); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java index 067f015e85e..aa9efc51f88 100644 --- a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java @@ -18,10 +18,12 @@ package com.android.settings.accessibility; import static android.provider.Settings.System.NOTIFICATION_VIBRATION_INTENSITY; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.arch.lifecycle.LifecycleOwner; import android.content.Context; +import android.content.res.Resources; import android.os.Vibrator; import android.provider.Settings; import android.support.v7.preference.Preference; @@ -48,6 +50,7 @@ public class NotificationVibrationIntensityPreferenceControllerTest { private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; private Context mContext; + private Resources mResources; private NotificationVibrationIntensityPreferenceController mController; private Preference mPreference; @@ -56,7 +59,11 @@ public class NotificationVibrationIntensityPreferenceControllerTest { MockitoAnnotations.initMocks(this); mLifecycleOwner = () -> mLifecycle; mLifecycle = new Lifecycle(mLifecycleOwner); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); + mResources = spy(mContext.getResources()); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getBoolean(R.bool.config_vibration_supports_multiple_intensities)) + .thenReturn(true); mController = new NotificationVibrationIntensityPreferenceController(mContext) { @Override protected int getDefaultIntensity() { @@ -68,7 +75,6 @@ public class NotificationVibrationIntensityPreferenceControllerTest { mPreference.setSummary("Test"); when(mScreen.findPreference(mController.getPreferenceKey())) .thenReturn(mPreference); - mController.displayPreference(mScreen); } @Test @@ -80,7 +86,10 @@ public class NotificationVibrationIntensityPreferenceControllerTest { } @Test - public void updateState_shouldRefreshSummary() { + public void updateState_withMultipleIntensitySuport_shouldRefreshSummary() { + setSupportsMultipleIntensities(true); + showPreference(); + Settings.System.putInt(mContext.getContentResolver(), NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); mController.updateState(mPreference); @@ -105,4 +114,43 @@ public class NotificationVibrationIntensityPreferenceControllerTest { assertThat(mPreference.getSummary()) .isEqualTo(mContext.getString(R.string.accessibility_vibration_intensity_off)); } + + @Test + public void updateState_withoutMultipleIntensitySupport_shouldRefreshSummary() { + setSupportsMultipleIntensities(false); + showPreference(); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_on_text)); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_on_text)); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_on_text)); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_off_text)); + } + + private void setSupportsMultipleIntensities(boolean hasSupport) { + when(mResources.getBoolean(R.bool.config_vibration_supports_multiple_intensities)) + .thenReturn(hasSupport); + } + + private void showPreference() { + mController.displayPreference(mScreen); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java index 8550cf98916..ecd2ff9ce57 100644 --- a/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java @@ -20,16 +20,19 @@ import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_LOW; import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_MEDIUM; import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_OFF; +import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_ON; 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; import android.app.Activity; import android.content.Context; +import android.content.res.Resources; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; +import com.android.settings.R; import com.android.settings.accessibility.VibrationPreferenceFragment.VibrationIntensityCandidateInfo; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -58,12 +61,11 @@ public class VibrationPreferenceFragmentTest { INTENSITY_TO_KEY.put(Vibrator.VIBRATION_INTENSITY_HIGH, KEY_INTENSITY_HIGH); } - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Activity mActivity; @Mock private UserManager mUserManager; private Context mContext; + private Resources mResources; private TestVibrationPreferenceFragment mFragment; @Before @@ -71,16 +73,18 @@ public class VibrationPreferenceFragmentTest { MockitoAnnotations.initMocks(this); FakeFeatureFactory.setupForTest(); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); + mResources = spy(mContext.getResources()); + when(mContext.getResources()).thenReturn(mResources); mFragment = spy(new TestVibrationPreferenceFragment()); - doReturn(mUserManager).when(mActivity).getSystemService(Context.USER_SERVICE); - doReturn(mContext).when(mFragment).getContext(); - mFragment.onAttach(mActivity); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); } @Test public void changeIntensitySetting_shouldResultInCorrespondingKey() { + setSupportsMultipleIntensities(true); + mFragment.onAttach(mContext); for (Map.Entry entry : INTENSITY_TO_KEY.entrySet()) { Settings.System.putInt(mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_INTENSITY, entry.getKey()); @@ -88,13 +92,38 @@ public class VibrationPreferenceFragmentTest { } } + @Test + public void changeIntensitySetting_WithoutMultipleIntensitySupport_shouldResultInOn() { + setSupportsMultipleIntensities(false); + mFragment.onAttach(mContext); + for (int intensity : INTENSITY_TO_KEY.keySet()) { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_INTENSITY, intensity); + final String expectedKey = intensity == Vibrator.VIBRATION_INTENSITY_OFF + ? KEY_INTENSITY_OFF + : KEY_INTENSITY_ON; + assertThat(mFragment.getDefaultKey()).isEqualTo(expectedKey); + } + } + @Test public void initialDefaultKey_shouldBeMedium() { + setSupportsMultipleIntensities(true); + mFragment.onAttach(mContext); assertThat(mFragment.getDefaultKey()).isEqualTo(KEY_INTENSITY_MEDIUM); } + @Test + public void initialDefaultKey_WithoutMultipleIntensitySupport_shouldBeOn() { + setSupportsMultipleIntensities(false); + mFragment.onAttach(mContext); + assertThat(mFragment.getDefaultKey()).isEqualTo(KEY_INTENSITY_ON); + } + @Test public void candidates_shouldBeSortedByIntensity() { + setSupportsMultipleIntensities(true); + mFragment.onAttach(mContext); final List candidates = mFragment.getCandidates(); assertThat(candidates.size()).isEqualTo(INTENSITY_TO_KEY.size()); VibrationIntensityCandidateInfo prevCandidate = @@ -106,6 +135,11 @@ public class VibrationPreferenceFragmentTest { } } + private void setSupportsMultipleIntensities(boolean hasSupport) { + when(mResources.getBoolean(R.bool.config_vibration_supports_multiple_intensities)) + .thenReturn(hasSupport); + } + private class TestVibrationPreferenceFragment extends VibrationPreferenceFragment { @Override protected int getPreferenceScreenResId() { @@ -129,5 +163,10 @@ public class VibrationPreferenceFragmentTest { protected int getDefaultVibrationIntensity() { return Vibrator.VIBRATION_INTENSITY_MEDIUM; } + + @Override + public Context getContext() { + return mContext; + } } } From c93dfd8f482934c4d849e82db87097f7d69d96f3 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Fri, 25 May 2018 23:37:29 +0100 Subject: [PATCH 12/12] Add michaelwr to OWNERS for haptics settings in accessibility Bug: 80243632 Test: N/A Change-Id: Id315d626af527946230affc292ed9b329efe8b5b --- src/com/android/settings/accessibility/OWNERS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/accessibility/OWNERS b/src/com/android/settings/accessibility/OWNERS index 28dfdefabef..ba03055c736 100644 --- a/src/com/android/settings/accessibility/OWNERS +++ b/src/com/android/settings/accessibility/OWNERS @@ -1,3 +1,6 @@ # Default reviewers for this and subdirectories. pweaver@google.com -zork@google.com \ No newline at end of file +zork@google.com + +per-file HapticFeedbackIntensityPreferenceController.java = michaelwr@google.com +per-file *Vibration* = michaelwr@google.com