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.VisibleForTesting;
     28 import android.text.TextUtils;
     29 import android.util.Log;
     30 
     31 import com.android.tv.TvApplication;
     32 import com.android.tv.common.SoftPreconditions;
     33 import com.android.tv.common.TvCommonUtils;
     34 import com.android.tv.perf.EventNames;
     35 import com.android.tv.perf.PerformanceMonitor;
     36 import com.android.tv.perf.TimerEvent;
     37 import com.android.tv.util.PermissionUtils;
     38 import com.android.tv.util.TvUriMatcher;
     39 
     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 = "com.android.tv.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 = 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_CONTENT_TYPE,
     63         SearchManager.SUGGEST_COLUMN_IS_LIVE,
     64         SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH,
     65         SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT,
     66         SearchManager.SUGGEST_COLUMN_DURATION,
     67         SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE
     68     };
     69 
     70     private static final String EXPECTED_PATH_PREFIX = "/" + SearchManager.SUGGEST_URI_PATH_QUERY;
     71     static final String SUGGEST_PARAMETER_ACTION = "action";
     72     // The launcher passes 10 as a 'limit' parameter by default.
     73     @VisibleForTesting
     74     static final int DEFAULT_SEARCH_LIMIT = 10;
     75     @VisibleForTesting
     76     static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS;
     77 
     78     private static final String NO_LIVE_CONTENTS = "0";
     79     private static final String LIVE_CONTENTS = "1";
     80 
     81     private PerformanceMonitor mPerformanceMonitor;
     82 
     83     /** Used only for testing */
     84     private SearchInterface mSearchInterface;
     85 
     86     @Override
     87     public boolean onCreate() {
     88         mPerformanceMonitor = TvApplication.getSingletons(getContext()).getPerformanceMonitor();
     89         return true;
     90     }
     91 
     92     @VisibleForTesting
     93     void setSearchInterface(SearchInterface searchInterface) {
     94         SoftPreconditions.checkState(TvCommonUtils.isRunningInTest());
     95         mSearchInterface = searchInterface;
     96     }
     97 
     98     @Override
     99     public Cursor query(@NonNull Uri uri, String[] projection, String selection,
    100             String[] selectionArgs, String sortOrder) {
    101         if (TvUriMatcher.match(uri) != TvUriMatcher.MATCH_ON_DEVICE_SEARCH) {
    102             throw new IllegalArgumentException("Unknown URI: " + uri);
    103         }
    104         TimerEvent queryTimer = mPerformanceMonitor.startTimer();
    105         if (DEBUG) {
    106             Log.d(TAG, "query(" + uri + ", " + Arrays.toString(projection) + ", " + selection + ", "
    107                     + Arrays.toString(selectionArgs) + ", " + sortOrder + ")");
    108         }
    109         long time = SystemClock.elapsedRealtime();
    110         SearchInterface search = mSearchInterface;
    111         if (search == null) {
    112             if (PermissionUtils.hasAccessAllEpg(getContext())) {
    113                 if (DEBUG) Log.d(TAG, "Performing TV Provider search.");
    114                 search = new TvProviderSearch(getContext());
    115             } else {
    116                 if (DEBUG) Log.d(TAG, "Performing Data Manager search.");
    117                 search = new DataManagerSearch(getContext());
    118             }
    119         }
    120         String query = uri.getLastPathSegment();
    121         int limit = getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT,
    122                 DEFAULT_SEARCH_LIMIT);
    123         if (limit <= 0) {
    124             limit = DEFAULT_SEARCH_LIMIT;
    125         }
    126         int action = getQueryParamater(uri, SUGGEST_PARAMETER_ACTION, DEFAULT_SEARCH_ACTION);
    127         if (action < SearchInterface.ACTION_TYPE_START
    128                 || action > SearchInterface.ACTION_TYPE_END) {
    129             action = DEFAULT_SEARCH_ACTION;
    130         }
    131         List<SearchResult> results = new ArrayList<>();
    132         if (!TextUtils.isEmpty(query)) {
    133             results.addAll(search.search(query, limit, action));
    134         }
    135         Cursor c = createSuggestionsCursor(results);
    136         if (DEBUG) {
    137             Log.d(TAG, "Elapsed time(count=" + c.getCount() + "): "
    138                     + (SystemClock.elapsedRealtime() - time) + "(msec)");
    139         }
    140         mPerformanceMonitor.stopTimer(queryTimer, EventNames.ON_DEVICE_SEARCH);
    141         return c;
    142     }
    143 
    144     private int getQueryParamater(Uri uri, String key, int defaultValue) {
    145         try {
    146             return Integer.parseInt(uri.getQueryParameter(key));
    147         } catch (NumberFormatException | UnsupportedOperationException e) {
    148             // Ignore the exceptions
    149         }
    150         return defaultValue;
    151     }
    152 
    153     private Cursor createSuggestionsCursor(List<SearchResult> results) {
    154         MatrixCursor cursor = new MatrixCursor(SEARCHABLE_COLUMNS, results.size());
    155         List<String> row = new ArrayList<>(SEARCHABLE_COLUMNS.length);
    156 
    157         int index = 0;
    158         for (SearchResult result : results) {
    159             row.clear();
    160             row.add(result.title);
    161             row.add(result.description);
    162             row.add(result.imageUri);
    163             row.add(result.intentAction);
    164             row.add(result.intentData);
    165             row.add(result.contentType);
    166             row.add(result.isLive ? LIVE_CONTENTS : NO_LIVE_CONTENTS);
    167             row.add(result.videoWidth == 0 ? null : String.valueOf(result.videoWidth));
    168             row.add(result.videoHeight == 0 ? null : String.valueOf(result.videoHeight));
    169             row.add(result.duration == 0 ? null : String.valueOf(result.duration));
    170             row.add(String.valueOf(result.progressPercentage));
    171             cursor.addRow(row);
    172             if (DEBUG) Log.d(TAG, "Result[" + (++index) + "]: " + result);
    173         }
    174         return cursor;
    175     }
    176 
    177     @Override
    178     public String getType(Uri uri) {
    179         if (!checkUriCorrect(uri)) return null;
    180         return SearchManager.SUGGEST_MIME_TYPE;
    181     }
    182 
    183     private static boolean checkUriCorrect(Uri uri) {
    184         return uri != null && uri.getPath().startsWith(EXPECTED_PATH_PREFIX);
    185     }
    186 
    187     @Override
    188     public Uri insert(Uri uri, ContentValues values) {
    189         throw new UnsupportedOperationException();
    190     }
    191 
    192     @Override
    193     public int delete(Uri uri, String selection, String[] selectionArgs) {
    194         throw new UnsupportedOperationException();
    195     }
    196 
    197     @Override
    198     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    199         throw new UnsupportedOperationException();
    200     }
    201 
    202     /**
    203      * A placeholder to a search result.
    204      */
    205     public static class SearchResult {
    206         public long channelId;
    207         public String channelNumber;
    208         public String title;
    209         public String description;
    210         public String imageUri;
    211         public String intentAction;
    212         public String intentData;
    213         public String contentType;
    214         public boolean isLive;
    215         public int videoWidth;
    216         public int videoHeight;
    217         public long duration;
    218         public int progressPercentage;
    219 
    220         @Override
    221         public String toString() {
    222             return "SearchResult{channelId=" + channelId +
    223                     ", channelNumber=" + channelNumber +
    224                     ", title=" + title +
    225                     ", description=" + description +
    226                     ", imageUri=" + imageUri +
    227                     ", intentAction=" + intentAction +
    228                     ", intentData=" + intentData +
    229                     ", contentType=" + contentType +
    230                     ", isLive=" + isLive +
    231                     ", videoWidth=" + videoWidth +
    232                     ", videoHeight=" + videoHeight +
    233                     ", duration=" + duration +
    234                     ", progressPercentage=" + progressPercentage +
    235                     "}";
    236         }
    237     }
    238 }