• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
20 import android.content.Intent;
21 import android.media.tv.TvContentRating;
22 import android.media.tv.TvContract;
23 import android.media.tv.TvContract.Programs;
24 import android.media.tv.TvInputManager;
25 import android.support.annotation.MainThread;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.tv.ApplicationSingletons;
30 import com.android.tv.TvApplication;
31 import com.android.tv.data.Channel;
32 import com.android.tv.data.ChannelDataManager;
33 import com.android.tv.data.Program;
34 import com.android.tv.data.ProgramDataManager;
35 import com.android.tv.search.LocalSearchProvider.SearchResult;
36 import com.android.tv.util.MainThreadExecutor;
37 import com.android.tv.util.Utils;
38 
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 import java.util.concurrent.Callable;
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.Future;
47 
48 /**
49  * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager}
50  * and {@link ProgramDataManager}.
51  */
52 public class DataManagerSearch implements SearchInterface {
53     private static final boolean DEBUG = false;
54     private static final String TAG = "TvProviderSearch";
55 
56     private final Context mContext;
57     private final TvInputManager mTvInputManager;
58     private final ChannelDataManager mChannelDataManager;
59     private final ProgramDataManager mProgramDataManager;
60 
DataManagerSearch(Context context)61     DataManagerSearch(Context context) {
62         mContext = context;
63         mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
64         ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
65         mChannelDataManager = appSingletons.getChannelDataManager();
66         mProgramDataManager = appSingletons.getProgramDataManager();
67     }
68 
69     @Override
search(final String query, final int limit, final int action)70     public List<SearchResult> search(final String query, final int limit, final int action) {
71         Future<List<SearchResult>> future = MainThreadExecutor.getInstance()
72                 .submit(new Callable<List<SearchResult>>() {
73                     @Override
74                     public List<SearchResult> call() throws Exception {
75                         return searchFromDataManagers(query, limit, action);
76                     }
77                 });
78 
79         try {
80             return future.get();
81         } catch (InterruptedException e) {
82             Thread.interrupted();
83             return Collections.EMPTY_LIST;
84         } catch (ExecutionException e) {
85             Log.w(TAG, "Error searching for " + query, e);
86             return Collections.EMPTY_LIST;
87         }
88     }
89 
90     @MainThread
searchFromDataManagers(String query, int limit, int action)91     private List<SearchResult> searchFromDataManagers(String query, int limit, int action) {
92         List<SearchResult> results = new ArrayList<>();
93         if (!mChannelDataManager.isDbLoadFinished()) {
94             return results;
95         }
96         if (action == ACTION_TYPE_SWITCH_CHANNEL
97                 || action == ACTION_TYPE_SWITCH_INPUT) {
98             // Voice search query should be handled by the a system TV app.
99             return results;
100         }
101         Set<Long> channelsFound = new HashSet<>();
102         List<Channel> channelList = mChannelDataManager.getBrowsableChannelList();
103         query = query.toLowerCase();
104         if (TextUtils.isDigitsOnly(query)) {
105             for (Channel channel : channelList) {
106                 if (channelsFound.contains(channel.getId())) {
107                     continue;
108                 }
109                 if (contains(channel.getDisplayNumber(), query)) {
110                     addResult(results, channelsFound, channel, null);
111                 }
112                 if (results.size() >= limit) {
113                     return results;
114                 }
115             }
116             // TODO: recently watched channels may have higher priority.
117         }
118         for (Channel channel : channelList) {
119             if (channelsFound.contains(channel.getId())) {
120                 continue;
121             }
122             if (contains(channel.getDisplayName(), query)
123                     || contains(channel.getDescription(), query)) {
124                 addResult(results, channelsFound, channel, null);
125             }
126             if (results.size() >= limit) {
127                 return results;
128             }
129         }
130         for (Channel channel : channelList) {
131             if (channelsFound.contains(channel.getId())) {
132                 continue;
133             }
134             Program program = mProgramDataManager.getCurrentProgram(channel.getId());
135             if (program == null) {
136                 continue;
137             }
138             if (contains(program.getTitle(), query)
139                     && !isRatingBlocked(program.getContentRatings())) {
140                 addResult(results, channelsFound, channel, program);
141             }
142             if (results.size() >= limit) {
143                 return results;
144             }
145         }
146         for (Channel channel : channelList) {
147             if (channelsFound.contains(channel.getId())) {
148                 continue;
149             }
150             Program program = mProgramDataManager.getCurrentProgram(channel.getId());
151             if (program == null) {
152                 continue;
153             }
154             if (contains(program.getDescription(), query)
155                     && !isRatingBlocked(program.getContentRatings())) {
156                 addResult(results, channelsFound, channel, program);
157             }
158             if (results.size() >= limit) {
159                 return results;
160             }
161         }
162         return results;
163     }
164 
165     // It assumes that query is already lower cases.
contains(String string, String query)166     private boolean contains(String string, String query) {
167         return string != null && string.toLowerCase().contains(query);
168     }
169 
170     /**
171      * If query is matched to channel, {@code program} should be null.
172      */
addResult(List<SearchResult> results, Set<Long> channelsFound, Channel channel, Program program)173     private void addResult(List<SearchResult> results, Set<Long> channelsFound, Channel channel,
174             Program program) {
175         if (program == null) {
176             program = mProgramDataManager.getCurrentProgram(channel.getId());
177             if (program != null && isRatingBlocked(program.getContentRatings())) {
178                 program = null;
179             }
180         }
181 
182         SearchResult result = new SearchResult();
183 
184         long channelId = channel.getId();
185         result.channelId = channelId;
186         result.channelNumber = channel.getDisplayNumber();
187         if (program == null) {
188             result.title = channel.getDisplayName();
189             result.description = channel.getDescription();
190             result.imageUri = TvContract.buildChannelLogoUri(channelId).toString();
191             result.intentAction = Intent.ACTION_VIEW;
192             result.intentData = buildIntentData(channelId);
193             result.contentType = Programs.CONTENT_ITEM_TYPE;
194             result.isLive = true;
195             result.progressPercentage = LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
196         } else {
197             result.title = program.getTitle();
198             result.description = buildProgramDescription(channel.getDisplayNumber(),
199                     channel.getDisplayName(), program.getStartTimeUtcMillis(),
200                     program.getEndTimeUtcMillis());
201             result.imageUri = program.getPosterArtUri();
202             result.intentAction = Intent.ACTION_VIEW;
203             result.intentData = buildIntentData(channelId);
204             result.contentType = Programs.CONTENT_ITEM_TYPE;
205             result.isLive = true;
206             result.videoWidth = program.getVideoWidth();
207             result.videoHeight = program.getVideoHeight();
208             result.duration = program.getDurationMillis();
209             result.progressPercentage = getProgressPercentage(
210                     program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis());
211         }
212         if (DEBUG) {
213             Log.d(TAG, "Add a result : channel=" + channel + " program=" + program);
214         }
215         results.add(result);
216         channelsFound.add(channel.getId());
217     }
218 
buildProgramDescription(String channelNumber, String channelName, long programStartUtcMillis, long programEndUtcMillis)219     private String buildProgramDescription(String channelNumber, String channelName,
220             long programStartUtcMillis, long programEndUtcMillis) {
221         return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
222                 + System.lineSeparator() + channelNumber + " " + channelName;
223     }
224 
getProgressPercentage(long startUtcMillis, long endUtcMillis)225     private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
226         long current = System.currentTimeMillis();
227         if (startUtcMillis > current || endUtcMillis <= current) {
228             return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
229         }
230         return (int)(100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
231     }
232 
buildIntentData(long channelId)233     private String buildIntentData(long channelId) {
234         return TvContract.buildChannelUri(channelId).buildUpon()
235                 .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH)
236                 .build().toString();
237     }
238 
isRatingBlocked(TvContentRating[] ratings)239     private boolean isRatingBlocked(TvContentRating[] ratings) {
240         if (ratings == null || ratings.length == 0
241                 || !mTvInputManager.isParentalControlsEnabled()) {
242             return false;
243         }
244         for (TvContentRating rating : ratings) {
245             try {
246                 if (mTvInputManager.isRatingBlocked(rating)) {
247                     return true;
248                 }
249             } catch (IllegalArgumentException e) {
250                 // Do nothing.
251             }
252         }
253         return false;
254     }
255 }
256