DataManagerSearch.java revision 0cc0713c1bf8027642987b750b80217569d2932a
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 17package com.android.tv.search; 18 19import android.content.Context; 20import android.content.Intent; 21import android.media.tv.TvContentRating; 22import android.media.tv.TvContract; 23import android.media.tv.TvContract.Programs; 24import android.media.tv.TvInputManager; 25import android.os.SystemClock; 26import android.support.annotation.MainThread; 27import android.text.TextUtils; 28import android.util.Log; 29import com.android.tv.TvSingletons; 30import com.android.tv.data.ChannelDataManager; 31import com.android.tv.data.Program; 32import com.android.tv.data.ProgramDataManager; 33import com.android.tv.data.api.Channel; 34import com.android.tv.search.LocalSearchProvider.SearchResult; 35import com.android.tv.util.MainThreadExecutor; 36import com.android.tv.util.Utils; 37import java.util.ArrayList; 38import java.util.Collections; 39import java.util.HashSet; 40import java.util.List; 41import java.util.Set; 42import java.util.concurrent.Callable; 43import java.util.concurrent.ExecutionException; 44import java.util.concurrent.Future; 45 46/** 47 * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager} and 48 * {@link ProgramDataManager}. 49 */ 50public 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 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 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 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. 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. */ 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 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 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 307 private String buildIntentData(long channelId) { 308 return TvContract.buildChannelUri(channelId).toString(); 309 } 310 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