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