/* * Copyright (C) 2017 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.slices; import static com.android.settings.slices.SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Binder; import android.text.TextUtils; import android.util.Pair; import androidx.slice.Slice; import com.android.settings.overlay.FeatureFactory; import com.android.settings.slices.SlicesDatabaseHelper.IndexColumns; import java.util.ArrayList; import java.util.List; /** * Class used to map a {@link Uri} from {@link SettingsSliceProvider} to a Slice. */ public class SlicesDatabaseAccessor { public static final String[] SELECT_COLUMNS_ALL = { IndexColumns.KEY, IndexColumns.TITLE, IndexColumns.SUMMARY, IndexColumns.SCREENTITLE, IndexColumns.KEYWORDS, IndexColumns.ICON_RESOURCE, IndexColumns.FRAGMENT, IndexColumns.CONTROLLER, IndexColumns.SLICE_TYPE, IndexColumns.UNAVAILABLE_SLICE_SUBTITLE, }; private final Context mContext; private final SlicesDatabaseHelper mHelper; public SlicesDatabaseAccessor(Context context) { mContext = context; mHelper = SlicesDatabaseHelper.getInstance(mContext); } /** * Query the slices database and return a {@link SliceData} object corresponding to the row * matching the key provided by the {@param uri}. Additionally adds the {@param uri} to the * {@link SliceData} object so the {@link Slice} can bind to the {@link Uri}. * Used when building a {@link Slice}. */ public SliceData getSliceDataFromUri(Uri uri) { Pair pathData = SliceBuilderUtils.getPathData(uri); if (pathData == null) { throw new IllegalStateException("Invalid Slices uri: " + uri); } try (Cursor cursor = getIndexedSliceData(pathData.second /* key */)) { return buildSliceData(cursor, uri, pathData.first /* isIntentOnly */); } } /** * Query the slices database and return a {@link SliceData} object corresponding to the row * matching the {@param key}. * Used when handling the action of the {@link Slice}. */ public SliceData getSliceDataFromKey(String key) { try (Cursor cursor = getIndexedSliceData(key)) { return buildSliceData(cursor, null /* uri */, false /* isIntentOnly */); } } /** * @return a list of Slice {@link Uri}s based on their visibility {@param isPublicSlice } and * {@param authority}. */ public List getSliceUris(String authority, boolean isPublicSlice) { verifyIndexing(); final List 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 (Cursor resultCursor = database.query(TABLE_SLICES_INDEX, columns, whereClause /* where */, null /* selection */, null /* groupBy */, null /* having */, null /* orderBy */)) { if (!resultCursor.moveToFirst()) { return uris; } do { final Uri uri = Uri.parse(resultCursor.getString(0 /* SLICE_URI */)); if (TextUtils.isEmpty(authority) || TextUtils.equals(authority, uri.getAuthority())) { uris.add(uri); } } while (resultCursor.moveToNext()); } return uris; } private Cursor getIndexedSliceData(String path) { verifyIndexing(); final String whereClause = buildKeyMatchWhereClause(); final SQLiteDatabase database = mHelper.getReadableDatabase(); final String[] selection = new String[]{path}; final Cursor resultCursor = database.query(TABLE_SLICES_INDEX, SELECT_COLUMNS_ALL, whereClause, selection, null /* groupBy */, null /* having */, null /* orderBy */); int numResults = resultCursor.getCount(); if (numResults == 0) { resultCursor.close(); throw new IllegalStateException("Invalid Slices key from path: " + path); } if (numResults > 1) { resultCursor.close(); throw new IllegalStateException( "Should not match more than 1 slice with path: " + path); } resultCursor.moveToFirst(); return resultCursor; } private String buildKeyMatchWhereClause() { return new StringBuilder(IndexColumns.KEY) .append(" = ?") .toString(); } private static SliceData buildSliceData(Cursor cursor, Uri uri, boolean isIntentOnly) { final String key = cursor.getString(cursor.getColumnIndex(IndexColumns.KEY)); final String title = cursor.getString(cursor.getColumnIndex(IndexColumns.TITLE)); final String summary = cursor.getString(cursor.getColumnIndex(IndexColumns.SUMMARY)); final String screenTitle = cursor.getString( cursor.getColumnIndex(IndexColumns.SCREENTITLE)); final String keywords = cursor.getString(cursor.getColumnIndex(IndexColumns.KEYWORDS)); final int iconResource = cursor.getInt(cursor.getColumnIndex(IndexColumns.ICON_RESOURCE)); final String fragmentClassName = cursor.getString( cursor.getColumnIndex(IndexColumns.FRAGMENT)); final String controllerClassName = cursor.getString( cursor.getColumnIndex(IndexColumns.CONTROLLER)); int sliceType = cursor.getInt( cursor.getColumnIndex(IndexColumns.SLICE_TYPE)); final String unavailableSliceSubtitle = cursor.getString( cursor.getColumnIndex(IndexColumns.UNAVAILABLE_SLICE_SUBTITLE)); if (isIntentOnly) { sliceType = SliceData.SliceType.INTENT; } return new SliceData.Builder() .setKey(key) .setTitle(title) .setSummary(summary) .setScreenTitle(screenTitle) .setKeywords(keywords) .setIcon(iconResource) .setFragmentName(fragmentClassName) .setPreferenceControllerClassName(controllerClassName) .setUri(uri) .setSliceType(sliceType) .setUnavailableSliceSubtitle(unavailableSliceSubtitle) .build(); } private void verifyIndexing() { final long uidToken = Binder.clearCallingIdentity(); try { FeatureFactory.getFactory( mContext).getSlicesFeatureProvider().indexSliceData(mContext); } finally { Binder.restoreCallingIdentity(uidToken); } } }