• 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 com.google.common.collect.ImmutableList;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
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(() -> searchFromDataManagers(query, limit, action));
72 
73         try {
74             return future.get();
75         } catch (InterruptedException e) {
76             Thread.interrupted();
77             return Collections.EMPTY_LIST;
78         } catch (ExecutionException e) {
79             Log.w(TAG, "Error searching for " + query, e);
80             return Collections.EMPTY_LIST;
81         }
82     }
83 
84     @MainThread
searchFromDataManagers(String query, int limit, int action)85     private List<SearchResult> searchFromDataManagers(String query, int limit, int action) {
86         // TODO(b/72499165): add a test.
87         List<SearchResult> results = new ArrayList<>();
88         if (!mChannelDataManager.isDbLoadFinished()) {
89             return results;
90         }
91         if (action == ACTION_TYPE_SWITCH_CHANNEL || action == ACTION_TYPE_SWITCH_INPUT) {
92             // Voice search query should be handled by the a system TV app.
93             return results;
94         }
95         if (DEBUG) Log.d(TAG, "Searching channels: '" + query + "'");
96         long time = SystemClock.elapsedRealtime();
97         Set<Long> channelsFound = new HashSet<>();
98         List<Channel> channelList = mChannelDataManager.getBrowsableChannelList();
99         query = query.toLowerCase();
100         if (TextUtils.isDigitsOnly(query)) {
101             for (Channel channel : channelList) {
102                 if (channelsFound.contains(channel.getId())) {
103                     continue;
104                 }
105                 if (contains(channel.getDisplayNumber(), query)) {
106                     addResult(results, channelsFound, channel, null);
107                 }
108                 if (results.size() >= limit) {
109                     if (DEBUG) {
110                         Log.d(
111                                 TAG,
112                                 "Found "
113                                         + results.size()
114                                         + " channels. Elapsed time for"
115                                         + " searching channels: "
116                                         + (SystemClock.elapsedRealtime() - time)
117                                         + "(msec)");
118                     }
119                     return results;
120                 }
121             }
122             // TODO: recently watched channels may have higher priority.
123         }
124         for (Channel channel : channelList) {
125             if (channelsFound.contains(channel.getId())) {
126                 continue;
127             }
128             if (contains(channel.getDisplayName(), query)
129                     || contains(channel.getDescription(), query)) {
130                 addResult(results, channelsFound, channel, null);
131             }
132             if (results.size() >= limit) {
133                 if (DEBUG) {
134                     Log.d(
135                             TAG,
136                             "Found "
137                                     + results.size()
138                                     + " channels. Elapsed time for"
139                                     + " searching channels: "
140                                     + (SystemClock.elapsedRealtime() - time)
141                                     + "(msec)");
142                 }
143                 return results;
144             }
145         }
146         if (DEBUG) {
147             Log.d(
148                     TAG,
149                     "Found "
150                             + results.size()
151                             + " channels. Elapsed time for"
152                             + " searching channels: "
153                             + (SystemClock.elapsedRealtime() - time)
154                             + "(msec)");
155         }
156         int channelResult = results.size();
157         if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
158         time = SystemClock.elapsedRealtime();
159         for (Channel channel : channelList) {
160             if (channelsFound.contains(channel.getId())) {
161                 continue;
162             }
163             Program program = mProgramDataManager.getCurrentProgram(channel.getId());
164             if (program == null) {
165                 continue;
166             }
167             if (contains(program.getTitle(), query)
168                     && !isRatingBlocked(program.getContentRatings())) {
169                 addResult(results, channelsFound, channel, program);
170             }
171             if (results.size() >= limit) {
172                 if (DEBUG) {
173                     Log.d(
174                             TAG,
175                             "Found "
176                                     + (results.size() - channelResult)
177                                     + " programs. Elapsed"
178                                     + " time for searching programs: "
179                                     + (SystemClock.elapsedRealtime() - time)
180                                     + "(msec)");
181                 }
182                 return results;
183             }
184         }
185         for (Channel channel : channelList) {
186             if (channelsFound.contains(channel.getId())) {
187                 continue;
188             }
189             Program program = mProgramDataManager.getCurrentProgram(channel.getId());
190             if (program == null) {
191                 continue;
192             }
193             if (contains(program.getDescription(), query)
194                     && !isRatingBlocked(program.getContentRatings())) {
195                 addResult(results, channelsFound, channel, program);
196             }
197             if (results.size() >= limit) {
198                 if (DEBUG) {
199                     Log.d(
200                             TAG,
201                             "Found "
202                                     + (results.size() - channelResult)
203                                     + " programs. Elapsed"
204                                     + " time for searching programs: "
205                                     + (SystemClock.elapsedRealtime() - time)
206                                     + "(msec)");
207                 }
208                 return results;
209             }
210         }
211         if (DEBUG) {
212             Log.d(
213                     TAG,
214                     "Found "
215                             + (results.size() - channelResult)
216                             + " programs. Elapsed time for"
217                             + " searching programs: "
218                             + (SystemClock.elapsedRealtime() - time)
219                             + "(msec)");
220         }
221         return results;
222     }
223 
224     // It assumes that query is already lower cases.
contains(String string, String query)225     private boolean contains(String string, String query) {
226         return string != null && string.toLowerCase().contains(query);
227     }
228 
229     /** If query is matched to channel, {@code program} should be null. */
addResult( List<SearchResult> results, Set<Long> channelsFound, Channel channel, Program program)230     private void addResult(
231             List<SearchResult> results, Set<Long> channelsFound, Channel channel, Program program) {
232         if (program == null) {
233             program = mProgramDataManager.getCurrentProgram(channel.getId());
234             if (program != null && isRatingBlocked(program.getContentRatings())) {
235                 program = null;
236             }
237         }
238 
239         SearchResult.Builder result = SearchResult.builder();
240 
241         long channelId = channel.getId();
242         result.setChannelId(channelId);
243         result.setChannelNumber(channel.getDisplayNumber());
244         if (program == null) {
245             result.setTitle(channel.getDisplayName());
246             result.setDescription(channel.getDescription());
247             result.setImageUri(TvContract.buildChannelLogoUri(channelId).toString());
248             result.setIntentAction(Intent.ACTION_VIEW);
249             result.setIntentData(buildIntentData(channelId));
250             result.setContentType(Programs.CONTENT_ITEM_TYPE);
251             result.setIsLive(true);
252             result.setProgressPercentage(SearchInterface.PROGRESS_PERCENTAGE_HIDE);
253         } else {
254             result.setTitle(program.getTitle());
255             result.setDescription(
256                     buildProgramDescription(
257                             channel.getDisplayNumber(),
258                             channel.getDisplayName(),
259                             program.getStartTimeUtcMillis(),
260                             program.getEndTimeUtcMillis()));
261             result.setImageUri(program.getPosterArtUri());
262             result.setIntentAction(Intent.ACTION_VIEW);
263             result.setIntentData(buildIntentData(channelId));
264             result.setIntentExtraData(TvContract.buildProgramUri(program.getId()).toString());
265             result.setContentType(Programs.CONTENT_ITEM_TYPE);
266             result.setIsLive(true);
267             result.setVideoWidth(program.getVideoWidth());
268             result.setVideoHeight(program.getVideoHeight());
269             result.setDuration(program.getDurationMillis());
270             result.setProgressPercentage(
271                     getProgressPercentage(
272                             program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()));
273         }
274         if (DEBUG) {
275             Log.d(TAG, "Add a result : channel=" + channel + " program=" + program);
276         }
277         results.add(result.build());
278         channelsFound.add(channel.getId());
279     }
280 
buildProgramDescription( String channelNumber, String channelName, long programStartUtcMillis, long programEndUtcMillis)281     private String buildProgramDescription(
282             String channelNumber,
283             String channelName,
284             long programStartUtcMillis,
285             long programEndUtcMillis) {
286         return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
287                 + System.lineSeparator()
288                 + channelNumber
289                 + " "
290                 + channelName;
291     }
292 
getProgressPercentage(long startUtcMillis, long endUtcMillis)293     private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
294         long current = System.currentTimeMillis();
295         if (startUtcMillis > current || endUtcMillis <= current) {
296             return SearchInterface.PROGRESS_PERCENTAGE_HIDE;
297         }
298         return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
299     }
300 
buildIntentData(long channelId)301     private String buildIntentData(long channelId) {
302         return TvContract.buildChannelUri(channelId).toString();
303     }
304 
isRatingBlocked(ImmutableList<TvContentRating> ratings)305     private boolean isRatingBlocked(ImmutableList<TvContentRating> ratings) {
306         if (ratings == null || ratings.isEmpty() || !mTvInputManager.isParentalControlsEnabled()) {
307             return false;
308         }
309         for (TvContentRating rating : ratings) {
310             try {
311                 if (mTvInputManager.isRatingBlocked(rating)) {
312                     return true;
313                 }
314             } catch (IllegalArgumentException e) {
315                 // Do nothing.
316             }
317         }
318         return false;
319     }
320 }
321