From 4b269698391864b4cc6b204300cd8ed9c36adb91 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Tue, 22 May 2018 14:26:00 -0700 Subject: [PATCH 01/18] Clear indexing before adding all Slices Before we index, add a call to clear indexed data before indexing again. It's an optional call. Fixes: 80065409 Test: robotests Change-Id: Iddb0ce02c50d84b51fbf6fc2be0bdc9aa1f5987a --- .../search/DeviceIndexFeatureProvider.java | 28 +++++++++-- .../DeviceIndexFeatureProviderImpl.java | 5 ++ .../search/DeviceIndexUpdateJobService.java | 9 ++++ .../DeviceIndexFeatureProviderTest.java | 48 +++++++++++++++++++ .../DeviceIndexUpdateJobServiceTest.java | 1 + 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/search/DeviceIndexFeatureProvider.java b/src/com/android/settings/search/DeviceIndexFeatureProvider.java index bf75ee81c3e..1c25399c8fa 100644 --- a/src/com/android/settings/search/DeviceIndexFeatureProvider.java +++ b/src/com/android/settings/search/DeviceIndexFeatureProvider.java @@ -31,6 +31,7 @@ import com.android.settings.Utils; import com.android.settings.slices.SettingsSliceProvider; import java.util.List; +import java.util.Locale; import java.util.Objects; public interface DeviceIndexFeatureProvider { @@ -39,15 +40,21 @@ public interface DeviceIndexFeatureProvider { String TAG = "DeviceIndex"; String INDEX_VERSION = "settings:index_version"; + String INDEX_LANGUAGE = "settings:language"; // Increment when new items are added to ensure they get pushed to the device index. String VERSION = Build.FINGERPRINT; + // When the device language changes, re-index so Slices trigger in device language. + Locale LANGUAGE = Locale.getDefault(); + boolean isIndexingEnabled(); void index(Context context, CharSequence title, Uri sliceUri, Uri launchUri, List keywords); + void clearIndex(Context context); + default void updateIndex(Context context, boolean force) { if (!isIndexingEnabled()) { Log.w(TAG, "Skipping: device index is not enabled"); @@ -59,12 +66,14 @@ public interface DeviceIndexFeatureProvider { return; } - if (!force && Objects.equals( - Settings.Secure.getString(context.getContentResolver(), INDEX_VERSION), VERSION)) { + if (!force && skipIndex(context)) { // No need to update. return; } + // Prevent scheduling multiple jobs + setIndexState(context); + final ComponentName jobComponent = new ComponentName(context.getPackageName(), DeviceIndexUpdateJobService.class.getName()); final int jobId = context.getResources().getInteger(R.integer.device_index_update); @@ -77,7 +86,6 @@ public interface DeviceIndexFeatureProvider { .setOverrideDeadline(1) .build()); - Settings.Secure.putString(context.getContentResolver(), INDEX_VERSION, VERSION); } static Uri createDeepLink(String s) { @@ -86,4 +94,18 @@ public interface DeviceIndexFeatureProvider { .appendQueryParameter(INTENT, s) .build(); } + + static boolean skipIndex(Context context) { + final boolean isSameVersion = Objects.equals( + Settings.Secure.getString(context.getContentResolver(), INDEX_VERSION), VERSION); + final boolean isSameLanguage = Objects.equals( + Settings.Secure.getString(context.getContentResolver(), INDEX_LANGUAGE), LANGUAGE); + return isSameLanguage && isSameVersion; + } + + static void setIndexState(Context context) { + Settings.Secure.putString(context.getContentResolver(), INDEX_VERSION, VERSION); + Settings.Secure.putString(context.getContentResolver(), INDEX_LANGUAGE, + LANGUAGE.toString()); + } } diff --git a/src/com/android/settings/search/DeviceIndexFeatureProviderImpl.java b/src/com/android/settings/search/DeviceIndexFeatureProviderImpl.java index 7a11bd46789..087ecf83dbc 100644 --- a/src/com/android/settings/search/DeviceIndexFeatureProviderImpl.java +++ b/src/com/android/settings/search/DeviceIndexFeatureProviderImpl.java @@ -31,4 +31,9 @@ public class DeviceIndexFeatureProviderImpl implements DeviceIndexFeatureProvide List keywords) { // Not enabled by default. } + + @Override + public void clearIndex(Context context) { + // Not enabled by default. + } } diff --git a/src/com/android/settings/search/DeviceIndexUpdateJobService.java b/src/com/android/settings/search/DeviceIndexUpdateJobService.java index 19b7d5e60b6..97b0a61ca6b 100644 --- a/src/com/android/settings/search/DeviceIndexUpdateJobService.java +++ b/src/com/android/settings/search/DeviceIndexUpdateJobService.java @@ -25,6 +25,7 @@ import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; import android.net.Uri.Builder; +import android.provider.SettingsSlicesContract; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -84,11 +85,19 @@ public class DeviceIndexUpdateJobService extends JobService { .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .build(); + final Uri platformBaseUri = new Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSlicesContract.AUTHORITY) + .build(); final Collection slices = manager.getSliceDescendants(baseUri); + slices.addAll(manager.getSliceDescendants(platformBaseUri)); + if (DEBUG) { Log.d(TAG, "Indexing " + slices.size() + " slices"); } + indexProvider.clearIndex(this /* context */); + for (Uri slice : slices) { if (!mRunningJob) { return; diff --git a/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java b/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java index d4c15802b99..a900db033bc 100644 --- a/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java +++ b/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.app.job.JobScheduler; +import android.os.Build; import android.provider.Settings; import com.android.settings.testutils.FakeFeatureFactory; @@ -76,4 +77,51 @@ public class DeviceIndexFeatureProviderTest { mProvider.updateIndex(mActivity, false); verify(jobScheduler).schedule(any()); } + + @Test + public void updateIndex_enabled_provisioned_newBuild_shouldIndex() { + Settings.Global.putInt(mActivity.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + DeviceIndexFeatureProvider.setIndexState(mActivity); + Settings.Global.putString(mActivity.getContentResolver(), + DeviceIndexFeatureProvider.INDEX_VERSION, "new version"); + Settings.Global.putString(mActivity.getContentResolver(), + DeviceIndexFeatureProvider.LANGUAGE.toString(), + DeviceIndexFeatureProvider.INDEX_LANGUAGE); + JobScheduler jobScheduler = mock(JobScheduler.class); + when(mProvider.isIndexingEnabled()).thenReturn(true); + when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); + + mProvider.updateIndex(mActivity, false); + verify(jobScheduler).schedule(any()); + } + + @Test + public void updateIndex_enabled_provisioned_newIndex_shouldIndex() { + Settings.Global.putInt(mActivity.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + DeviceIndexFeatureProvider.setIndexState(mActivity); + Settings.Global.putString(mActivity.getContentResolver(), + DeviceIndexFeatureProvider.INDEX_LANGUAGE, "new language"); + JobScheduler jobScheduler = mock(JobScheduler.class); + when(mProvider.isIndexingEnabled()).thenReturn(true); + when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); + + mProvider.updateIndex(mActivity, false); + verify(jobScheduler).schedule(any()); + } + + @Test + public void updateIndex_enabled_provisioned_sameBuild_sameLang_shouldNotIndex() { + Settings.Global.putInt(mActivity.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + DeviceIndexFeatureProvider.setIndexState(mActivity); + JobScheduler jobScheduler = mock(JobScheduler.class); + when(mProvider.isIndexingEnabled()).thenReturn(true); + when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); + + mProvider.updateIndex(mActivity, false); + + verify(mProvider, never()).index(any(), any(), any(), any(), any()); + } } diff --git a/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java b/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java index ec16893de24..b5de9737db2 100644 --- a/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java +++ b/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java @@ -124,6 +124,7 @@ public class DeviceIndexUpdateJobServiceTest { DeviceIndexFeatureProvider indexFeatureProvider = FakeFeatureFactory.getFactory(mActivity) .getDeviceIndexFeatureProvider(); + verify(indexFeatureProvider).clearIndex(any()); verify(indexFeatureProvider, times(1)).index(any(), any(), any(), any(), any()); } From d9613b2742bdb8441b8050edfdf0a9772f7d45c9 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Tue, 22 May 2018 18:24:06 -0700 Subject: [PATCH 02/18] Return null when setting can't be changed. If a Slice cannot be changed, return null instead of an error slice. Bug: 80155832 Test: robotests Change-Id: I843fee76cf19d49cc994062059cb231f222120b2 Merged-In: Ib94136c449c6d9c1911f89833bba62fd2263daa4 --- .../settings/slices/SliceBuilderUtils.java | 47 +++++-------------- .../wifi/calling/WifiCallingSliceHelper.java | 13 ++++- .../slices/SliceBuilderUtilsTest.java | 16 ++----- .../settings/testutils/SliceTester.java | 16 +------ .../calling/WifiCallingSliceHelperTest.java | 4 +- 5 files changed, 30 insertions(+), 66 deletions(-) diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index feb7042552d..5158bdb03ea 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -86,8 +86,13 @@ public class SliceBuilderUtils { FeatureFactory.getFactory(context).getMetricsFeatureProvider() .action(context, MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED, sliceNamePair); - if (controller.getAvailabilityStatus() != AVAILABLE) { - return buildUnavailableSlice(context, sliceData, controller); + if (!controller.isAvailable()) { + // Cannot guarantee setting page is accessible, let the presenter handle error case. + return null; + } + + if (controller.getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) { + return buildUnavailableSlice(context, sliceData); } switch (sliceData.getSliceType()) { @@ -176,14 +181,6 @@ public class SliceBuilderUtils { return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */); } - /** - * @return {@link PendingIntent} to the Settings home page. - */ - public static PendingIntent getSettingsIntent(Context context) { - final Intent intent = new Intent(Settings.ACTION_SETTINGS); - return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */); - } - /** * @return the summary text for a {@link Slice} built for {@param sliceData}. */ @@ -355,40 +352,20 @@ public class SliceBuilderUtils { return keywords; } - private static Slice buildUnavailableSlice(Context context, SliceData data, - BasePreferenceController controller) { + private static Slice buildUnavailableSlice(Context context, SliceData data) { final String title = data.getTitle(); final List keywords = buildSliceKeywords(data); @ColorInt final int color = Utils.getColorAccent(context); - final String summary; - final SliceAction primaryAction; + final CharSequence summary = context.getText(R.string.disabled_dependent_setting_summary); final IconCompat icon = IconCompat.createWithResource(context, data.getIconResource()); - - switch (controller.getAvailabilityStatus()) { - case UNSUPPORTED_ON_DEVICE: - summary = context.getString(R.string.unsupported_setting_summary); - primaryAction = new SliceAction(getSettingsIntent(context), icon, title); - break; - case DISABLED_FOR_USER: - summary = context.getString(R.string.disabled_for_user_setting_summary); - primaryAction = new SliceAction(getContentPendingIntent(context, data), icon, - title); - break; - case DISABLED_DEPENDENT_SETTING: - summary = context.getString(R.string.disabled_dependent_setting_summary); - primaryAction = new SliceAction(getContentPendingIntent(context, data), icon, - title); - break; - case CONDITIONALLY_UNAVAILABLE: - default: - summary = context.getString(R.string.unknown_unavailability_setting_summary); - primaryAction = new SliceAction(getSettingsIntent(context), icon, title); - } + final SliceAction primaryAction = new SliceAction(getContentPendingIntent(context, data), + icon, title); return new ListBuilder(context, data.getUri(), ListBuilder.INFINITY) .setAccentColor(color) .addRow(builder -> builder .setTitle(title) + .setTitleItem(icon) .setSubtitle(summary) .setPrimaryAction(primaryAction)) .setKeywords(keywords) diff --git a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java index a554e74352f..cec8545bfb7 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.PersistableBundle; +import android.provider.Settings; import android.support.v4.graphics.drawable.IconCompat; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; @@ -121,7 +122,7 @@ public class WifiCallingSliceHelper { return getNonActionableWifiCallingSlice( mContext.getString(R.string.wifi_calling_settings_title), mContext.getString(R.string.wifi_calling_not_supported, carrierName), - sliceUri, SliceBuilderUtils.getSettingsIntent(mContext)); + sliceUri, getSettingsIntent(mContext)); } final ImsManager imsManager = getImsManager(subId); @@ -132,7 +133,7 @@ public class WifiCallingSliceHelper { return getNonActionableWifiCallingSlice( mContext.getString(R.string.wifi_calling_settings_title), mContext.getString(R.string.wifi_calling_not_supported, carrierName), - sliceUri, SliceBuilderUtils.getSettingsIntent(mContext)); + sliceUri, getSettingsIntent(mContext)); } try { @@ -338,6 +339,14 @@ public class WifiCallingSliceHelper { return intent; } + /** + * @return {@link PendingIntent} to the Settings home page. + */ + public static PendingIntent getSettingsIntent(Context context) { + final Intent intent = new Intent(Settings.ACTION_SETTINGS); + return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */); + } + private PendingIntent getBroadcastIntent(String action) { final Intent intent = new Intent(action); intent.setClass(mContext, SliceBroadcastReceiver.class); diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index 00e8fe17d08..96e475e9fbe 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -336,7 +336,7 @@ public class SliceBuilderUtilsTest { final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - SliceTester.testSettingsUnavailableSlice(mContext, slice, data); + assertThat(slice).isNull(); } @Test @@ -349,7 +349,7 @@ public class SliceBuilderUtilsTest { final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - SliceTester.testSettingsUnavailableSlice(mContext, slice, data); + assertThat(slice).isNull(); } @Test @@ -394,7 +394,7 @@ public class SliceBuilderUtilsTest { .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(capturedLoggingPair.second) .isEqualTo(data.getKey()); - SliceTester.testSettingsUnavailableSlice(mContext, slice, data); + assertThat(slice).isNull(); } @Test @@ -408,16 +408,6 @@ public class SliceBuilderUtilsTest { assertThat(intentData).isEqualTo(expectedUri); } - @Test - public void getSettingsIntent_createsIntentToSettings() { - final Intent intent = new Intent(Settings.ACTION_SETTINGS); - final PendingIntent expectedIntent = PendingIntent.getActivity(mContext, 0, intent, 0); - - final PendingIntent settingsIntent = SliceBuilderUtils.getSettingsIntent(mContext); - - assertThat(expectedIntent).isEqualTo(settingsIntent); - } - private SliceData getDummyData() { return getDummyData(TOGGLE_CONTROLLER, SUMMARY, SliceData.SliceType.SWITCH, SCREEN_TITLE); } diff --git a/tests/robotests/src/com/android/settings/testutils/SliceTester.java b/tests/robotests/src/com/android/settings/testutils/SliceTester.java index f617aa95c8f..6653d00c9d8 100644 --- a/tests/robotests/src/com/android/settings/testutils/SliceTester.java +++ b/tests/robotests/src/com/android/settings/testutils/SliceTester.java @@ -191,20 +191,8 @@ public class SliceTester { assertThat(toggles).isEmpty(); final PendingIntent primaryPendingIntent = metadata.getPrimaryAction().getAction(); - final int availabilityStatus = SliceBuilderUtils.getPreferenceController(context, - sliceData).getAvailabilityStatus(); - switch (availabilityStatus) { - case UNSUPPORTED_ON_DEVICE: - case CONDITIONALLY_UNAVAILABLE: - assertThat(primaryPendingIntent).isEqualTo( - SliceBuilderUtils.getSettingsIntent(context)); - break; - case DISABLED_FOR_USER: - case DISABLED_DEPENDENT_SETTING: - assertThat(primaryPendingIntent).isEqualTo( - SliceBuilderUtils.getContentPendingIntent(context, sliceData)); - break; - } + assertThat(primaryPendingIntent).isEqualTo(SliceBuilderUtils.getContentPendingIntent( + context, sliceData)); final List sliceItems = slice.getItems(); assertTitle(sliceItems, sliceData.getTitle()); diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java index ac3ff3ff635..21f6daa3428 100644 --- a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java @@ -114,7 +114,7 @@ public class WifiCallingSliceHelperTest { final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); testWifiCallingSettingsUnavailableSlice(slice, null, - SliceBuilderUtils.getSettingsIntent(mContext)); + WifiCallingSliceHelper.getSettingsIntent(mContext)); } @Test @@ -125,7 +125,7 @@ public class WifiCallingSliceHelperTest { assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); testWifiCallingSettingsUnavailableSlice(slice, null, - SliceBuilderUtils.getSettingsIntent(mContext)); + WifiCallingSliceHelper.getSettingsIntent(mContext)); } @Test From 77af7afd048b1c0c16ad664abca09973544d5681 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Tue, 22 May 2018 16:25:40 -0700 Subject: [PATCH 03/18] Updated misc. settings icon This isn't the main app icon, but rather a 24dp version used in a few miscellaneous places in the app. This also removes the outdated inverse version of the previous icon since the new version now works in either context. Bug: 77982107 Test: manual Change-Id: Ibd5928895ac1aba52b64a853c4ccd806a2f1de70 --- res/drawable/ic_settings_24dp.xml | 7 +++-- res/drawable/ic_settings_24dp_inverse.xml | 37 ----------------------- res/layout/apps_filter_spinner.xml | 2 +- 3 files changed, 6 insertions(+), 40 deletions(-) delete mode 100644 res/drawable/ic_settings_24dp_inverse.xml diff --git a/res/drawable/ic_settings_24dp.xml b/res/drawable/ic_settings_24dp.xml index da343457cad..ac4c43bd35b 100644 --- a/res/drawable/ic_settings_24dp.xml +++ b/res/drawable/ic_settings_24dp.xml @@ -21,6 +21,9 @@ android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal"> + android:fillColor="#FFFFFFFF" + android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/> + diff --git a/res/drawable/ic_settings_24dp_inverse.xml b/res/drawable/ic_settings_24dp_inverse.xml deleted file mode 100644 index 6f70d21e256..00000000000 --- a/res/drawable/ic_settings_24dp_inverse.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/layout/apps_filter_spinner.xml b/res/layout/apps_filter_spinner.xml index 3eebbb02f51..3faff507a2b 100644 --- a/res/layout/apps_filter_spinner.xml +++ b/res/layout/apps_filter_spinner.xml @@ -39,7 +39,7 @@ android:layout_height="56dp" android:contentDescription="@string/configure" android:scaleType="center" - android:src="@drawable/ic_settings_24dp_inverse" + android:src="@drawable/ic_settings_24dp" android:visibility="gone" /> From 48668576e44e4e557547df4c1fec4c29e070b18f Mon Sep 17 00:00:00 2001 From: Chienyuan Date: Wed, 23 May 2018 19:29:58 +0800 Subject: [PATCH 04/18] Set default value of A2DP HW offload toggle When developer options is disabled, A2DP HW offload toggle will switch to default value, we should decide default value according to A2DP HW offload is supported of not. Bug: 63932139 Bug: 79568680 Test: robotests Change-Id: I3bf941edd3c0e0f70cfba32dc856e3f14fee07fa --- .../BluetoothA2dpHwOffloadPreferenceController.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java index 553a63e5954..01ec156e754 100644 --- a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java +++ b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java @@ -69,8 +69,15 @@ public class BluetoothA2dpHwOffloadPreferenceController extends DeveloperOptions @Override protected void onDeveloperOptionsSwitchDisabled() { super.onDeveloperOptionsSwitchDisabled(); - ((SwitchPreference) mPreference).setChecked(true); - SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, "true"); + final boolean offloadSupported = + SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false); + if (offloadSupported) { + ((SwitchPreference) mPreference).setChecked(false); + SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, "false"); + } else { + ((SwitchPreference) mPreference).setChecked(true); + SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, "true"); + } } public void onA2dpHwDialogConfirmed() { From db03de4e80dd311ecd16bbda8136350733a31024 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 22 May 2018 14:12:32 -0700 Subject: [PATCH 05/18] Convert BatterySaverButton controller to Slice compatible. - Update preference key to match the key defined in SettingsSlicesContract - Model TwoStateButtonPreference similar to TwoStatePreference (add setChecked, isChecked method) - Remove TwoStateButtonPreferenceController entirely because all methods are moved into Preference directly for better encapsulation. - Make BatterySaverButtonPrefController direclty implement TogglePreferenceController. It was not possible before because the interface between TwoStateButtonPreferene is too different from TwoStatePreference. Bug: 80106671 Test: robotests Change-Id: Ib72807dcf1b36e959e08df8d80538c3f9f79b76d Merged-In: Ib72807dcf1b36e959e08df8d80538c3f9f79b76d --- res/xml/battery_saver_settings.xml | 15 ++-- ...atterySaverButtonPreferenceController.java | 76 +++++++++++------- .../batterysaver/BatterySaverSettings.java | 1 - .../widget/TwoStateButtonPreference.java | 62 ++++++++++++--- .../TwoStateButtonPreferenceController.java | 78 ------------------- ...rySaverButtonPreferenceControllerTest.java | 44 ++++------- ...java => TwoStateButtonPreferenceTest.java} | 72 +++++++---------- 7 files changed, 153 insertions(+), 195 deletions(-) delete mode 100644 src/com/android/settings/widget/TwoStateButtonPreferenceController.java rename tests/robotests/src/com/android/settings/widget/{TwoStateButtonPreferenceControllerTest.java => TwoStateButtonPreferenceTest.java} (60%) diff --git a/res/xml/battery_saver_settings.xml b/res/xml/battery_saver_settings.xml index 116079d5247..0460459d961 100644 --- a/res/xml/battery_saver_settings.xml +++ b/res/xml/battery_saver_settings.xml @@ -18,32 +18,35 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/battery_saver" - android:key="battery_saver"> + android:key="battery_saver_page"> + settings:controller="com.android.settings.fuelgauge.batterysaver.AutoBatterySaverPreferenceController" /> + android:min="5" /> + settings:textOff="@string/battery_saver_button_turn_off" + settings:platform_slice="true" + settings:controller="com.android.settings.fuelgauge.batterysaver.BatterySaverButtonPreferenceController" /> + android:selectable="false" /> diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java index b23a5f08a70..9485868d20c 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java @@ -18,12 +18,12 @@ package com.android.settings.fuelgauge.batterysaver; import android.content.Context; import android.os.PowerManager; -import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import com.android.settings.core.TogglePreferenceController; import com.android.settings.fuelgauge.BatterySaverReceiver; -import com.android.settings.widget.TwoStateButtonPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settings.widget.TwoStateButtonPreference; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; @@ -33,21 +33,29 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils; * Controller to update the battery saver button */ public class BatterySaverButtonPreferenceController extends - TwoStateButtonPreferenceController implements + TogglePreferenceController implements LifecycleObserver, OnStart, OnStop, BatterySaverReceiver.BatterySaverListener { - private static final String KEY = "battery_saver_button_container"; - private BatterySaverReceiver mBatterySaverReceiver; - @VisibleForTesting - PowerManager mPowerManager; - public BatterySaverButtonPreferenceController(Context context, Lifecycle lifecycle) { - super(context, KEY); + private final BatterySaverReceiver mBatterySaverReceiver; + private final PowerManager mPowerManager; + + private TwoStateButtonPreference mPreference; + + public BatterySaverButtonPreferenceController(Context context, String key) { + super(context, key); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mBatterySaverReceiver = new BatterySaverReceiver(context); mBatterySaverReceiver.setBatterySaverListener(this); - if (lifecycle != null) { - lifecycle.addObserver(this); - } + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isSliceable() { + return true; } @Override @@ -60,30 +68,44 @@ public class BatterySaverButtonPreferenceController extends mBatterySaverReceiver.setListening(false); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = (TwoStateButtonPreference) screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean isChecked() { + return mPowerManager.isPowerSaveMode(); + } + + @Override + public boolean setChecked(boolean stateOn) { + // This screen already shows a warning, so we don't need another warning. + return BatterySaverUtils.setPowerSaveMode(mContext, stateOn, + false /* needFirstTimeWarning */); + } + @Override public void updateState(Preference preference) { super.updateState(preference); - setButtonVisibility(!mPowerManager.isPowerSaveMode()); - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } - - @Override - public void onButtonClicked(boolean stateOn) { - // This screen already shows a warning, so we don't need another warning. - BatterySaverUtils.setPowerSaveMode(mContext, stateOn, /*needFirstTimeWarning*/ false); + if (mPreference != null) { + mPreference.setChecked(isChecked()); + } } @Override public void onPowerSaveModeChanged() { - setButtonVisibility(!mPowerManager.isPowerSaveMode()); + final boolean isChecked = isChecked(); + if (mPreference != null && mPreference.isChecked() != isChecked) { + mPreference.setChecked(isChecked); + } } @Override public void onBatteryChanged(boolean pluggedIn) { - setButtonEnabled(!pluggedIn); + if (mPreference != null) { + mPreference.setButtonEnabled(!pluggedIn); + } } } diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java index 8009e954e38..26f9e171212 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java @@ -73,7 +73,6 @@ public class BatterySaverSettings extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new AutoBatterySaverPreferenceController(context)); controllers.add(new AutoBatterySeekBarPreferenceController(context, lifecycle)); - controllers.add(new BatterySaverButtonPreferenceController(context, lifecycle)); return controllers; } diff --git a/src/com/android/settings/widget/TwoStateButtonPreference.java b/src/com/android/settings/widget/TwoStateButtonPreference.java index 6b5fbbb0e11..78d48747b26 100644 --- a/src/com/android/settings/widget/TwoStateButtonPreference.java +++ b/src/com/android/settings/widget/TwoStateButtonPreference.java @@ -18,8 +18,10 @@ package com.android.settings.widget; import android.content.Context; import android.content.res.TypedArray; +import android.support.annotation.VisibleForTesting; import android.support.v4.content.res.TypedArrayUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.Button; import com.android.settings.R; @@ -28,12 +30,21 @@ import com.android.settings.applications.LayoutPreference; /** * Preference that presents a button with two states(On vs Off) */ -public class TwoStateButtonPreference extends LayoutPreference { +public class TwoStateButtonPreference extends LayoutPreference implements + View.OnClickListener { + + private boolean mIsChecked; + private final Button mButtonOn; + private final Button mButtonOff; + public TwoStateButtonPreference(Context context, AttributeSet attrs) { super(context, attrs, TypedArrayUtils.getAttr( context, R.attr.twoStateButtonPreferenceStyle, android.R.attr.preferenceStyle)); - if (attrs != null) { + if (attrs == null) { + mButtonOn = null; + mButtonOff = null; + } else { final TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.TwoStateButtonPreference); final int textOnId = styledAttrs.getResourceId( @@ -44,19 +55,52 @@ public class TwoStateButtonPreference extends LayoutPreference { R.string.summary_placeholder); styledAttrs.recycle(); - final Button buttonOn = getStateOnButton(); - buttonOn.setText(textOnId); - final Button buttonOff = getStateOffButton(); - buttonOff.setText(textOffId); + mButtonOn = findViewById(R.id.state_on_button); + mButtonOn.setText(textOnId); + mButtonOn.setOnClickListener(this); + mButtonOff = findViewById(R.id.state_off_button); + mButtonOff.setText(textOffId); + mButtonOff.setOnClickListener(this); + setChecked(isChecked()); } } - public Button getStateOnButton() { - return findViewById(R.id.state_on_button); + @Override + public void onClick(View v) { + final boolean stateOn = v.getId() == R.id.state_on_button; + setChecked(stateOn); + callChangeListener(stateOn); } + public void setChecked(boolean checked) { + // Update state + mIsChecked = checked; + // And update UI + if (checked) { + mButtonOn.setVisibility(View.GONE); + mButtonOff.setVisibility(View.VISIBLE); + } else { + mButtonOn.setVisibility(View.VISIBLE); + mButtonOff.setVisibility(View.GONE); + } + } + public boolean isChecked() { + return mIsChecked; + } + + public void setButtonEnabled(boolean enabled) { + mButtonOn.setEnabled(enabled); + mButtonOff.setEnabled(enabled); + } + + @VisibleForTesting + public Button getStateOnButton() { + return mButtonOn; + } + + @VisibleForTesting public Button getStateOffButton() { - return findViewById(R.id.state_off_button); + return mButtonOff; } } \ No newline at end of file diff --git a/src/com/android/settings/widget/TwoStateButtonPreferenceController.java b/src/com/android/settings/widget/TwoStateButtonPreferenceController.java deleted file mode 100644 index 47699ef0a7b..00000000000 --- a/src/com/android/settings/widget/TwoStateButtonPreferenceController.java +++ /dev/null @@ -1,78 +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.widget; - -import android.content.Context; -import android.support.v7.preference.PreferenceScreen; -import android.view.View; -import android.widget.Button; - -import com.android.settings.R; -import com.android.settings.core.BasePreferenceController; - -/** - * Controller to update the button with two states(On vs Off). - */ -public abstract class TwoStateButtonPreferenceController extends BasePreferenceController - implements View.OnClickListener { - private Button mButtonOn; - private Button mButtonOff; - - public TwoStateButtonPreferenceController(Context context, String key) { - super(context, key); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - final TwoStateButtonPreference preference = - (TwoStateButtonPreference) screen.findPreference(getPreferenceKey()); - mButtonOn = preference.getStateOnButton(); - mButtonOn.setOnClickListener(this); - mButtonOff = preference.getStateOffButton(); - mButtonOff.setOnClickListener(this); - } - - protected void setButtonVisibility(boolean stateOn) { - if (stateOn) { - mButtonOff.setVisibility(View.GONE); - mButtonOn.setVisibility(View.VISIBLE); - } else { - mButtonOff.setVisibility(View.VISIBLE); - mButtonOn.setVisibility(View.GONE); - } - } - - protected void setButtonEnabled(boolean enabled) { - mButtonOn.setEnabled(enabled); - mButtonOff.setEnabled(enabled); - } - - @Override - public void onClick(View v) { - final boolean stateOn = v.getId() == R.id.state_on_button; - onButtonClicked(stateOn); - } - - /** - * Callback when button is clicked - * - * @param stateOn {@code true} if stateOn button is clicked, otherwise it means stateOff - * button is clicked - */ - public abstract void onButtonClicked(boolean stateOn); -} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java index 14f6533f0f3..641a15f4d13 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java @@ -21,17 +21,13 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.os.PowerManager; import android.support.v7.preference.PreferenceScreen; -import android.view.View; import android.widget.Button; -import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.widget.TwoStateButtonPreference; -import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Test; @@ -41,6 +37,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowPowerManager; +import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) @Config(shadows = ShadowPowerManager.class) @@ -48,67 +45,58 @@ public class BatterySaverButtonPreferenceControllerTest { private BatterySaverButtonPreferenceController mController; private Context mContext; - private Lifecycle mLifecycle; - private LifecycleOwner mLifecycleOwner; private Button mButtonOn; private Button mButtonOff; private PowerManager mPowerManager; - @Mock private TwoStateButtonPreference mPreference; + @Mock private PreferenceScreen mPreferenceScreen; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); mContext = spy(RuntimeEnvironment.application); + mButtonOn = new Button(mContext); + mButtonOff = new Button(mContext); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPreference = spy(new TwoStateButtonPreference(mContext, null /* AttributeSet */)); + ReflectionHelpers.setField(mPreference, "mButtonOn", mButtonOn); + ReflectionHelpers.setField(mPreference, "mButtonOff", mButtonOff); doReturn(mPreference).when(mPreferenceScreen).findPreference(anyString()); - mButtonOn = new Button(mContext); - mButtonOn.setId(R.id.state_on_button); - doReturn(mButtonOn).when(mPreference).getStateOnButton(); - mButtonOff = new Button(mContext); - mButtonOff.setId(R.id.state_off_button); - doReturn(mButtonOff).when(mPreference).getStateOffButton(); - - mController = new BatterySaverButtonPreferenceController(mContext, mLifecycle); + mController = new BatterySaverButtonPreferenceController(mContext, "test_key"); mController.displayPreference(mPreferenceScreen); } @Test - public void testUpdateState_lowPowerOn_displayButtonOff() { + public void updateState_lowPowerOn_preferenceIsChecked() { mPowerManager.setPowerSaveMode(true); mController.updateState(mPreference); - assertThat(mButtonOn.getVisibility()).isEqualTo(View.GONE); - assertThat(mButtonOff.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mPreference.isChecked()).isTrue(); } @Test - public void testUpdateState_lowPowerOff_displayButtonOn() { + public void testUpdateState_lowPowerOff_preferenceIsUnchecked() { mPowerManager.setPowerSaveMode(false); mController.updateState(mPreference); - assertThat(mButtonOn.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mButtonOff.getVisibility()).isEqualTo(View.GONE); + assertThat(mPreference.isChecked()).isFalse(); } @Test - public void testOnClick_clickButtonOn_setPowerSaveMode() { - mController.onClick(mButtonOn); + public void setChecked_on_setPowerSaveMode() { + mController.setChecked(true); assertThat(mPowerManager.isPowerSaveMode()).isTrue(); } @Test - public void testOnClick_clickButtonOff_clearPowerSaveMode() { - mController.onClick(mButtonOff); + public void setChecked_off_unsetPowerSaveMode() { + mController.setChecked(false); assertThat(mPowerManager.isPowerSaveMode()).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceTest.java similarity index 60% rename from tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceTest.java index d6df25df47f..c88e7f87d00 100644 --- a/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceTest.java @@ -17,34 +17,27 @@ package com.android.settings.widget; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import android.content.Context; -import android.support.v7.preference.PreferenceScreen; import android.view.View; import android.widget.Button; +import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) -public class TwoStateButtonPreferenceControllerTest { +public class TwoStateButtonPreferenceTest { - private static final String KEY = "pref_key"; - - @Mock - private PreferenceScreen mPreferenceScreen; - @Mock private TwoStateButtonPreference mPreference; - private TwoStateButtonPreferenceController mController; private Context mContext; private Button mButtonOn; private Button mButtonOff; @@ -53,35 +46,34 @@ public class TwoStateButtonPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - doReturn(mPreference).when(mPreferenceScreen).findPreference(anyString()); + mPreference = spy(new TwoStateButtonPreference(mContext, null /* AttributeSet */)); mButtonOn = new Button(mContext); - doReturn(mButtonOn).when(mPreference).getStateOnButton(); + mButtonOn.setId(R.id.state_on_button); mButtonOff = new Button(mContext); - doReturn(mButtonOff).when(mPreference).getStateOffButton(); - - mController = new TestButtonsPreferenceController(mContext, KEY); - mController.displayPreference(mPreferenceScreen); + mButtonOff.setId(R.id.state_off_button); + ReflectionHelpers.setField(mPreference, "mButtonOn", mButtonOn); + ReflectionHelpers.setField(mPreference, "mButtonOff", mButtonOff); } @Test public void testSetButtonVisibility_stateOn_onlyShowButtonOn() { - mController.setButtonVisibility(true /* stateOn */); - - assertThat(mButtonOn.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mButtonOff.getVisibility()).isEqualTo(View.GONE); - } - - @Test - public void testSetButtonVisibility_stateOff_onlyShowButtonOff() { - mController.setButtonVisibility(false /* stateOn */); + mPreference.setChecked(true /* stateOn */); assertThat(mButtonOn.getVisibility()).isEqualTo(View.GONE); assertThat(mButtonOff.getVisibility()).isEqualTo(View.VISIBLE); } + @Test + public void testSetButtonVisibility_stateOff_onlyShowButtonOff() { + mPreference.setChecked(false /* stateOn */); + + assertThat(mButtonOn.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mButtonOff.getVisibility()).isEqualTo(View.GONE); + } + @Test public void testSetButtonEnabled_enabled_buttonEnabled() { - mController.setButtonEnabled(true /* enabled */); + mPreference.setButtonEnabled(true /* enabled */); assertThat(mButtonOn.isEnabled()).isTrue(); assertThat(mButtonOff.isEnabled()).isTrue(); @@ -89,30 +81,18 @@ public class TwoStateButtonPreferenceControllerTest { @Test public void testSetButtonEnabled_disabled_buttonDisabled() { - mController.setButtonEnabled(false /* enabled */); + mPreference.setButtonEnabled(false /* enabled */); assertThat(mButtonOn.isEnabled()).isFalse(); assertThat(mButtonOff.isEnabled()).isFalse(); } - /** - * Controller to test methods in {@link TwoStateButtonPreferenceController} - */ - public static class TestButtonsPreferenceController - extends TwoStateButtonPreferenceController { + @Test + public void onClick_shouldPropagateChangeToListener() { + mPreference.onClick(mButtonOn); + verify(mPreference).callChangeListener(true); - TestButtonsPreferenceController(Context context, String key) { - super(context, key); - } - - @Override - public void onButtonClicked(boolean stateOn) { - //do nothing - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } + mPreference.onClick(mButtonOff); + verify(mPreference).callChangeListener(false); } } From e31e60ce94e5af2c08427173a256ee6be09ba790 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Fri, 18 May 2018 10:23:34 -0700 Subject: [PATCH 06/18] Add Location Slice Location is an intent-only Slice. Test: Robotests Change-Id: Ie9ed05be2224f2c4b393ed201d5f313f80183edc Merged-In: I07e27683b46fe4ded8215009a983bb909555fb59 Fixes: 67997314 --- .../bluetooth/BluetoothSliceBuilder.java | 2 + .../location/LocationSliceBuilder.java | 94 +++++++++++++++++++ .../notification/ZenModeSliceBuilder.java | 2 + .../slices/SettingsSliceProvider.java | 15 ++- .../settings/wifi/WifiSliceBuilder.java | 2 + .../location/LocationSliceBuilderTest.java | 65 +++++++++++++ .../slices/SettingsSliceProviderTest.java | 17 +++- .../settings/wifi/WifiSliceBuilderTest.java | 1 - 8 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 src/com/android/settings/location/LocationSliceBuilder.java create mode 100644 tests/robotests/src/com/android/settings/location/LocationSliceBuilderTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java index e6452def3d7..da759a33e5e 100644 --- a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java +++ b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java @@ -17,6 +17,8 @@ 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; diff --git a/src/com/android/settings/location/LocationSliceBuilder.java b/src/com/android/settings/location/LocationSliceBuilder.java new file mode 100644 index 00000000000..e69ccfba54c --- /dev/null +++ b/src/com/android/settings/location/LocationSliceBuilder.java @@ -0,0 +1,94 @@ +/* + * 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.location; + +import static android.provider.SettingsSlicesContract.KEY_LOCATION; + +import static androidx.slice.builders.ListBuilder.ICON_IMAGE; + +import android.annotation.ColorInt; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.provider.SettingsSlicesContract; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SubSettings; +import com.android.settings.Utils; +import com.android.settings.search.DatabaseIndexingUtils; + +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import android.support.v4.graphics.drawable.IconCompat; + +/** + * Utility class to build an intent-based Location Slice. + */ +public class LocationSliceBuilder { + + /** + * Backing Uri for the Location Slice. + */ + public static final Uri LOCATION_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSlicesContract.AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(KEY_LOCATION) + .build(); + + private LocationSliceBuilder() { + } + + /** + * Return a Location Slice bound to {@link #LOCATION_URI}. + */ + public static Slice getSlice(Context context) { + final IconCompat icon = IconCompat.createWithResource(context, + R.drawable.ic_signal_location); + final String title = context.getString(R.string.location_settings_title); + @ColorInt final int color = Utils.getColorAccent(context); + final PendingIntent primaryAction = getPrimaryAction(context); + final SliceAction primarySliceAction = new SliceAction(primaryAction, icon, title); + + return new ListBuilder(context, LOCATION_URI, ListBuilder.INFINITY) + .setAccentColor(color) + .addRow(b -> b + .setTitle(title) + .setTitleItem(icon, ICON_IMAGE) + .setPrimaryAction(primarySliceAction)) + .build(); + } + + private static PendingIntent getPrimaryAction(Context context) { + final String screenTitle = context.getText(R.string.location_settings_title).toString(); + final Uri contentUri = new Uri.Builder().appendPath(KEY_LOCATION).build(); + final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context, + LocationSettings.class.getName(), KEY_LOCATION, screenTitle, + MetricsEvent.LOCATION) + .setClassName(context.getPackageName(), SubSettings.class.getName()) + .setData(contentUri); + + return PendingIntent.getActivity(context, 0 /* requestCode */, + intent, 0 /* flags */); + } +} diff --git a/src/com/android/settings/notification/ZenModeSliceBuilder.java b/src/com/android/settings/notification/ZenModeSliceBuilder.java index 0edf214179c..aedd0b3e4c3 100644 --- a/src/com/android/settings/notification/ZenModeSliceBuilder.java +++ b/src/com/android/settings/notification/ZenModeSliceBuilder.java @@ -18,6 +18,8 @@ package com.android.settings.notification; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; +import static androidx.slice.builders.ListBuilder.ICON_IMAGE; + import android.annotation.ColorInt; import android.app.NotificationManager; import android.app.PendingIntent; diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 3ed6185563c..557ecad5428 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -33,6 +33,7 @@ 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.core.BasePreferenceController; import com.android.settings.wifi.WifiSliceBuilder; @@ -188,6 +189,8 @@ public class SettingsSliceProvider extends SliceProvider { 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); @@ -289,10 +292,17 @@ public class SettingsSliceProvider extends SliceProvider { void loadSlice(Uri uri) { long startBuildTime = System.currentTimeMillis(); - final SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); + final SliceData sliceData; + try { + sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); + } catch (IllegalStateException e) { + Log.e(TAG, "Could not get slice data for uri: " + uri, e); + return; + } final BasePreferenceController controller = SliceBuilderUtils.getPreferenceController( getContext(), sliceData); + final IntentFilter filter = controller.getIntentFilter(); if (filter != null) { registerIntentToUri(filter, uri); @@ -336,7 +346,8 @@ public class SettingsSliceProvider extends SliceProvider { private List getSpecialCasePlatformUris() { return Arrays.asList( WifiSliceBuilder.WIFI_URI, - BluetoothSliceBuilder.BLUETOOTH_URI + BluetoothSliceBuilder.BLUETOOTH_URI, + LocationSliceBuilder.LOCATION_URI ); } diff --git a/src/com/android/settings/wifi/WifiSliceBuilder.java b/src/com/android/settings/wifi/WifiSliceBuilder.java index 96d1b82b86a..7df7f19ad78 100644 --- a/src/com/android/settings/wifi/WifiSliceBuilder.java +++ b/src/com/android/settings/wifi/WifiSliceBuilder.java @@ -19,6 +19,8 @@ package com.android.settings.wifi; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; import static android.provider.SettingsSlicesContract.KEY_WIFI; +import static androidx.slice.builders.ListBuilder.ICON_IMAGE; + import android.annotation.ColorInt; import android.app.PendingIntent; import android.content.ContentResolver; diff --git a/tests/robotests/src/com/android/settings/location/LocationSliceBuilderTest.java b/tests/robotests/src/com/android/settings/location/LocationSliceBuilderTest.java new file mode 100644 index 00000000000..22928bfee95 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationSliceBuilderTest.java @@ -0,0 +1,65 @@ +package com.android.settings.location; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.res.Resources; +import android.support.v4.graphics.drawable.IconCompat; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.SliceTester; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.SliceMetadata; +import androidx.slice.SliceProvider; +import androidx.slice.core.SliceAction; +import androidx.slice.widget.SliceLiveData; + +@RunWith(SettingsRobolectricTestRunner.class) +public class LocationSliceBuilderTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + + // Prevent crash in SliceMetadata. + Resources resources = spy(mContext.getResources()); + doReturn(60).when(resources).getDimensionPixelSize(anyInt()); + doReturn(resources).when(mContext).getResources(); + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + } + + @Test + public void getLocationSlice_correctSliceContent() { + final Slice LocationSlice = LocationSliceBuilder.getSlice(mContext); + final SliceMetadata metadata = SliceMetadata.from(mContext, LocationSlice); + + final List toggles = metadata.getToggles(); + assertThat(toggles).isEmpty(); + + final SliceAction primaryAction = metadata.getPrimaryAction(); + final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext, + R.drawable.ic_signal_location); + assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString()); + + final List sliceItems = LocationSlice.getItems(); + SliceTester.assertTitle(sliceItems, mContext.getString(R.string.location_settings_title)); + } +} diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index 722f481d10f..bb064d1f915 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.slice.SliceManager; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; @@ -35,6 +36,7 @@ import android.net.Uri; import android.os.StrictMode; import android.provider.SettingsSlicesContract; +import com.android.settings.location.LocationSliceBuilder; import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.bluetooth.BluetoothSliceBuilder; import com.android.settings.notification.ZenModeSliceBuilder; @@ -81,7 +83,8 @@ public class SettingsSliceProviderTest { private static final List SPECIAL_CASE_PLATFORM_URIS = Arrays.asList( WifiSliceBuilder.WIFI_URI, - BluetoothSliceBuilder.BLUETOOTH_URI + BluetoothSliceBuilder.BLUETOOTH_URI, + LocationSliceBuilder.LOCATION_URI ); private static final List SPECIAL_CASE_OEM_URIS = Arrays.asList( @@ -401,6 +404,18 @@ public class SettingsSliceProviderTest { assertThat(wifiSlice.getUri()).isEqualTo(WifiSliceBuilder.WIFI_URI); } + @Test + public void onSlicePinned_noIntentRegistered_specialCaseUri_doesNotCrash() { + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSlicesContract.AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(SettingsSlicesContract.KEY_LOCATION) + .build(); + + mProvider.onSlicePinned(uri); + } + private void insertSpecialCase(String key) { insertSpecialCase(key, true); } diff --git a/tests/robotests/src/com/android/settings/wifi/WifiSliceBuilderTest.java b/tests/robotests/src/com/android/settings/wifi/WifiSliceBuilderTest.java index 865785fc745..605a661a307 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiSliceBuilderTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiSliceBuilderTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.spy; import android.content.Context; import com.android.settings.R; -import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SliceTester; From 9555afb64e8bc5573b996c33a6a5dc242f2c1390 Mon Sep 17 00:00:00 2001 From: Daichi Ueura Date: Tue, 22 May 2018 18:36:59 +0900 Subject: [PATCH 07/18] Hide entire hidden field for non-hidden networks This CL hides not just hidden network spinner but entire hidden field when a user is adding a network from scanned network list. Test: robotests Bug: 80114851 Change-Id: I8b29fd764d62af1e46beaa7d26fae97848f4efe6 --- res/layout/wifi_dialog.xml | 1 + .../settings/wifi/WifiConfigController.java | 5 +---- .../wifi/WifiConfigControllerTest.java | 21 ++++--------------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/res/layout/wifi_dialog.xml b/res/layout/wifi_dialog.xml index fe891e1f830..233072745b8 100644 --- a/res/layout/wifi_dialog.xml +++ b/res/layout/wifi_dialog.xml @@ -604,6 +604,7 @@