• 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.os.SystemClock;
26 import android.support.annotation.MainThread;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import com.android.tv.TvSingletons;
30 import com.android.tv.data.ChannelDataManager;
31 import com.android.tv.data.Program;
32 import com.android.tv.data.ProgramDataManager;
33 import com.android.tv.data.api.Channel;
34 import com.android.tv.search.LocalSearchProvider.SearchResult;
35 import com.android.tv.util.MainThreadExecutor;
36 import com.android.tv.util.Utils;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Set;
42 import java.util.concurrent.Callable;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.Future;
45 
46 /**
47  * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager} and
48  * {@link ProgramDataManager}.
49  */
50 public class DataManagerSearch implements SearchInterface {
51     private static final String TAG = "DataManagerSearch";
52     private static final boolean DEBUG = false;
53 
54     private final Context mContext;
55     private final TvInputManager mTvInputManager;
56     private final ChannelDataManager mChannelDataManager;
57     private final ProgramDataManager mProgramDataManager;
58 
DataManagerSearch(Context context)59     DataManagerSearch(Context context) {
60         mContext = context;
61         mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
62         TvSingletons tvSingletons = TvSingletons.getSingletons(context);
63         mChannelDataManager = tvSingletons.getChannelDataManager();
64         mProgramDataManager = tvSingletons.getProgramDataManager();
65     }
66 
67     @Override
search(final String query, final int limit, final int action)68     public List<SearchResult> search(final String query, final int limit, final int action) {
69         Future<List<SearchResult>> future =
70                 MainThreadExecutor.getInstance()
71                         .submit(
72                                 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         // TODO(b/72499165): add a test.
93         List<SearchResult> results = new ArrayList<>();
94         if (!mChannelDataManager.isDbLoadFinished()) {
95             return results;
96         }
97         if (action == ACTION_TYPE_SWITCH_CHANNEL || action == ACTION_TYPE_SWITCH_INPUT) {
98             // Voice search query should be handled by the a system TV app.
99             return results;
100         }
101         if (DEBUG) Log.d(TAG, "Searching channels: '" + query + "'");
102         long time = SystemClock.elapsedRealtime();
103         Set<Long> channelsFound = new HashSet<>();
104         List<Channel> channelList = mChannelDataManager.getBrowsableChannelList();
105         query = query.toLowerCase();
106         if (TextUtils.isDigitsOnly(query)) {
107             for (Channel channel : channelList) {
108                 if (channelsFound.contains(channel.getId())) {
109                     continue;
110                 }
111                 if (contains(channel.getDisplayNumber(), query)) {
112                     addResult(results, channelsFound, channel, null);
113                 }
114                 if (results.size() >= limit) {
115                     if (DEBUG) {
116                         Log.d(
117                                 TAG,
118                                 "Found "
119                                         + results.size()
120                                         + " channels. Elapsed time for"
121                                         + " searching channels: "
122                                         + (SystemClock.elapsedRealtime() - time)
123                                         + "(msec)");
124                     }
125                     return results;
126                 }
127             }
128             // TODO: recently watched channels may have higher priority.
129         }
130         for (Channel channel : channelList) {
131             if (channelsFound.contains(channel.getId())) {
132                 continue;
133             }
134             if (contains(channel.getDisplayName(), query)
135                     || contains(channel.getDescription(), query)) {
136                 addResult(results, channelsFound, channel, null);
137             }
138             if (results.size() >= limit) {
139                 if (DEBUG) {
140                     Log.d(
141                             TAG,
142                             "Found "
143                                     + results.size()
144                                     + " channels. Elapsed time for"
145                                     + " searching channels: "
146                                     + (SystemClock.elapsedRealtime() - time)
147                                     + "(msec)");
148                 }
149                 return results;
150             }
151         }
152         if (DEBUG) {
153             Log.d(
154                     TAG,
155                     "Found "
156                             + results.size()
157                             + " channels. Elapsed time for"
158                             + " searching channels: "
159                             + (SystemClock.elapsedRealtime() - time)
160                             + "(msec)");
161         }
162         int channelResult = results.size();
163         if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
164         time = SystemClock.elapsedRealtime();
165         for (Channel channel : channelList) {
166             if (channelsFound.contains(channel.getId())) {
167                 continue;
168             }
169             Program program = mProgramDataManager.getCurrentProgram(channel.getId());
170             if (program == null) {
171                 continue;
172             }
173             if (contains(program.getTitle(), query)
174                     && !isRatingBlocked(program.getContentRatings())) {
175                 addResult(results, channelsFound, channel, program);
176             }
177             if (results.size() >= limit) {
178                 if (DEBUG) {
179                     Log.d(
180                             TAG,
181                             "Found "
182                                     + (results.size() - channelResult)
183                                     + " programs. Elapsed"
184                                     + " time for searching programs: "
185                                     + (SystemClock.elapsedRealtime() - time)
186                                     + "(msec)");
187                 }
188                 return results;
189             }
190         }
191         for (Channel channel : channelList) {
192             if (channelsFound.contains(channel.getId())) {
193                 continue;
194             }
195             Program program = mProgramDataManager.getCurrentProgram(channel.getId());
196             if (program == null) {
197                 continue;
198             }
199             if (contains(program.getDescription(), query)
200                     && !isRatingBlocked(program.getContentRatings())) {
201                 addResult(results, channelsFound, channel, program);
202             }
203             if (results.size() >= limit) {
204                 if (DEBUG) {
205                     Log.d(
206                             TAG,
207                             "Found "
208                                     + (results.size() - channelResult)
209                                     + " programs. Elapsed"
210                                     + " time for searching programs: "
211                                     + (SystemClock.elapsedRealtime() - time)
212                                     + "(msec)");
213                 }
214                 return results;
215             }
216         }
217         if (DEBUG) {
218             Log.d(
219                     TAG,
220                     "Found "
221                             + (results.size() - channelResult)
222                             + " programs. Elapsed time for"
223                             + " searching programs: "
224                             + (SystemClock.elapsedRealtime() - time)
225                             + "(msec)");
226         }
227         return results;
228     }
229 
230     // It assumes that query is already lower cases.
contains(String string, String query)231     private boolean contains(String string, String query) {
232         return string != null && string.toLowerCase().contains(query);
233     }
234 
235     /** If query is matched to channel, {@code program} should be null. */
addResult( List<SearchResult> results, Set<Long> channelsFound, Channel channel, Program program)236     private void addResult(
237             List<SearchResult> results, Set<Long> channelsFound, Channel channel, Program program) {
238         if (program == null) {
239             program = mProgramDataManager.getCurrentProgram(channel.getId());
240             if (program != null && isRatingBlocked(program.getContentRatings())) {
241                 program = null;
242             }
243         }
244 
245         SearchResult.Builder result = SearchResult.builder();
246 
247         long channelId = channel.getId();
248         result.setChannelId(channelId);
249         result.setChannelNumber(channel.getDisplayNumber());
250         if (program == null) {
251             result.setTitle(channel.getDisplayName());
252             result.setDescription(channel.getDescription());
253             result.setImageUri(TvContract.buildChannelLogoUri(channelId).toString());
254             result.setIntentAction(Intent.ACTION_VIEW);
255             result.setIntentData(buildIntentData(channelId));
256             result.setContentType(Programs.CONTENT_ITEM_TYPE);
257             result.setIsLive(true);
258             result.setProgressPercentage(LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE);
259         } else {
260             result.setTitle(program.getTitle());
261             result.setDescription(
262                     buildProgramDescription(
263                             channel.getDisplayNumber(),
264                             channel.getDisplayName(),
265                             program.getStartTimeUtcMillis(),
266                             program.getEndTimeUtcMillis()));
267             result.setImageUri(program.getPosterArtUri());
268             result.setIntentAction(Intent.ACTION_VIEW);
269             result.setIntentData(buildIntentData(channelId));
270             result.setIntentExtraData(TvContract.buildProgramUri(program.getId()).toString());
271             result.setContentType(Programs.CONTENT_ITEM_TYPE);
272             result.setIsLive(true);
273             result.setVideoWidth(program.getVideoWidth());
274             result.setVideoHeight(program.getVideoHeight());
275             result.setDuration(program.getDurationMillis());
276             result.setProgressPercentage(
277                     getProgressPercentage(
278                             program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()));
279         }
280         if (DEBUG) {
281             Log.d(TAG, "Add a result : channel=" + channel + " program=" + program);
282         }
283         results.add(result.build());
284         channelsFound.add(channel.getId());
285     }
286 
buildProgramDescription( String channelNumber, String channelName, long programStartUtcMillis, long programEndUtcMillis)287     private String buildProgramDescription(
288             String channelNumber,
289             String channelName,
290             long programStartUtcMillis,
291             long programEndUtcMillis) {
292         return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
293                 + System.lineSeparator()
294                 + channelNumber
295                 + " "
296                 + channelName;
297     }
298 
getProgressPercentage(long startUtcMillis, long endUtcMillis)299     private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
300         long current = System.currentTimeMillis();
301         if (startUtcMillis > current || endUtcMillis <= current) {
302             return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
303         }
304         return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
305     }
306 
buildIntentData(long channelId)307     private String buildIntentData(long channelId) {
308         return TvContract.buildChannelUri(channelId).toString();
309     }
310 
isRatingBlocked(TvContentRating[] ratings)311     private boolean isRatingBlocked(TvContentRating[] ratings) {
312         if (ratings == null
313                 || ratings.length == 0
314                 || !mTvInputManager.isParentalControlsEnabled()) {
315             return false;
316         }
317         for (TvContentRating rating : ratings) {
318             try {
319                 if (mTvInputManager.isRatingBlocked(rating)) {
320                     return true;
321                 }
322             } catch (IllegalArgumentException e) {
323                 // Do nothing.
324             }
325         }
326         return false;
327     }
328 }
329