1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.settings.slices; 18 19 import static com.android.settings.slices.SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX; 20 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.database.sqlite.SQLiteDatabase; 24 import android.net.Uri; 25 import android.os.Binder; 26 import android.text.TextUtils; 27 import android.util.Pair; 28 29 import androidx.slice.Slice; 30 31 import com.android.settings.overlay.FeatureFactory; 32 import com.android.settings.slices.SlicesDatabaseHelper.IndexColumns; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 37 /** 38 * Class used to map a {@link Uri} from {@link SettingsSliceProvider} to a Slice. 39 */ 40 public class SlicesDatabaseAccessor { 41 42 public static final String[] SELECT_COLUMNS_ALL = { 43 IndexColumns.KEY, 44 IndexColumns.TITLE, 45 IndexColumns.SUMMARY, 46 IndexColumns.SCREENTITLE, 47 IndexColumns.KEYWORDS, 48 IndexColumns.ICON_RESOURCE, 49 IndexColumns.FRAGMENT, 50 IndexColumns.CONTROLLER, 51 IndexColumns.SLICE_TYPE, 52 IndexColumns.UNAVAILABLE_SLICE_SUBTITLE, 53 }; 54 55 private final Context mContext; 56 private final SlicesDatabaseHelper mHelper; 57 SlicesDatabaseAccessor(Context context)58 public SlicesDatabaseAccessor(Context context) { 59 mContext = context; 60 mHelper = SlicesDatabaseHelper.getInstance(mContext); 61 } 62 63 /** 64 * Query the slices database and return a {@link SliceData} object corresponding to the row 65 * matching the key provided by the {@param uri}. Additionally adds the {@param uri} to the 66 * {@link SliceData} object so the {@link Slice} can bind to the {@link Uri}. 67 * Used when building a {@link Slice}. 68 */ getSliceDataFromUri(Uri uri)69 public SliceData getSliceDataFromUri(Uri uri) { 70 Pair<Boolean, String> pathData = SliceBuilderUtils.getPathData(uri); 71 if (pathData == null) { 72 throw new IllegalStateException("Invalid Slices uri: " + uri); 73 } 74 try (Cursor cursor = getIndexedSliceData(pathData.second /* key */)) { 75 return buildSliceData(cursor, uri, pathData.first /* isIntentOnly */); 76 } 77 } 78 79 /** 80 * Query the slices database and return a {@link SliceData} object corresponding to the row 81 * matching the {@param key}. 82 * Used when handling the action of the {@link Slice}. 83 */ getSliceDataFromKey(String key)84 public SliceData getSliceDataFromKey(String key) { 85 try (Cursor cursor = getIndexedSliceData(key)) { 86 return buildSliceData(cursor, null /* uri */, false /* isIntentOnly */); 87 } 88 } 89 90 /** 91 * @return a list of Slice {@link Uri}s based on their visibility {@param isPublicSlice } and 92 * {@param authority}. 93 */ getSliceUris(String authority, boolean isPublicSlice)94 public List<Uri> getSliceUris(String authority, boolean isPublicSlice) { 95 verifyIndexing(); 96 final List<Uri> uris = new ArrayList<>(); 97 final String whereClause = IndexColumns.PUBLIC_SLICE + (isPublicSlice ? "=1" : "=0"); 98 final SQLiteDatabase database = mHelper.getReadableDatabase(); 99 final String[] columns = new String[]{IndexColumns.SLICE_URI}; 100 try (Cursor resultCursor = database.query(TABLE_SLICES_INDEX, columns, 101 whereClause /* where */, null /* selection */, null /* groupBy */, 102 null /* having */, null /* orderBy */)) { 103 if (!resultCursor.moveToFirst()) { 104 return uris; 105 } 106 107 do { 108 final Uri uri = Uri.parse(resultCursor.getString(0 /* SLICE_URI */)); 109 if (TextUtils.isEmpty(authority) 110 || TextUtils.equals(authority, uri.getAuthority())) { 111 uris.add(uri); 112 } 113 } while (resultCursor.moveToNext()); 114 } 115 return uris; 116 } 117 getIndexedSliceData(String path)118 private Cursor getIndexedSliceData(String path) { 119 verifyIndexing(); 120 121 final String whereClause = buildKeyMatchWhereClause(); 122 final SQLiteDatabase database = mHelper.getReadableDatabase(); 123 final String[] selection = new String[]{path}; 124 final Cursor resultCursor = database.query(TABLE_SLICES_INDEX, SELECT_COLUMNS_ALL, 125 whereClause, selection, null /* groupBy */, null /* having */, null /* orderBy */); 126 127 int numResults = resultCursor.getCount(); 128 129 if (numResults == 0) { 130 resultCursor.close(); 131 throw new IllegalStateException("Invalid Slices key from path: " + path); 132 } 133 134 if (numResults > 1) { 135 resultCursor.close(); 136 throw new IllegalStateException( 137 "Should not match more than 1 slice with path: " + path); 138 } 139 140 resultCursor.moveToFirst(); 141 return resultCursor; 142 } 143 buildKeyMatchWhereClause()144 private String buildKeyMatchWhereClause() { 145 return new StringBuilder(IndexColumns.KEY) 146 .append(" = ?") 147 .toString(); 148 } 149 buildSliceData(Cursor cursor, Uri uri, boolean isIntentOnly)150 private static SliceData buildSliceData(Cursor cursor, Uri uri, boolean isIntentOnly) { 151 final String key = cursor.getString(cursor.getColumnIndex(IndexColumns.KEY)); 152 final String title = cursor.getString(cursor.getColumnIndex(IndexColumns.TITLE)); 153 final String summary = cursor.getString(cursor.getColumnIndex(IndexColumns.SUMMARY)); 154 final String screenTitle = cursor.getString( 155 cursor.getColumnIndex(IndexColumns.SCREENTITLE)); 156 final String keywords = cursor.getString(cursor.getColumnIndex(IndexColumns.KEYWORDS)); 157 final int iconResource = cursor.getInt(cursor.getColumnIndex(IndexColumns.ICON_RESOURCE)); 158 final String fragmentClassName = cursor.getString( 159 cursor.getColumnIndex(IndexColumns.FRAGMENT)); 160 final String controllerClassName = cursor.getString( 161 cursor.getColumnIndex(IndexColumns.CONTROLLER)); 162 int sliceType = cursor.getInt( 163 cursor.getColumnIndex(IndexColumns.SLICE_TYPE)); 164 final String unavailableSliceSubtitle = cursor.getString( 165 cursor.getColumnIndex(IndexColumns.UNAVAILABLE_SLICE_SUBTITLE)); 166 167 if (isIntentOnly) { 168 sliceType = SliceData.SliceType.INTENT; 169 } 170 171 return new SliceData.Builder() 172 .setKey(key) 173 .setTitle(title) 174 .setSummary(summary) 175 .setScreenTitle(screenTitle) 176 .setKeywords(keywords) 177 .setIcon(iconResource) 178 .setFragmentName(fragmentClassName) 179 .setPreferenceControllerClassName(controllerClassName) 180 .setUri(uri) 181 .setSliceType(sliceType) 182 .setUnavailableSliceSubtitle(unavailableSliceSubtitle) 183 .build(); 184 } 185 verifyIndexing()186 private void verifyIndexing() { 187 final long uidToken = Binder.clearCallingIdentity(); 188 try { 189 FeatureFactory.getFactory( 190 mContext).getSlicesFeatureProvider().indexSliceData(mContext); 191 } finally { 192 Binder.restoreCallingIdentity(uidToken); 193 } 194 } 195 }