Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d57e5a5d authored by Yi-Ling Chuang's avatar Yi-Ling Chuang
Browse files

Add the impl for the ability to query non-public Slices

Apps get Settings Slices through onGetSliceDescendants(), so adding some
codes here to make us be capable returning non-public Slices. As these
SliceData come from slice_index.db, where SliceDatabaseAccessor is the
middleman for us to access those data, so adding a parameter in
getSliceUris() to determine what data should be returned.

Bug: 141088937
Test: robotests
Change-Id: I411eb1ff194b7c8915b9e7309c684046dbde29fb
parent 1a359b5b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -425,4 +425,7 @@
    <string-array name="config_panel_keep_observe_uri" translatable="false">
        <item>content://com.android.settings.slices/intent/media_output_indicator</item>
    </string-array>

    <!-- Uri to query non-public Slice Uris. -->
    <string name="config_non_public_slice_query_uri" translatable="false"></string>
</resources>
+39 −5
Original line number Diff line number Diff line
@@ -20,10 +20,13 @@ import static android.Manifest.permission.READ_SEARCH_INDEXABLES;

import android.app.PendingIntent;
import android.app.slice.SliceManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
import android.os.StrictMode;
import android.provider.Settings;
import android.provider.SettingsSlicesContract;
@@ -265,16 +268,29 @@ public class SettingsSliceProvider extends SliceProvider {
    @Override
    public Collection<Uri> onGetSliceDescendants(Uri uri) {
        final List<Uri> descendants = new ArrayList<>();
        final Pair<Boolean, String> pathData = SliceBuilderUtils.getPathData(uri);
        Uri finalUri = uri;

        if (isPrivateSlicesNeeded(finalUri)) {
            descendants.addAll(
                    mSlicesDatabaseAccessor.getSliceUris(finalUri.getAuthority(),
                            false /* isPublicSlice */));
            Log.d(TAG, "provide " + descendants.size() + " non-public slices");
            finalUri = new Uri.Builder()
                    .scheme(ContentResolver.SCHEME_CONTENT)
                    .authority(finalUri.getAuthority())
                    .build();
        }

        final Pair<Boolean, String> pathData = SliceBuilderUtils.getPathData(finalUri);

        if (pathData != null) {
            // Uri has a full path and will not have any descendants.
            descendants.add(uri);
            descendants.add(finalUri);
            return descendants;
        }

        final String authority = uri.getAuthority();
        final String path = uri.getPath();
        final String authority = finalUri.getAuthority();
        final String path = finalUri.getPath();
        final boolean isPathEmpty = path.isEmpty();

        // Path is anything but empty, "action", or "intent". Return empty list.
@@ -286,7 +302,7 @@ public class SettingsSliceProvider extends SliceProvider {
        }

        // Add all descendants from db with matching authority.
        descendants.addAll(mSlicesDatabaseAccessor.getSliceUris(authority));
        descendants.addAll(mSlicesDatabaseAccessor.getSliceUris(authority, true /*isPublicSlice*/));

        if (isPathEmpty && TextUtils.isEmpty(authority)) {
            // No path nor authority. Return all possible Uris by adding all special slice uri
@@ -404,6 +420,24 @@ public class SettingsSliceProvider extends SliceProvider {
        return set;
    }

    @VisibleForTesting
    boolean isPrivateSlicesNeeded(Uri uri) {
        final String queryUri = getContext().getString(R.string.config_non_public_slice_query_uri);

        if (!TextUtils.isEmpty(queryUri) && TextUtils.equals(uri.toString(), queryUri)) {
            // check if the calling package is eligible for private slices
            final int callingUid = Binder.getCallingUid();
            final boolean hasPermission = getContext().checkPermission(
                    android.Manifest.permission.READ_SEARCH_INDEXABLES, Binder.getCallingPid(),
                    callingUid) == PackageManager.PERMISSION_GRANTED;
            final String callingPackage = getContext().getPackageManager()
                    .getPackagesForUid(callingUid)[0];
            return hasPermission && TextUtils.equals(callingPackage,
                    getContext().getString(R.string.config_settingsintelligence_package_name));
        }
        return false;
    }

    private void startBackgroundWorker(Sliceable sliceable, Uri uri) {
        final Class workerClass = sliceable.getBackgroundWorkerClass();
        if (workerClass == null) {
+7 −5
Original line number Diff line number Diff line
@@ -88,16 +88,18 @@ public class SlicesDatabaseAccessor {
    }

    /**
     * @return a list of Slice {@link Uri}s matching {@param authority}.
     * @return a list of Slice {@link Uri}s based on their visibility {@param isPublicSlice } and
     * {@param authority}.
     */
    public List<Uri> getSliceUris(String authority) {
    public List<Uri> getSliceUris(String authority, boolean isPublicSlice) {
        verifyIndexing();
        final List<Uri> uris = new ArrayList<>();
        final String whereClause = IndexColumns.PUBLIC_SLICE + (isPublicSlice ? "=1" : "=0");
        final SQLiteDatabase database = mHelper.getReadableDatabase();
        final String[] columns = new String[]{IndexColumns.SLICE_URI};
        try (final Cursor resultCursor = database.query(TABLE_SLICES_INDEX, columns,
                null /* where */, null /* selection */, null /* groupBy */, null /* having */,
                null /* orderBy */)) {
        try (Cursor resultCursor = database.query(TABLE_SLICES_INDEX, columns,
                whereClause /* where */, null /* selection */, null /* groupBy */,
                null /* having */, null /* orderBy */)) {
            if (!resultCursor.moveToFirst()) {
                return uris;
            }
+3 −0
Original line number Diff line number Diff line
@@ -94,4 +94,7 @@
        <item>injected_tile_key</item>
        <item>injected_tile_key2</item>
    </string-array>

    <!-- Uri to query non-public Slice Uris. -->
    <string name="config_non_public_slice_query_uri" translatable="false">content://com.android.settings.slices/test</string>
</resources>
+140 −9
Original line number Diff line number Diff line
@@ -18,10 +18,13 @@
package com.android.settings.slices;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -66,12 +69,15 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowAccessibilityManager;
import org.robolectric.shadows.ShadowBinder;
import org.robolectric.shadows.ShadowPackageManager;

import java.util.ArrayList;
import java.util.Arrays;
@@ -110,6 +116,7 @@ public class SettingsSliceProviderTest {

    private Context mContext;
    private SettingsSliceProvider mProvider;
    private ShadowPackageManager mPackageManager;
    @Mock
    private SliceManager mManager;

@@ -146,6 +153,8 @@ public class SettingsSliceProviderTest {
        doReturn(mManager).when(mContext).getSystemService(SliceManager.class);
        when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList());

        mPackageManager = Shadows.shadowOf(mContext.getPackageManager());

        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
    }

@@ -284,7 +293,8 @@ public class SettingsSliceProviderTest {
    @Test
    public void getDescendantUris_invalidPath_returnsEmpty() {
        final String key = "platform_key";
        SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSlicesContract.AUTHORITY)
@@ -299,7 +309,8 @@ public class SettingsSliceProviderTest {

    @Test
    public void getDescendantUris_platformSlice_doesNotReturnOEMSlice() {
        SliceTestUtils.insertSliceToDb(mContext, "oem_key", false /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, "oem_key", false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSlicesContract.AUTHORITY)
@@ -313,7 +324,8 @@ public class SettingsSliceProviderTest {

    @Test
    public void getDescendantUris_oemSlice_doesNotReturnPlatformSlice() {
        SliceTestUtils.insertSliceToDb(mContext, "platform_key", true /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, "platform_key", true /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
@@ -328,7 +340,8 @@ public class SettingsSliceProviderTest {
    @Test
    public void getDescendantUris_oemSlice_returnsOEMUriDescendant() {
        final String key = "oem_key";
        SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
@@ -351,7 +364,8 @@ public class SettingsSliceProviderTest {
    @Test
    public void getDescendantUris_oemSliceNoPath_returnsOEMUriDescendant() {
        final String key = "oem_key";
        SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
@@ -370,10 +384,32 @@ public class SettingsSliceProviderTest {
        assertThat(descendants).containsExactlyElementsIn(expectedUris);
    }

    @Test
    public void getDescendantUris_oemSliceNoPath_notContainPrivateUri() {
        final String key = "oem_key";
        SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, false /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
                .build();
        final Uri expectedUri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
                .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
                .appendPath(key)
                .build();

        final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);

        assertThat(descendants).doesNotContain(expectedUri);
    }

    @Test
    public void getDescendantUris_platformSlice_returnsPlatformUriDescendant() {
        final String key = "platform_key";
        SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSlicesContract.AUTHORITY)
@@ -396,7 +432,8 @@ public class SettingsSliceProviderTest {
    @Test
    public void getDescendantUris_platformSliceNoPath_returnsPlatformUriDescendant() {
        final String key = "platform_key";
        SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSlicesContract.AUTHORITY)
@@ -419,8 +456,10 @@ public class SettingsSliceProviderTest {
    public void getDescendantUris_noAuthorityNorPath_returnsAllUris() {
        final String platformKey = "platform_key";
        final String oemKey = "oemKey";
        SliceTestUtils.insertSliceToDb(mContext, platformKey, true /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, oemKey, false /* isPlatformSlice */);
        SliceTestUtils.insertSliceToDb(mContext, platformKey, true /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        SliceTestUtils.insertSliceToDb(mContext, oemKey, false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
        final Uri uri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .build();
@@ -445,6 +484,48 @@ public class SettingsSliceProviderTest {
        assertThat(descendants).containsExactlyElementsIn(expectedUris);
    }

    @Test
    @Config(qualifiers = "mcc999")
    public void getDescendantUris_privateSlicesNeeded_containsPrivateSliceUri() {
        final String privateKey = "test_private";
        final Uri specialUri = Uri.parse("content://com.android.settings.slices/test");
        doReturn(true).when(mProvider).isPrivateSlicesNeeded(specialUri);
        SliceTestUtils.insertSliceToDb(mContext, privateKey /* key */, false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, false /* isPublicSlice */);
        final Collection<Uri> expectedUris = new HashSet<>();
        expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
        expectedUris.add(new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
                .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
                .appendPath(privateKey)
                .build());

        final Collection<Uri> descendants = mProvider.onGetSliceDescendants(specialUri);

        assertThat(descendants).containsExactlyElementsIn(expectedUris);
    }

    @Test
    @Config(qualifiers = "mcc999")
    public void getDescendantUris_privateSlicesNotNeeded_notContainPrivateSliceUri() {
        final Uri specialUri = Uri.parse("content://com.android.settings.slices/test");
        doReturn(false).when(mProvider).isPrivateSlicesNeeded(specialUri);
        SliceTestUtils.insertSliceToDb(mContext,
                "test_private" /* key */, false /* isPlatformSlice */,
                null /* customizedUnavailableSliceSubtitle */, false /* isPublicSlice */);
        final Uri expectedUri = new Uri.Builder()
                .scheme(SCHEME_CONTENT)
                .authority(SettingsSliceProvider.SLICE_AUTHORITY)
                .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
                .appendPath("test_private")
                .build();

        final Collection<Uri> descendants = mProvider.onGetSliceDescendants(specialUri);

        assertThat(descendants).doesNotContain(expectedUri);
    }

    @Test
    public void onCreatePermissionRequest_returnsSettingIntent() {
        final PendingIntent pendingIntent = mProvider.onCreatePermissionRequest(
@@ -531,6 +612,56 @@ public class SettingsSliceProviderTest {
                .grantSlicePermission("com.android.settings.slice_whitelist_package", uris.get(0));
    }

    @Test
    @Config(qualifiers = "mcc999")
    public void isPrivateSlicesNeeded_incorrectUri_returnFalse() {
        final Uri uri = Uri.parse("content://com.android.settings.slices/test123");

        assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
    }

    @Test
    public void isPrivateSlicesNeeded_noUri_returnFalse() {
        final Uri uri = Uri.parse("content://com.android.settings.slices/test");

        assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
    }

    @Test
    @Config(qualifiers = "mcc999")
    public void isPrivateSlicesNeeded_correctUriWithPermissionAndIsSI_returnTrue() {
        final Uri uri = Uri.parse("content://com.android.settings.slices/test");
        ShadowBinder.setCallingUid(123);
        doReturn(PERMISSION_GRANTED)
                .when(mContext).checkPermission(anyString(), anyInt(), anyInt());
        mPackageManager.setPackagesForUid(123, new String[]{"com.android.settings.intelligence"});

        assertThat(mProvider.isPrivateSlicesNeeded(uri)).isTrue();
    }

    @Test
    @Config(qualifiers = "mcc999")
    public void isPrivateSlicesNeeded_correctUriWithPermissionNotSI_returnFalse() {
        final Uri uri = Uri.parse("content://com.android.settings.slices/test");
        ShadowBinder.setCallingUid(123);
        doReturn(PERMISSION_GRANTED)
                .when(mContext).checkPermission(anyString(), anyInt(), anyInt());
        mPackageManager.setPackagesForUid(123, new String[]{"com.android.settings.test"});

        assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
    }

    @Test
    @Config(qualifiers = "mcc999")
    public void isPrivateSlicesNeeded_correctUriNoPermission_returnFalse() {
        final Uri uri = Uri.parse("content://com.android.settings.slices/test");
        ShadowBinder.setCallingUid(123);
        doReturn(PERMISSION_DENIED).when(mContext).checkPermission(anyString(), anyInt(), anyInt());
        mPackageManager.setPackagesForUid(123, new String[]{"com.android.settings.intelligence"});

        assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
    }

    private static SliceData getDummyData() {
        return new SliceData.Builder()
                .setKey(KEY)
Loading