| /* |
| * Copyright (C) 2015 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.tv.search; |
| |
| import android.app.SearchManager; |
| import android.content.ContentValues; |
| import android.database.Cursor; |
| import android.database.MatrixCursor; |
| import android.net.Uri; |
| import android.os.SystemClock; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.annotation.VisibleForTesting; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.tv.common.CommonConstants; |
| import com.android.tv.common.SoftPreconditions; |
| import com.android.tv.common.dagger.init.SafePreDaggerInitializer; |
| import com.android.tv.common.util.CommonUtils; |
| import com.android.tv.common.util.PermissionUtils; |
| import com.android.tv.perf.EventNames; |
| import com.android.tv.perf.PerformanceMonitor; |
| import com.android.tv.perf.TimerEvent; |
| import com.android.tv.util.TvUriMatcher; |
| |
| import com.google.auto.value.AutoValue; |
| |
| import dagger.android.ContributesAndroidInjector; |
| import dagger.android.DaggerContentProvider; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| |
| /** Content provider for local search */ |
| public class LocalSearchProvider extends DaggerContentProvider { |
| private static final String TAG = "LocalSearchProvider"; |
| private static final boolean DEBUG = false; |
| |
| /** The authority for LocalSearchProvider. */ |
| public static final String AUTHORITY = CommonConstants.BASE_PACKAGE + ".search"; |
| |
| // TODO: Remove this once added to the SearchManager. |
| private static final String SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE = "progress_bar_percentage"; |
| |
| private static final String[] SEARCHABLE_COLUMNS = |
| new String[] { |
| SearchManager.SUGGEST_COLUMN_TEXT_1, |
| SearchManager.SUGGEST_COLUMN_TEXT_2, |
| SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE, |
| SearchManager.SUGGEST_COLUMN_INTENT_ACTION, |
| SearchManager.SUGGEST_COLUMN_INTENT_DATA, |
| SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, |
| SearchManager.SUGGEST_COLUMN_CONTENT_TYPE, |
| SearchManager.SUGGEST_COLUMN_IS_LIVE, |
| SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH, |
| SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT, |
| SearchManager.SUGGEST_COLUMN_DURATION, |
| SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE |
| }; |
| |
| private static final String EXPECTED_PATH_PREFIX = "/" + SearchManager.SUGGEST_URI_PATH_QUERY; |
| static final String SUGGEST_PARAMETER_ACTION = "action"; |
| // The launcher passes 10 as a 'limit' parameter by default. |
| @VisibleForTesting static final int DEFAULT_SEARCH_LIMIT = 10; |
| |
| @VisibleForTesting |
| static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS; |
| |
| private static final String NO_LIVE_CONTENTS = "0"; |
| private static final String LIVE_CONTENTS = "1"; |
| |
| @Inject PerformanceMonitor mPerformanceMonitor; |
| |
| /** Used only for testing */ |
| private SearchInterface mSearchInterface; |
| |
| @Override |
| public boolean onCreate() { |
| SafePreDaggerInitializer.init(getContext()); |
| if (!super.onCreate()) { |
| Log.e(TAG, "LocalSearchProvider.onCreate() failed."); |
| return false; |
| } |
| return true; |
| } |
| |
| @VisibleForTesting |
| void setSearchInterface(SearchInterface searchInterface) { |
| SoftPreconditions.checkState(CommonUtils.isRunningInTest()); |
| mSearchInterface = searchInterface; |
| } |
| |
| @Override |
| public Cursor query( |
| @NonNull Uri uri, |
| String[] projection, |
| String selection, |
| String[] selectionArgs, |
| String sortOrder) { |
| if (TvUriMatcher.match(uri) != TvUriMatcher.MATCH_ON_DEVICE_SEARCH) { |
| throw new IllegalArgumentException("Unknown URI: " + uri); |
| } |
| TimerEvent queryTimer = mPerformanceMonitor.startTimer(); |
| if (DEBUG) { |
| Log.d( |
| TAG, |
| "query(" |
| + uri |
| + ", " |
| + Arrays.toString(projection) |
| + ", " |
| + selection |
| + ", " |
| + Arrays.toString(selectionArgs) |
| + ", " |
| + sortOrder |
| + ")"); |
| } |
| long time = SystemClock.elapsedRealtime(); |
| SearchInterface search = mSearchInterface; |
| if (search == null) { |
| if (PermissionUtils.hasAccessAllEpg(getContext())) { |
| if (DEBUG) Log.d(TAG, "Performing TV Provider search."); |
| search = new TvProviderSearch(getContext()); |
| } else { |
| if (DEBUG) Log.d(TAG, "Performing Data Manager search."); |
| search = new DataManagerSearch(getContext()); |
| } |
| } |
| String query = uri.getLastPathSegment(); |
| int limit = |
| getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT, DEFAULT_SEARCH_LIMIT); |
| if (limit <= 0) { |
| limit = DEFAULT_SEARCH_LIMIT; |
| } |
| int action = getQueryParamater(uri, SUGGEST_PARAMETER_ACTION, DEFAULT_SEARCH_ACTION); |
| if (action < SearchInterface.ACTION_TYPE_START |
| || action > SearchInterface.ACTION_TYPE_END) { |
| action = DEFAULT_SEARCH_ACTION; |
| } |
| List<SearchResult> results = new ArrayList<>(); |
| if (!TextUtils.isEmpty(query)) { |
| results.addAll(search.search(query, limit, action)); |
| } |
| Cursor c = createSuggestionsCursor(results); |
| if (DEBUG) { |
| Log.d( |
| TAG, |
| "Elapsed time(count=" |
| + c.getCount() |
| + "): " |
| + (SystemClock.elapsedRealtime() - time) |
| + "(msec)"); |
| } |
| mPerformanceMonitor.stopTimer(queryTimer, EventNames.ON_DEVICE_SEARCH); |
| return c; |
| } |
| |
| private int getQueryParamater(Uri uri, String key, int defaultValue) { |
| try { |
| return Integer.parseInt(uri.getQueryParameter(key)); |
| } catch (NumberFormatException | UnsupportedOperationException e) { |
| // Ignore the exceptions |
| } |
| return defaultValue; |
| } |
| |
| private Cursor createSuggestionsCursor(List<SearchResult> results) { |
| MatrixCursor cursor = new MatrixCursor(SEARCHABLE_COLUMNS, results.size()); |
| List<String> row = new ArrayList<>(SEARCHABLE_COLUMNS.length); |
| |
| int index = 0; |
| for (SearchResult result : results) { |
| row.clear(); |
| row.add(result.getTitle()); |
| row.add(result.getDescription()); |
| row.add(result.getImageUri()); |
| row.add(result.getIntentAction()); |
| row.add(result.getIntentData()); |
| row.add(result.getIntentExtraData()); |
| row.add(result.getContentType()); |
| row.add(result.getIsLive() ? LIVE_CONTENTS : NO_LIVE_CONTENTS); |
| row.add(result.getVideoWidth() == 0 ? null : String.valueOf(result.getVideoWidth())); |
| row.add(result.getVideoHeight() == 0 ? null : String.valueOf(result.getVideoHeight())); |
| row.add(result.getDuration() == 0 ? null : String.valueOf(result.getDuration())); |
| row.add(String.valueOf(result.getProgressPercentage())); |
| cursor.addRow(row); |
| if (DEBUG) Log.d(TAG, "Result[" + (++index) + "]: " + result); |
| } |
| return cursor; |
| } |
| |
| @Override |
| public String getType(Uri uri) { |
| if (!checkUriCorrect(uri)) return null; |
| return SearchManager.SUGGEST_MIME_TYPE; |
| } |
| |
| private static boolean checkUriCorrect(Uri uri) { |
| return uri != null && uri.getPath().startsWith(EXPECTED_PATH_PREFIX); |
| } |
| |
| @Override |
| public Uri insert(Uri uri, ContentValues values) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int delete(Uri uri, String selection, String[] selectionArgs) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** Module for {@link LocalSearchProvider} */ |
| @dagger.Module |
| public abstract static class Module { |
| @ContributesAndroidInjector |
| abstract LocalSearchProvider contributesLocalSearchProviderInjector(); |
| } |
| |
| /** A placeholder to a search result. */ |
| @AutoValue |
| public abstract static class SearchResult { |
| public static Builder builder() { |
| // primitive fields cannot be nullable. Set to default; |
| return new AutoValue_LocalSearchProvider_SearchResult.Builder() |
| .setChannelId(0) |
| .setIsLive(false) |
| .setVideoWidth(0) |
| .setVideoHeight(0) |
| .setDuration(0) |
| .setProgressPercentage(0); |
| } |
| |
| public abstract Builder toBuilder(); |
| |
| @AutoValue.Builder |
| abstract static class Builder { |
| abstract Builder setChannelId(long value); |
| |
| abstract Builder setChannelNumber(String value); |
| |
| abstract Builder setTitle(String value); |
| |
| abstract Builder setDescription(String value); |
| |
| abstract Builder setImageUri(String value); |
| |
| abstract Builder setIntentAction(String value); |
| |
| abstract Builder setIntentData(String value); |
| |
| abstract Builder setIntentExtraData(String value); |
| |
| abstract Builder setContentType(String value); |
| |
| abstract Builder setIsLive(boolean value); |
| |
| abstract Builder setVideoWidth(int value); |
| |
| abstract Builder setVideoHeight(int value); |
| |
| abstract Builder setDuration(long value); |
| |
| abstract Builder setProgressPercentage(int value); |
| |
| abstract SearchResult build(); |
| } |
| |
| abstract long getChannelId(); |
| |
| @Nullable |
| abstract String getChannelNumber(); |
| |
| @Nullable |
| abstract String getTitle(); |
| |
| @Nullable |
| abstract String getDescription(); |
| |
| @Nullable |
| abstract String getImageUri(); |
| |
| @Nullable |
| abstract String getIntentAction(); |
| |
| @Nullable |
| abstract String getIntentData(); |
| |
| @Nullable |
| abstract String getIntentExtraData(); |
| |
| @Nullable |
| abstract String getContentType(); |
| |
| abstract boolean getIsLive(); |
| |
| abstract int getVideoWidth(); |
| |
| abstract int getVideoHeight(); |
| |
| abstract long getDuration(); |
| |
| abstract int getProgressPercentage(); |
| } |
| } |