From a4a3dfffa20e317f4236349e4717f9da312b8ee1 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Wed, 28 Feb 2018 08:15:55 -0800 Subject: [PATCH] Declare official platform slice Create the notion of an official platform slice. This includes: - Adding a second authority to the provider - tagging slices in xml with a platform slice flag - Including authority in the getUri method Bug:73359139 Test: robotests Change-Id: I5382be138a262dbc5a8324c34aab131c5d0d5516 Merged-In: I581ee6dfcdf935f452a15e89e5d055e375ff1877 --- AndroidManifest.xml | 2 +- res/values/attrs.xml | 4 + res/xml/connected_devices_advanced.xml | 3 +- .../settings/core/PreferenceXmlParserUtils.java | 90 ++++++++++++++++++---- .../settings/slices/SettingsSliceProvider.java | 15 ++-- .../settings/slices/SliceBroadcastReceiver.java | 3 +- .../android/settings/slices/SliceBuilderUtils.java | 14 ++++ src/com/android/settings/slices/SliceData.java | 14 ++++ .../settings/slices/SliceDataConverter.java | 6 +- .../robotests/res/xml-mcc999/location_settings.xml | 3 +- .../settings/slices/SettingsSliceProviderTest.java | 31 +++----- .../settings/slices/SliceBuilderUtilsTest.java | 57 ++++++++++++++ .../settings/slices/SliceDataConverterTest.java | 3 +- .../com/android/settings/slices/SliceDataTest.java | 5 +- .../slices/SlicesDatabaseAccessorTest.java | 4 +- 15 files changed, 201 insertions(+), 53 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dbe231b525..f1bb5954ef 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3218,7 +3218,7 @@ diff --git a/res/values/attrs.xml b/res/values/attrs.xml index d37091c594..d0ddab6ffe 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -96,8 +96,12 @@ + + + + diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml index 36eab22975..dda5655f08 100644 --- a/res/xml/connected_devices_advanced.xml +++ b/res/xml/connected_devices_advanced.xml @@ -26,7 +26,8 @@ android:icon="@drawable/ic_settings_bluetooth" android:summary="@string/bluetooth_pref_summary" android:order="-7" - settings:controller="com.android.settings.bluetooth.BluetoothSwitchPreferenceController"/> + settings:controller="com.android.settings.bluetooth.BluetoothSwitchPreferenceController" + settings:platform_slice="true"/> outerDepth)); parser.close(); @@ -205,12 +239,16 @@ public class PreferenceXmlParserUtils { * Returns the fragment name if this preference launches a child fragment. */ public static String getDataChildFragment(Context context, AttributeSet attrs) { - return getData(context, attrs, R.styleable.Preference, + return getStringData(context, attrs, R.styleable.Preference, R.styleable.Preference_android_fragment); } + /** + * Call {@link #extractMetadata(Context, int, int)} with a {@link MetadataFlag} instead. + */ + @Deprecated @Nullable - private static String getData(Context context, AttributeSet set, int[] attrs, int resId) { + private static String getStringData(Context context, AttributeSet set, int[] attrs, int resId) { final TypedArray ta = context.obtainStyledAttributes(set, attrs); String data = ta.getString(resId); ta.recycle(); @@ -243,4 +281,28 @@ public class PreferenceXmlParserUtils { } return result.toString(); } + + private static String getKey(TypedArray styledAttributes) { + return styledAttributes.getString(com.android.internal.R.styleable.Preference_key); + } + + private static String getTitle(TypedArray styledAttributes) { + return styledAttributes.getString(com.android.internal.R.styleable.Preference_title); + } + + private static String getSummary(TypedArray styledAttributes) { + return styledAttributes.getString(com.android.internal.R.styleable.Preference_summary); + } + + private static String getController(TypedArray styledAttributes) { + return styledAttributes.getString(R.styleable.Preference_controller); + } + + private static int getIcon(TypedArray styledAttributes) { + return styledAttributes.getResourceId(com.android.internal.R.styleable.Icon_icon, 0); + } + + private static boolean getPlatformSlice(TypedArray styledAttributes) { + return styledAttributes.getBoolean(R.styleable.Preference_platform_slice, false /* def */); + } } diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index d8ba991a14..68c9731d8c 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.graphics.drawable.Icon; import android.net.Uri; import android.net.wifi.WifiManager; +import android.provider.SettingsSlicesContract; import android.support.annotation.VisibleForTesting; import android.util.Log; @@ -63,6 +64,10 @@ public class SettingsSliceProvider extends SliceProvider { private static final String TAG = "SettingsSliceProvider"; + /** + * Authority for Settings slices not officially supported by the platform, but extensible for + * OEMs. + */ public static final String SLICE_AUTHORITY = "com.android.settings.slices"; public static final String PATH_WIFI = "wifi"; @@ -82,13 +87,6 @@ public class SettingsSliceProvider extends SliceProvider { @VisibleForTesting Map mSliceDataCache; - public static Uri getUri(String path) { - return new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(SLICE_AUTHORITY) - .appendPath(path).build(); - } - @Override public boolean onCreateSliceProvider() { mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext()); @@ -176,7 +174,8 @@ public class SettingsSliceProvider extends SliceProvider { .setSubtitle(state) .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED), null, finalWifiEnabled)) - .setPrimaryAction(new SliceAction(getIntent(Intent.ACTION_MAIN), null, null))) + .setPrimaryAction( + new SliceAction(getIntent(Intent.ACTION_MAIN), null, null))) .build(); } diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index a68ed19db1..4124df7718 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -59,7 +59,8 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { // Wait a bit for wifi to update (TODO: is there a better way to do this?) Handler h = new Handler(); h.postDelayed(() -> { - Uri uri = SettingsSliceProvider.getUri(SettingsSliceProvider.PATH_WIFI); + Uri uri = SliceBuilderUtils.getUri(SettingsSliceProvider.PATH_WIFI, + false /* isPlatformSlice */); context.getContentResolver().notifyChange(uri, null); }, 1000); break; diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index e94ee56ddc..e90ea8e9f6 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -19,9 +19,12 @@ package com.android.settings.slices; import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY; import android.app.PendingIntent; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; +import android.net.Uri; +import android.provider.SettingsSlicesContract; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; @@ -98,6 +101,17 @@ public class SliceBuilderUtils { sliceData.getKey()); } + public static Uri getUri(String path, boolean isPlatformSlice) { + final String authority = isPlatformSlice + ? SettingsSlicesContract.AUTHORITY + : SettingsSliceProvider.SLICE_AUTHORITY; + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority) + .appendPath(path) + .build(); + } + private static BasePreferenceController getPreferenceController(Context context, String controllerClassName, String controllerKey) { try { diff --git a/src/com/android/settings/slices/SliceData.java b/src/com/android/settings/slices/SliceData.java index a7c9a116da..c02b113569 100644 --- a/src/com/android/settings/slices/SliceData.java +++ b/src/com/android/settings/slices/SliceData.java @@ -70,6 +70,8 @@ public class SliceData { @SliceType private final int mSliceType; + private final boolean mIsPlatformDefined; + public String getKey() { return mKey; } @@ -106,6 +108,10 @@ public class SliceData { return mSliceType; } + public boolean isPlatformDefined() { + return mIsPlatformDefined; + } + private SliceData(Builder builder) { mKey = builder.mKey; mTitle = builder.mTitle; @@ -116,6 +122,7 @@ public class SliceData { mUri = builder.mUri; mPreferenceController = builder.mPrefControllerClassName; mSliceType = builder.mSliceType; + mIsPlatformDefined = builder.mIsPlatformDefined; } @Override @@ -151,6 +158,8 @@ public class SliceData { private int mSliceType; + private boolean mIsPlatformDefined; + public Builder setKey(String key) { mKey = key; return this; @@ -196,6 +205,11 @@ public class SliceData { return this; } + public Builder setPlatformDefined(boolean isPlatformDefined) { + mIsPlatformDefined = isPlatformDefined; + return this; + } + public SliceData build() { if (TextUtils.isEmpty(mKey)) { throw new IllegalStateException("Key cannot be empty"); diff --git a/src/com/android/settings/slices/SliceDataConverter.java b/src/com/android/settings/slices/SliceDataConverter.java index e7b53d0394..7cf1994f9e 100644 --- a/src/com/android/settings/slices/SliceDataConverter.java +++ b/src/com/android/settings/slices/SliceDataConverter.java @@ -19,6 +19,7 @@ package com.android.settings.slices; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_ICON; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_PLATFORM_SLICE_FLAG; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SUMMARY; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_TITLE; @@ -164,7 +165,8 @@ class SliceDataConverter { | MetadataFlag.FLAG_NEED_PREF_TYPE | MetadataFlag.FLAG_NEED_PREF_TITLE | MetadataFlag.FLAG_NEED_PREF_ICON - | MetadataFlag.FLAG_NEED_PREF_SUMMARY); + | MetadataFlag.FLAG_NEED_PREF_SUMMARY + | MetadataFlag.FLAG_NEED_PLATFORM_SLICE_FLAG); for (Bundle bundle : metadata) { // TODO (b/67996923) Non-controller Slices should become intent-only slices. @@ -179,6 +181,7 @@ class SliceDataConverter { final int iconResId = bundle.getInt(METADATA_ICON); final int sliceType = SliceBuilderUtils.getSliceType(mContext, controllerClassName, key); + final boolean isPlatformSlice = bundle.getBoolean(METADATA_PLATFORM_SLICE_FLAG); final SliceData xmlSlice = new SliceData.Builder() .setKey(key) @@ -189,6 +192,7 @@ class SliceDataConverter { .setPreferenceControllerClassName(controllerClassName) .setFragmentName(fragmentName) .setSliceType(sliceType) + .setPlatformDefined(isPlatformSlice) .build(); xmlSliceData.add(xmlSlice); diff --git a/tests/robotests/res/xml-mcc999/location_settings.xml b/tests/robotests/res/xml-mcc999/location_settings.xml index 5619c774a6..08cd8ad65d 100644 --- a/tests/robotests/res/xml-mcc999/location_settings.xml +++ b/tests/robotests/res/xml-mcc999/location_settings.xml @@ -25,6 +25,7 @@ android:title="title" android:icon="@drawable/ic_android" android:summary="summary" - settings:controller="com.android.settings.slices.FakePreferenceController"/> + settings:controller="com.android.settings.slices.FakePreferenceController" + settings:platform_slice="true"/> \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index 50f47ff780..9edae7e39c 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -18,8 +18,7 @@ package com.android.settings.slices; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -28,6 +27,7 @@ import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; +import android.provider.SettingsSlicesContract; import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.FakeToggleController; @@ -50,6 +50,8 @@ import java.util.HashMap; public class SettingsSliceProviderTest { private final String KEY = "KEY"; + private final String INTENT_PATH = SettingsSlicesContract.PATH_SETTING_INTENT + "/" + KEY; + private final String ACTION_PATH = SettingsSlicesContract.PATH_SETTING_ACTION + "/" + KEY; private final String TITLE = "title"; private final String SUMMARY = "summary"; private final String SCREEN_TITLE = "screen title"; @@ -67,6 +69,8 @@ public class SettingsSliceProviderTest { mProvider = spy(new SettingsSliceProvider()); mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); + when(mProvider.getContext()).thenReturn(mContext); + mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase(); SlicesDatabaseHelper.getInstance(mContext).setIndexedState(); } @@ -78,12 +82,8 @@ public class SettingsSliceProviderTest { @Test public void testInitialSliceReturned_emptySlice() { - insertSpecialCase(KEY); - ContentResolver mockResolver = mock(ContentResolver.class); - doReturn(mockResolver).when(mContext).getContentResolver(); - when(mProvider.getContext()).thenReturn(mContext); - - Uri uri = SettingsSliceProvider.getUri(KEY); + insertSpecialCase(INTENT_PATH); + Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false); Slice slice = mProvider.onBindSlice(uri); assertThat(slice.getUri()).isEqualTo(uri); @@ -91,21 +91,9 @@ public class SettingsSliceProviderTest { } @Test - public void testUriBuilder_returnsValidSliceUri() { - Uri uri = SettingsSliceProvider.getUri(KEY); - - assertThat(uri.getScheme()).isEqualTo(ContentResolver.SCHEME_CONTENT); - assertThat(uri.getAuthority()).isEqualTo(SettingsSliceProvider.SLICE_AUTHORITY); - assertThat(uri.getLastPathSegment()).isEqualTo(KEY); - } - - @Test public void testLoadSlice_returnsSliceFromAccessor() { - ContentResolver mockResolver = mock(ContentResolver.class); - doReturn(mockResolver).when(mContext).getContentResolver(); - when(mProvider.getContext()).thenReturn(mContext); insertSpecialCase(KEY); - Uri uri = SettingsSliceProvider.getUri(KEY); + Uri uri = SliceBuilderUtils.getUri(KEY, false); mProvider.loadSlice(uri); SliceData data = mProvider.mSliceDataCache.get(uri); @@ -116,7 +104,6 @@ public class SettingsSliceProviderTest { @Test public void testLoadSlice_cachedEntryRemovedOnBuild() { - when(mProvider.getContext()).thenReturn(mContext); SliceData data = getDummyData(); mProvider.mSliceDataCache.put(data.getUri(), data); mProvider.onBindSlice(data.getUri()); diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index 5a2271aadd..0fcce5fb19 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import android.content.ContentResolver; import android.content.Context; import android.net.Uri; +import android.provider.SettingsSlicesContract; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; @@ -48,6 +50,9 @@ public class SliceBuilderUtilsTest { private final Class PREF_CONTROLLER = FakeToggleController.class; private final Class PREF_CONTROLLER2 = FakeContextOnlyPreferenceController.class; + private final String INTENT_PATH = SettingsSlicesContract.PATH_SETTING_INTENT + "/" + KEY; + private final String ACTION_PATH = SettingsSlicesContract.PATH_SETTING_ACTION + "/" + KEY; + private Context mContext; @Before @@ -63,6 +68,58 @@ public class SliceBuilderUtilsTest { } @Test + public void testUriBuilder_oemAuthority_intentPath_returnsValidSliceUri() { + Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(INTENT_PATH) + .build(); + + Uri actualUri = SliceBuilderUtils.getUri(INTENT_PATH, false); + + assertThat(actualUri).isEqualTo(expectedUri); + } + + @Test + public void testUriBuilder_oemAuthority_actionPath_returnsValidSliceUri() { + Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(ACTION_PATH) + .build(); + + Uri actualUri = SliceBuilderUtils.getUri(ACTION_PATH, false); + + assertThat(actualUri).isEqualTo(expectedUri); + } + + @Test + public void testUriBuilder_platformAuthority_intentPath_returnsValidSliceUri() { + Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSlicesContract.AUTHORITY) + .appendPath(ACTION_PATH) + .build(); + + Uri actualUri = SliceBuilderUtils.getUri(ACTION_PATH, true); + + assertThat(actualUri).isEqualTo(expectedUri); + } + + @Test + public void testUriBuilder_platformAuthority_actionPath_returnsValidSliceUri() { + Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSlicesContract.AUTHORITY) + .appendPath(ACTION_PATH) + .build(); + + Uri actualUri = SliceBuilderUtils.getUri(ACTION_PATH, true); + + assertThat(actualUri).isEqualTo(expectedUri); + } + + @Test public void testGetPreferenceController_buildsMatchingController() { BasePreferenceController controller = SliceBuilderUtils.getPreferenceController(mContext, getDummyData()); diff --git a/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java b/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java index 4d6c08dda8..36c27548c1 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java @@ -81,6 +81,7 @@ public class SliceDataConverterTest { assertThat(fakeSlice.getUri()).isNull(); assertThat(fakeSlice.getFragmentClassName()).isEqualTo(fakeFragmentClassName); assertThat(fakeSlice.getPreferenceController()).isEqualTo(fakeControllerName); - assertThat(fakeSlice.getSliceType()).isEqualTo(SliceData.SliceType.SLIDER); + assertThat(fakeSlice.getSliceType()).isEqualTo(SliceData.SliceType.SLIDER); // from XML + assertThat(fakeSlice.isPlatformDefined()).isTrue(); // from XML } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/slices/SliceDataTest.java b/tests/robotests/src/com/android/settings/slices/SliceDataTest.java index 7cd19b5bcf..c2ab0afb18 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceDataTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceDataTest.java @@ -37,6 +37,7 @@ public class SliceDataTest { private final Uri URI = Uri.parse("content://com.android.settings.slices/test"); private final String PREF_CONTROLLER = "com.android.settings.slices.tester"; private final int SLICE_TYPE = SliceData.SliceType.SWITCH; + private final boolean IS_PLATFORM_DEFINED = true; @Test public void testBuilder_buildsMatchingObject() { @@ -49,7 +50,8 @@ public class SliceDataTest { .setFragmentName(FRAGMENT_NAME) .setUri(URI) .setPreferenceControllerClassName(PREF_CONTROLLER) - .setSliceType(SLICE_TYPE); + .setSliceType(SLICE_TYPE) + .setPlatformDefined(IS_PLATFORM_DEFINED); SliceData data = builder.build(); @@ -62,6 +64,7 @@ public class SliceDataTest { assertThat(data.getUri()).isEqualTo(URI); assertThat(data.getPreferenceController()).isEqualTo(PREF_CONTROLLER); assertThat(data.getSliceType()).isEqualTo(SLICE_TYPE); + assertThat(data.isPlatformDefined()).isEqualTo(IS_PLATFORM_DEFINED); } @Test(expected = IllegalStateException.class) diff --git a/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java index 8989de9ef7..331058c96c 100644 --- a/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java +++ b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java @@ -90,7 +90,7 @@ public class SlicesDatabaseAccessorTest { public void testGetSliceFromUri_validUri_validSliceReturned() { String key = "key"; insertSpecialCase(key); - Uri uri = SettingsSliceProvider.getUri(key); + Uri uri = SliceBuilderUtils.getUri(key, false); SliceData data = mAccessor.getSliceDataFromUri(uri); @@ -106,7 +106,7 @@ public class SlicesDatabaseAccessorTest { @Test(expected = IllegalStateException.class) public void testGetSliceFromUri_invalidUri_errorThrown() { - Uri uri = SettingsSliceProvider.getUri("durr"); + Uri uri = SliceBuilderUtils.getUri("durr", false); mAccessor.getSliceDataFromUri(uri); } -- 2.11.0