1 /* 2 * Copyright (C) 2015 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.tv.search; 18 19 import android.app.SearchManager; 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.database.Cursor; 23 import android.database.MatrixCursor; 24 import android.net.Uri; 25 import android.os.SystemClock; 26 import android.support.annotation.NonNull; 27 import android.support.annotation.Nullable; 28 import android.support.annotation.VisibleForTesting; 29 import android.text.TextUtils; 30 import android.util.Log; 31 import com.android.tv.TvSingletons; 32 import com.android.tv.common.CommonConstants; 33 import com.android.tv.common.SoftPreconditions; 34 import com.android.tv.common.util.CommonUtils; 35 import com.android.tv.common.util.PermissionUtils; 36 import com.android.tv.perf.EventNames; 37 import com.android.tv.perf.PerformanceMonitor; 38 import com.android.tv.perf.TimerEvent; 39 import com.android.tv.util.TvUriMatcher; 40 import com.google.auto.value.AutoValue; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.List; 44 45 public class LocalSearchProvider extends ContentProvider { 46 private static final String TAG = "LocalSearchProvider"; 47 private static final boolean DEBUG = false; 48 49 /** The authority for LocalSearchProvider. */ 50 public static final String AUTHORITY = CommonConstants.BASE_PACKAGE + ".search"; 51 52 // TODO: Remove this once added to the SearchManager. 53 private static final String SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE = "progress_bar_percentage"; 54 55 private static final String[] SEARCHABLE_COLUMNS = 56 new String[] { 57 SearchManager.SUGGEST_COLUMN_TEXT_1, 58 SearchManager.SUGGEST_COLUMN_TEXT_2, 59 SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE, 60 SearchManager.SUGGEST_COLUMN_INTENT_ACTION, 61 SearchManager.SUGGEST_COLUMN_INTENT_DATA, 62 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, 63 SearchManager.SUGGEST_COLUMN_CONTENT_TYPE, 64 SearchManager.SUGGEST_COLUMN_IS_LIVE, 65 SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH, 66 SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT, 67 SearchManager.SUGGEST_COLUMN_DURATION, 68 SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE 69 }; 70 71 private static final String EXPECTED_PATH_PREFIX = "/" + SearchManager.SUGGEST_URI_PATH_QUERY; 72 static final String SUGGEST_PARAMETER_ACTION = "action"; 73 // The launcher passes 10 as a 'limit' parameter by default. 74 @VisibleForTesting static final int DEFAULT_SEARCH_LIMIT = 10; 75 76 @VisibleForTesting 77 static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS; 78 79 private static final String NO_LIVE_CONTENTS = "0"; 80 private static final String LIVE_CONTENTS = "1"; 81 82 private PerformanceMonitor mPerformanceMonitor; 83 84 /** Used only for testing */ 85 private SearchInterface mSearchInterface; 86 87 @Override onCreate()88 public boolean onCreate() { 89 mPerformanceMonitor = TvSingletons.getSingletons(getContext()).getPerformanceMonitor(); 90 return true; 91 } 92 93 @VisibleForTesting setSearchInterface(SearchInterface searchInterface)94 void setSearchInterface(SearchInterface searchInterface) { 95 SoftPreconditions.checkState(CommonUtils.isRunningInTest()); 96 mSearchInterface = searchInterface; 97 } 98 99 @Override query( @onNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)100 public Cursor query( 101 @NonNull Uri uri, 102 String[] projection, 103 String selection, 104 String[] selectionArgs, 105 String sortOrder) { 106 if (TvUriMatcher.match(uri) != TvUriMatcher.MATCH_ON_DEVICE_SEARCH) { 107 throw new IllegalArgumentException("Unknown URI: " + uri); 108 } 109 TimerEvent queryTimer = mPerformanceMonitor.startTimer(); 110 if (DEBUG) { 111 Log.d( 112 TAG, 113 "query(" 114 + uri 115 + ", " 116 + Arrays.toString(projection) 117 + ", " 118 + selection 119 + ", " 120 + Arrays.toString(selectionArgs) 121 + ", " 122 + sortOrder 123 + ")"); 124 } 125 long time = SystemClock.elapsedRealtime(); 126 SearchInterface search = mSearchInterface; 127 if (search == null) { 128 if (PermissionUtils.hasAccessAllEpg(getContext())) { 129 if (DEBUG) Log.d(TAG, "Performing TV Provider search."); 130 search = new TvProviderSearch(getContext()); 131 } else { 132 if (DEBUG) Log.d(TAG, "Performing Data Manager search."); 133 search = new DataManagerSearch(getContext()); 134 } 135 } 136 String query = uri.getLastPathSegment(); 137 int limit = 138 getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT, DEFAULT_SEARCH_LIMIT); 139 if (limit <= 0) { 140 limit = DEFAULT_SEARCH_LIMIT; 141 } 142 int action = getQueryParamater(uri, SUGGEST_PARAMETER_ACTION, DEFAULT_SEARCH_ACTION); 143 if (action < SearchInterface.ACTION_TYPE_START 144 || action > SearchInterface.ACTION_TYPE_END) { 145 action = DEFAULT_SEARCH_ACTION; 146 } 147 List<SearchResult> results = new ArrayList<>(); 148 if (!TextUtils.isEmpty(query)) { 149 results.addAll(search.search(query, limit, action)); 150 } 151 Cursor c = createSuggestionsCursor(results); 152 if (DEBUG) { 153 Log.d( 154 TAG, 155 "Elapsed time(count=" 156 + c.getCount() 157 + "): " 158 + (SystemClock.elapsedRealtime() - time) 159 + "(msec)"); 160 } 161 mPerformanceMonitor.stopTimer(queryTimer, EventNames.ON_DEVICE_SEARCH); 162 return c; 163 } 164 getQueryParamater(Uri uri, String key, int defaultValue)165 private int getQueryParamater(Uri uri, String key, int defaultValue) { 166 try { 167 return Integer.parseInt(uri.getQueryParameter(key)); 168 } catch (NumberFormatException | UnsupportedOperationException e) { 169 // Ignore the exceptions 170 } 171 return defaultValue; 172 } 173 createSuggestionsCursor(List<SearchResult> results)174 private Cursor createSuggestionsCursor(List<SearchResult> results) { 175 MatrixCursor cursor = new MatrixCursor(SEARCHABLE_COLUMNS, results.size()); 176 List<String> row = new ArrayList<>(SEARCHABLE_COLUMNS.length); 177 178 int index = 0; 179 for (SearchResult result : results) { 180 row.clear(); 181 row.add(result.getTitle()); 182 row.add(result.getDescription()); 183 row.add(result.getImageUri()); 184 row.add(result.getIntentAction()); 185 row.add(result.getIntentData()); 186 row.add(result.getIntentExtraData()); 187 row.add(result.getContentType()); 188 row.add(result.getIsLive() ? LIVE_CONTENTS : NO_LIVE_CONTENTS); 189 row.add(result.getVideoWidth() == 0 ? null : String.valueOf(result.getVideoWidth())); 190 row.add(result.getVideoHeight() == 0 ? null : String.valueOf(result.getVideoHeight())); 191 row.add(result.getDuration() == 0 ? null : String.valueOf(result.getDuration())); 192 row.add(String.valueOf(result.getProgressPercentage())); 193 cursor.addRow(row); 194 if (DEBUG) Log.d(TAG, "Result[" + (++index) + "]: " + result); 195 } 196 return cursor; 197 } 198 199 @Override getType(Uri uri)200 public String getType(Uri uri) { 201 if (!checkUriCorrect(uri)) return null; 202 return SearchManager.SUGGEST_MIME_TYPE; 203 } 204 checkUriCorrect(Uri uri)205 private static boolean checkUriCorrect(Uri uri) { 206 return uri != null && uri.getPath().startsWith(EXPECTED_PATH_PREFIX); 207 } 208 209 @Override insert(Uri uri, ContentValues values)210 public Uri insert(Uri uri, ContentValues values) { 211 throw new UnsupportedOperationException(); 212 } 213 214 @Override delete(Uri uri, String selection, String[] selectionArgs)215 public int delete(Uri uri, String selection, String[] selectionArgs) { 216 throw new UnsupportedOperationException(); 217 } 218 219 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)220 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 221 throw new UnsupportedOperationException(); 222 } 223 224 /** A placeholder to a search result. */ 225 @AutoValue 226 public abstract static class SearchResult { builder()227 public static Builder builder() { 228 // primitive fields cannot be nullable. Set to default; 229 return new AutoValue_LocalSearchProvider_SearchResult.Builder() 230 .setChannelId(0) 231 .setIsLive(false) 232 .setVideoWidth(0) 233 .setVideoHeight(0) 234 .setDuration(0) 235 .setProgressPercentage(0); 236 } 237 238 @AutoValue.Builder 239 abstract static class Builder { setChannelId(long value)240 abstract Builder setChannelId(long value); 241 setChannelNumber(String value)242 abstract Builder setChannelNumber(String value); 243 setTitle(String value)244 abstract Builder setTitle(String value); 245 setDescription(String value)246 abstract Builder setDescription(String value); 247 setImageUri(String value)248 abstract Builder setImageUri(String value); 249 setIntentAction(String value)250 abstract Builder setIntentAction(String value); 251 setIntentData(String value)252 abstract Builder setIntentData(String value); 253 setIntentExtraData(String value)254 abstract Builder setIntentExtraData(String value); 255 setContentType(String value)256 abstract Builder setContentType(String value); 257 setIsLive(boolean value)258 abstract Builder setIsLive(boolean value); 259 setVideoWidth(int value)260 abstract Builder setVideoWidth(int value); 261 setVideoHeight(int value)262 abstract Builder setVideoHeight(int value); 263 setDuration(long value)264 abstract Builder setDuration(long value); 265 setProgressPercentage(int value)266 abstract Builder setProgressPercentage(int value); 267 build()268 abstract SearchResult build(); 269 } 270 getChannelId()271 abstract long getChannelId(); 272 273 @Nullable getChannelNumber()274 abstract String getChannelNumber(); 275 276 @Nullable getTitle()277 abstract String getTitle(); 278 279 @Nullable getDescription()280 abstract String getDescription(); 281 282 @Nullable getImageUri()283 abstract String getImageUri(); 284 285 @Nullable getIntentAction()286 abstract String getIntentAction(); 287 288 @Nullable getIntentData()289 abstract String getIntentData(); 290 291 @Nullable getIntentExtraData()292 abstract String getIntentExtraData(); 293 294 @Nullable getContentType()295 abstract String getContentType(); 296 getIsLive()297 abstract boolean getIsLive(); 298 getVideoWidth()299 abstract int getVideoWidth(); 300 getVideoHeight()301 abstract int getVideoHeight(); 302 getDuration()303 abstract long getDuration(); 304 getProgressPercentage()305 abstract int getProgressPercentage(); 306 } 307 } 308