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