TvProviderSearch.java revision 8294723aefc8341646c66f378a02098db95716de
1/* 2 * Copyright (C) 2014 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.ContentUris; 20import android.content.Context; 21import android.content.Intent; 22import android.database.Cursor; 23import android.media.tv.TvContract.Channels; 24import android.media.tv.TvContract.Programs; 25import android.net.Uri; 26 27import com.android.internal.util.Preconditions; 28 29import java.util.ArrayList; 30import java.util.HashMap; 31import java.util.HashSet; 32import java.util.List; 33import java.util.Map; 34import java.util.Set; 35 36public class TvProviderSearch { 37 public static List<SearchResult> search(Context context, String query, int limit) { 38 List<SearchResult> results = new ArrayList<SearchResult>(); 39 results.addAll(searchChannels(context, query, new String[] { 40 Channels.COLUMN_DISPLAY_NAME, 41 Channels.COLUMN_DESCRIPTION 42 }, limit)); 43 if (results.size() >= limit) { 44 return results; 45 } 46 47 Set<Long> previousResults = getChannelIdSet(results); 48 limit -= results.size(); 49 results.addAll(searchPrograms(context, query, new String[] { 50 Programs.COLUMN_TITLE, 51 Programs.COLUMN_SHORT_DESCRIPTION 52 }, previousResults, limit)); 53 return results; 54 } 55 56 private static Set<Long> getChannelIdSet(List<SearchResult> results) { 57 Set<Long> channelIdSet = new HashSet<Long>(); 58 for (SearchResult sr : results) { 59 channelIdSet.add(sr.getChannelId()); 60 } 61 return channelIdSet; 62 } 63 64 private static List<SearchResult> searchChannels(Context context, String query, 65 String[] columnNames, int limit) { 66 Preconditions.checkState(columnNames != null && columnNames.length > 0); 67 68 String[] projection = { 69 Channels._ID, 70 Channels.COLUMN_DISPLAY_NAME, 71 Channels.COLUMN_DESCRIPTION, 72 }; 73 74 StringBuilder sb = new StringBuilder(); 75 sb.append(Channels.COLUMN_BROWSABLE).append("=1 AND "); 76 sb.append(Channels.COLUMN_SEARCHABLE).append("=1 AND ("); 77 sb.append(columnNames[0]).append(" like ?"); 78 for (int i = 1; i < columnNames.length; ++i) { 79 sb.append(" OR ").append(columnNames[i]).append(" like ?"); 80 } 81 sb.append(")"); 82 String selection = sb.toString(); 83 84 String selectionArg = "%" + query + "%"; 85 String[] selectionArgs = new String[columnNames.length]; 86 for (int i = 0; i < selectionArgs.length; ++i) { 87 selectionArgs[i] = selectionArg; 88 } 89 90 return search(context, Channels.CONTENT_URI, projection, selection, selectionArgs, limit, 91 null); 92 } 93 94 private static List<SearchResult> searchPrograms(final Context context, String query, 95 String[] columnNames, final Set<Long> previousResults, int limit) { 96 Preconditions.checkState(columnNames != null && columnNames.length > 0); 97 98 String[] projection = { 99 Programs.COLUMN_CHANNEL_ID, 100 Programs.COLUMN_TITLE, 101 Programs.COLUMN_SHORT_DESCRIPTION, 102 }; 103 104 StringBuilder sb = new StringBuilder(); 105 // Search among the programs which are now being on the air. 106 sb.append(Programs.COLUMN_START_TIME_UTC_MILLIS).append("<=? AND "); 107 sb.append(Programs.COLUMN_END_TIME_UTC_MILLIS).append(">=? AND ("); 108 sb.append(columnNames[0]).append(" like ?"); 109 for (int i = 1; i < columnNames.length; ++i) { 110 sb.append(" OR ").append(columnNames[0]).append(" like ?"); 111 } 112 sb.append(")"); 113 String selection = sb.toString(); 114 String selectionArg = "%" + query + "%"; 115 String[] selectionArgs = new String[columnNames.length+2]; 116 selectionArgs[0] = selectionArgs[1] = String.valueOf(System.currentTimeMillis()); 117 for (int i = 2; i < selectionArgs.length; ++i) { 118 selectionArgs[i] = selectionArg; 119 } 120 121 return search(context, Programs.CONTENT_URI, projection, selection, selectionArgs, limit, 122 new ResultFilter() { 123 private Map<Long, Boolean> searchableMap = new HashMap<Long, Boolean>(); 124 125 @Override 126 public boolean filter(Cursor c) { 127 long id = c.getLong(0); 128 // Filter out the program whose channel is already searched. 129 if (previousResults.contains(id)) { 130 return false; 131 } 132 // The channel is cached. 133 Boolean isSearchable = searchableMap.get(id); 134 if (isSearchable != null) { 135 return isSearchable; 136 } 137 138 // Don't know whether the channel is searchable or not. 139 String selection = Channels._ID + "=? AND " 140 + Channels.COLUMN_BROWSABLE + "=1 AND " 141 + Channels.COLUMN_SEARCHABLE + "=1"; 142 Cursor cursor = null; 143 try { 144 // Don't need to fetch all the columns. 145 cursor = context.getContentResolver().query(Channels.CONTENT_URI, 146 new String[] { Channels._ID }, selection, 147 new String[] { String.valueOf(id) }, null); 148 boolean isSearchableChannel = cursor != null && cursor.getCount() > 0; 149 searchableMap.put(id, isSearchableChannel); 150 return isSearchableChannel; 151 } finally { 152 if (cursor != null) { 153 cursor.close(); 154 } 155 } 156 } 157 }); 158 } 159 160 private static List<SearchResult> search(Context context, Uri uri, String[] projection, 161 String selection, String[] selectionArgs, int limit, ResultFilter resultFilter) { 162 List<SearchResult> results = new ArrayList<SearchResult>(); 163 164 Cursor cursor = null; 165 try { 166 cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 167 null); 168 if (cursor != null) { 169 // TODO: Need to add image when available. 170 int count = 0; 171 while (cursor.moveToNext()) { 172 if (resultFilter != null && !resultFilter.filter(cursor)) { 173 continue; 174 } 175 176 long id = cursor.getLong(0); 177 String title = cursor.getString(1); 178 String description = cursor.getString(2); 179 180 SearchResult result = SearchResult.builder() 181 .setChannelId(id) 182 .setTitle(title) 183 .setDescription(description) 184 .setIntentAction(Intent.ACTION_VIEW) 185 .setIntentData(ContentUris.withAppendedId(Channels.CONTENT_URI, id) 186 .toString()) 187 .build(); 188 results.add(result); 189 190 if (++count >= limit) { 191 break; 192 } 193 } 194 } 195 } finally { 196 if (cursor != null) { 197 cursor.close(); 198 } 199 } 200 201 return results; 202 } 203 204 private interface ResultFilter { 205 boolean filter(Cursor c); 206 } 207} 208