• 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.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.database.Cursor;
23 import android.media.tv.TvContentRating;
24 import android.media.tv.TvContract;
25 import android.media.tv.TvContract.Channels;
26 import android.media.tv.TvContract.Programs;
27 import android.media.tv.TvContract.WatchedPrograms;
28 import android.media.tv.TvInputInfo;
29 import android.media.tv.TvInputManager;
30 import android.net.Uri;
31 import android.os.SystemClock;
32 import android.support.annotation.WorkerThread;
33 import android.text.TextUtils;
34 import android.util.Log;
35 import com.android.tv.common.TvContentRatingCache;
36 import com.android.tv.common.util.PermissionUtils;
37 import com.android.tv.search.LocalSearchProvider.SearchResult;
38 import com.android.tv.util.Utils;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.Objects;
48 import java.util.Set;
49 import java.util.concurrent.TimeUnit;
50 
51 /** An implementation of {@link SearchInterface} to search query from TvProvider directly. */
52 public class TvProviderSearch implements SearchInterface {
53     private static final String TAG = "TvProviderSearch";
54     private static final boolean DEBUG = false;
55 
56     private static final long SEARCH_TIME_FRAME_MS = TimeUnit.DAYS.toMillis(14);
57 
58     private static final int NO_LIMIT = 0;
59 
60     private final Context mContext;
61     private final ContentResolver mContentResolver;
62     private final TvInputManager mTvInputManager;
63     private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
64 
TvProviderSearch(Context context)65     TvProviderSearch(Context context) {
66         mContext = context;
67         mContentResolver = context.getContentResolver();
68         mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
69     }
70 
71     /**
72      * Search channels, inputs, or programs from TvProvider. This assumes that parental control
73      * settings will not be change while searching.
74      *
75      * @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT},
76      *     or {@link #ACTION_TYPE_AMBIGUOUS},
77      */
78     @Override
79     @WorkerThread
search(String query, int limit, int action)80     public List<SearchResult> search(String query, int limit, int action) {
81         // TODO(b/72499463): add a test.
82         List<SearchResult> results = new ArrayList<>();
83         if (!PermissionUtils.hasAccessAllEpg(mContext)) {
84             // TODO: support this feature for non-system LC app. b/23939816
85             return results;
86         }
87         Set<Long> channelsFound = new HashSet<>();
88         if (action == ACTION_TYPE_SWITCH_CHANNEL) {
89             results.addAll(searchChannels(query, channelsFound, limit));
90         } else if (action == ACTION_TYPE_SWITCH_INPUT) {
91             results.addAll(searchInputs(query, limit));
92         } else {
93             // Search channels first.
94             results.addAll(searchChannels(query, channelsFound, limit));
95             if (results.size() >= limit) {
96                 return results;
97             }
98 
99             // In case the user wanted to perform the action "switch to XXX", which is indicated by
100             // setting the limit to 1, search inputs.
101             if (limit == 1) {
102                 results.addAll(searchInputs(query, limit));
103                 if (!results.isEmpty()) {
104                     return results;
105                 }
106             }
107 
108             // Lastly, search programs.
109             limit -= results.size();
110             results.addAll(
111                     searchPrograms(
112                             query,
113                             null,
114                             new String[] {Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION},
115                             channelsFound,
116                             limit));
117         }
118         return results;
119     }
120 
appendSelectionString( StringBuilder sb, String[] columnForExactMatching, String[] columnForPartialMatching)121     private void appendSelectionString(
122             StringBuilder sb, String[] columnForExactMatching, String[] columnForPartialMatching) {
123         boolean firstColumn = true;
124         if (columnForExactMatching != null) {
125             for (String column : columnForExactMatching) {
126                 if (!firstColumn) {
127                     sb.append(" OR ");
128                 } else {
129                     firstColumn = false;
130                 }
131                 sb.append(column).append("=?");
132             }
133         }
134         if (columnForPartialMatching != null) {
135             for (String column : columnForPartialMatching) {
136                 if (!firstColumn) {
137                     sb.append(" OR ");
138                 } else {
139                     firstColumn = false;
140                 }
141                 sb.append(column).append(" LIKE ?");
142             }
143         }
144     }
145 
insertSelectionArgumentStrings( String[] selectionArgs, int pos, String query, String[] columnForExactMatching, String[] columnForPartialMatching)146     private void insertSelectionArgumentStrings(
147             String[] selectionArgs,
148             int pos,
149             String query,
150             String[] columnForExactMatching,
151             String[] columnForPartialMatching) {
152         if (columnForExactMatching != null) {
153             int until = pos + columnForExactMatching.length;
154             for (; pos < until; ++pos) {
155                 selectionArgs[pos] = query;
156             }
157         }
158         String selectionArg = "%" + query + "%";
159         if (columnForPartialMatching != null) {
160             int until = pos + columnForPartialMatching.length;
161             for (; pos < until; ++pos) {
162                 selectionArgs[pos] = selectionArg;
163             }
164         }
165     }
166 
167     @WorkerThread
searchChannels(String query, Set<Long> channels, int limit)168     private List<SearchResult> searchChannels(String query, Set<Long> channels, int limit) {
169         if (DEBUG) Log.d(TAG, "Searching channels: '" + query + "'");
170         long time = SystemClock.elapsedRealtime();
171         List<SearchResult> results = new ArrayList<>();
172         if (TextUtils.isDigitsOnly(query)) {
173             results.addAll(
174                     searchChannels(
175                             query,
176                             new String[] {Channels.COLUMN_DISPLAY_NUMBER},
177                             null,
178                             channels,
179                             NO_LIMIT));
180             if (results.size() > 1) {
181                 Collections.sort(results, new ChannelComparatorWithSameDisplayNumber());
182             }
183         }
184         if (results.size() < limit) {
185             results.addAll(
186                     searchChannels(
187                             query,
188                             null,
189                             new String[] {
190                                 Channels.COLUMN_DISPLAY_NAME, Channels.COLUMN_DESCRIPTION
191                             },
192                             channels,
193                             limit - results.size()));
194         }
195         if (results.size() > limit) {
196             results = results.subList(0, limit);
197         }
198         for (int i = 0; i < results.size(); i++) {
199             results.set(i, fillProgramInfo(results.get(i)));
200         }
201         if (DEBUG) {
202             Log.d(
203                     TAG,
204                     "Found "
205                             + results.size()
206                             + " channels. Elapsed time for searching"
207                             + " channels: "
208                             + (SystemClock.elapsedRealtime() - time)
209                             + "(msec)");
210         }
211         return results;
212     }
213 
214     @WorkerThread
searchChannels( String query, String[] columnForExactMatching, String[] columnForPartialMatching, Set<Long> channelsFound, int limit)215     private List<SearchResult> searchChannels(
216             String query,
217             String[] columnForExactMatching,
218             String[] columnForPartialMatching,
219             Set<Long> channelsFound,
220             int limit) {
221         String[] projection = {
222             Channels._ID,
223             Channels.COLUMN_DISPLAY_NUMBER,
224             Channels.COLUMN_DISPLAY_NAME,
225             Channels.COLUMN_DESCRIPTION
226         };
227 
228         StringBuilder sb = new StringBuilder();
229         sb.append(Channels.COLUMN_BROWSABLE)
230                 .append("=1 AND ")
231                 .append(Channels.COLUMN_SEARCHABLE)
232                 .append("=1");
233         if (mTvInputManager.isParentalControlsEnabled()) {
234             sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0");
235         }
236         sb.append(" AND (");
237         appendSelectionString(sb, columnForExactMatching, columnForPartialMatching);
238         sb.append(")");
239         String selection = sb.toString();
240 
241         int len =
242                 (columnForExactMatching == null ? 0 : columnForExactMatching.length)
243                         + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
244         String[] selectionArgs = new String[len];
245         insertSelectionArgumentStrings(
246                 selectionArgs, 0, query, columnForExactMatching, columnForPartialMatching);
247 
248         List<SearchResult> searchResults = new ArrayList<>();
249 
250         try (Cursor c =
251                 mContentResolver.query(
252                         Channels.CONTENT_URI, projection, selection, selectionArgs, null)) {
253             if (c != null) {
254                 int count = 0;
255                 while (c.moveToNext()) {
256                     long id = c.getLong(0);
257                     // Filter out the channel which has been already searched.
258                     if (channelsFound.contains(id)) {
259                         continue;
260                     }
261                     channelsFound.add(id);
262 
263                     SearchResult.Builder result = SearchResult.builder();
264                     result.setChannelId(id);
265                     result.setChannelNumber(c.getString(1));
266                     result.setTitle(c.getString(2));
267                     result.setDescription(c.getString(3));
268                     result.setImageUri(TvContract.buildChannelLogoUri(id).toString());
269                     result.setIntentAction(Intent.ACTION_VIEW);
270                     result.setIntentData(buildIntentData(id));
271                     result.setContentType(Programs.CONTENT_ITEM_TYPE);
272                     result.setIsLive(true);
273                     result.setProgressPercentage(LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE);
274 
275                     searchResults.add(result.build());
276 
277                     if (limit != NO_LIMIT && ++count >= limit) {
278                         break;
279                     }
280                 }
281             }
282         }
283         return searchResults;
284     }
285 
286     /**
287      * Replaces the channel information - title, description, channel logo - with the current
288      * program information of the channel if the current program information exists and it is not
289      * blocked.
290      */
291     @WorkerThread
fillProgramInfo(SearchResult result)292     private SearchResult fillProgramInfo(SearchResult result) {
293         long now = System.currentTimeMillis();
294         Uri uri = TvContract.buildProgramsUriForChannel(result.getChannelId(), now, now);
295         String[] projection =
296                 new String[] {
297                     Programs.COLUMN_TITLE,
298                     Programs.COLUMN_POSTER_ART_URI,
299                     Programs.COLUMN_CONTENT_RATING,
300                     Programs.COLUMN_VIDEO_WIDTH,
301                     Programs.COLUMN_VIDEO_HEIGHT,
302                     Programs.COLUMN_START_TIME_UTC_MILLIS,
303                     Programs.COLUMN_END_TIME_UTC_MILLIS
304                 };
305 
306         try (Cursor c = mContentResolver.query(uri, projection, null, null, null)) {
307             if (c != null && c.moveToNext() && !isRatingBlocked(c.getString(2))) {
308                 String channelName = result.getTitle();
309                 String channelNumber = result.getChannelNumber();
310                 SearchResult.Builder builder = SearchResult.builder();
311                 long startUtcMillis = c.getLong(5);
312                 long endUtcMillis = c.getLong(6);
313                 builder.setTitle(c.getString(0));
314                 builder.setDescription(
315                         buildProgramDescription(
316                                 channelNumber, channelName, startUtcMillis, endUtcMillis));
317                 String imageUri = c.getString(1);
318                 if (imageUri != null) {
319                     builder.setImageUri(imageUri);
320                 }
321                 builder.setVideoWidth(c.getInt(3));
322                 builder.setVideoHeight(c.getInt(4));
323                 builder.setDuration(endUtcMillis - startUtcMillis);
324                 builder.setProgressPercentage(getProgressPercentage(startUtcMillis, endUtcMillis));
325                 return builder.build();
326             }
327         }
328         return result;
329     }
330 
buildProgramDescription( String channelNumber, String channelName, long programStartUtcMillis, long programEndUtcMillis)331     private String buildProgramDescription(
332             String channelNumber,
333             String channelName,
334             long programStartUtcMillis,
335             long programEndUtcMillis) {
336         return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
337                 + System.lineSeparator()
338                 + channelNumber
339                 + " "
340                 + channelName;
341     }
342 
getProgressPercentage(long startUtcMillis, long endUtcMillis)343     private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
344         long current = System.currentTimeMillis();
345         if (startUtcMillis > current || endUtcMillis <= current) {
346             return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
347         }
348         return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
349     }
350 
351     @WorkerThread
searchPrograms( String query, String[] columnForExactMatching, String[] columnForPartialMatching, Set<Long> channelsFound, int limit)352     private List<SearchResult> searchPrograms(
353             String query,
354             String[] columnForExactMatching,
355             String[] columnForPartialMatching,
356             Set<Long> channelsFound,
357             int limit) {
358         if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
359         long time = SystemClock.elapsedRealtime();
360         String[] projection = {
361             Programs.COLUMN_CHANNEL_ID,
362             Programs.COLUMN_TITLE,
363             Programs.COLUMN_POSTER_ART_URI,
364             Programs.COLUMN_CONTENT_RATING,
365             Programs.COLUMN_VIDEO_WIDTH,
366             Programs.COLUMN_VIDEO_HEIGHT,
367             Programs.COLUMN_START_TIME_UTC_MILLIS,
368             Programs.COLUMN_END_TIME_UTC_MILLIS,
369             Programs._ID
370         };
371 
372         StringBuilder sb = new StringBuilder();
373         // Search among the programs which are now being on the air.
374         sb.append(Programs.COLUMN_START_TIME_UTC_MILLIS).append("<=? AND ");
375         sb.append(Programs.COLUMN_END_TIME_UTC_MILLIS).append(">=? AND (");
376         appendSelectionString(sb, columnForExactMatching, columnForPartialMatching);
377         sb.append(")");
378         String selection = sb.toString();
379 
380         int len =
381                 (columnForExactMatching == null ? 0 : columnForExactMatching.length)
382                         + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
383         String[] selectionArgs = new String[len + 2];
384         long now = System.currentTimeMillis();
385         selectionArgs[0] = String.valueOf(now + SEARCH_TIME_FRAME_MS);
386         selectionArgs[1] = String.valueOf(now);
387         insertSelectionArgumentStrings(
388                 selectionArgs, 2, query, columnForExactMatching, columnForPartialMatching);
389 
390         List<SearchResult> searchResults = new ArrayList<>();
391 
392         try (Cursor c =
393                 mContentResolver.query(
394                         Programs.CONTENT_URI, projection, selection, selectionArgs, null)) {
395             if (c != null) {
396                 int count = 0;
397                 while (c.moveToNext()) {
398                     long id = c.getLong(0);
399                     // Filter out the program whose channel is already searched.
400                     if (channelsFound.contains(id)) {
401                         continue;
402                     }
403                     channelsFound.add(id);
404 
405                     // Don't know whether the channel is searchable or not.
406                     String[] channelProjection = {
407                         Channels._ID, Channels.COLUMN_DISPLAY_NUMBER, Channels.COLUMN_DISPLAY_NAME
408                     };
409                     sb = new StringBuilder();
410                     sb.append(Channels._ID)
411                             .append("=? AND ")
412                             .append(Channels.COLUMN_BROWSABLE)
413                             .append("=1 AND ")
414                             .append(Channels.COLUMN_SEARCHABLE)
415                             .append("=1");
416                     if (mTvInputManager.isParentalControlsEnabled()) {
417                         sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0");
418                     }
419                     String selectionChannel = sb.toString();
420                     try (Cursor cChannel =
421                             mContentResolver.query(
422                                     Channels.CONTENT_URI,
423                                     channelProjection,
424                                     selectionChannel,
425                                     new String[] {String.valueOf(id)},
426                                     null)) {
427                         if (cChannel != null
428                                 && cChannel.moveToNext()
429                                 && !isRatingBlocked(c.getString(3))) {
430                             long startUtcMillis = c.getLong(6);
431                             long endUtcMillis = c.getLong(7);
432                             SearchResult.Builder result = SearchResult.builder();
433                             result.setChannelId(c.getLong(0));
434                             result.setTitle(c.getString(1));
435                             result.setDescription(
436                                     buildProgramDescription(
437                                             cChannel.getString(1),
438                                             cChannel.getString(2),
439                                             startUtcMillis,
440                                             endUtcMillis));
441                             result.setImageUri(c.getString(2));
442                             result.setIntentAction(Intent.ACTION_VIEW);
443                             result.setIntentData(buildIntentData(id));
444                             result.setIntentExtraData(
445                                     TvContract.buildProgramUri(c.getLong(8)).toString());
446                             result.setContentType(Programs.CONTENT_ITEM_TYPE);
447                             result.setIsLive(true);
448                             result.setVideoWidth(c.getInt(4));
449                             result.setVideoHeight(c.getInt(5));
450                             result.setDuration(endUtcMillis - startUtcMillis);
451                             result.setProgressPercentage(
452                                     getProgressPercentage(startUtcMillis, endUtcMillis));
453                             searchResults.add(result.build());
454 
455                             if (limit != NO_LIMIT && ++count >= limit) {
456                                 break;
457                             }
458                         }
459                     }
460                 }
461             }
462         }
463         if (DEBUG) {
464             Log.d(
465                     TAG,
466                     "Found "
467                             + searchResults.size()
468                             + " programs. Elapsed time for searching"
469                             + " programs: "
470                             + (SystemClock.elapsedRealtime() - time)
471                             + "(msec)");
472         }
473         return searchResults;
474     }
475 
buildIntentData(long channelId)476     private String buildIntentData(long channelId) {
477         return TvContract.buildChannelUri(channelId).toString();
478     }
479 
isRatingBlocked(String ratings)480     private boolean isRatingBlocked(String ratings) {
481         if (TextUtils.isEmpty(ratings) || !mTvInputManager.isParentalControlsEnabled()) {
482             return false;
483         }
484         TvContentRating[] ratingArray = mTvContentRatingCache.getRatings(ratings);
485         if (ratingArray != null) {
486             for (TvContentRating r : ratingArray) {
487                 if (mTvInputManager.isRatingBlocked(r)) {
488                     return true;
489                 }
490             }
491         }
492         return false;
493     }
494 
searchInputs(String query, int limit)495     private List<SearchResult> searchInputs(String query, int limit) {
496         if (DEBUG) Log.d(TAG, "Searching inputs: '" + query + "'");
497         long time = SystemClock.elapsedRealtime();
498 
499         query = canonicalizeLabel(query);
500         List<TvInputInfo> inputList = mTvInputManager.getTvInputList();
501         List<SearchResult> results = new ArrayList<>();
502 
503         // Find exact matches first.
504         for (TvInputInfo input : inputList) {
505             if (input.getType() == TvInputInfo.TYPE_TUNER) {
506                 continue;
507             }
508             String label = canonicalizeLabel(input.loadLabel(mContext));
509             String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
510             if (TextUtils.equals(query, label) || TextUtils.equals(query, customLabel)) {
511                 results.add(buildSearchResultForInput(input.getId()));
512                 if (results.size() >= limit) {
513                     if (DEBUG) {
514                         Log.d(
515                                 TAG,
516                                 "Found "
517                                         + results.size()
518                                         + " inputs. Elapsed time for"
519                                         + " searching inputs: "
520                                         + (SystemClock.elapsedRealtime() - time)
521                                         + "(msec)");
522                     }
523                     return results;
524                 }
525             }
526         }
527 
528         // Then look for partial matches.
529         for (TvInputInfo input : inputList) {
530             if (input.getType() == TvInputInfo.TYPE_TUNER) {
531                 continue;
532             }
533             String label = canonicalizeLabel(input.loadLabel(mContext));
534             String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
535             if ((label != null && label.contains(query))
536                     || (customLabel != null && customLabel.contains(query))) {
537                 results.add(buildSearchResultForInput(input.getId()));
538                 if (results.size() >= limit) {
539                     if (DEBUG) {
540                         Log.d(
541                                 TAG,
542                                 "Found "
543                                         + results.size()
544                                         + " inputs. Elapsed time for"
545                                         + " searching inputs: "
546                                         + (SystemClock.elapsedRealtime() - time)
547                                         + "(msec)");
548                     }
549                     return results;
550                 }
551             }
552         }
553         if (DEBUG) {
554             Log.d(
555                     TAG,
556                     "Found "
557                             + results.size()
558                             + " inputs. Elapsed time for searching"
559                             + " inputs: "
560                             + (SystemClock.elapsedRealtime() - time)
561                             + "(msec)");
562         }
563         return results;
564     }
565 
canonicalizeLabel(CharSequence cs)566     private String canonicalizeLabel(CharSequence cs) {
567         Locale locale = mContext.getResources().getConfiguration().locale;
568         return cs != null ? cs.toString().replaceAll("[ -]", "").toLowerCase(locale) : null;
569     }
570 
buildSearchResultForInput(String inputId)571     private SearchResult buildSearchResultForInput(String inputId) {
572         SearchResult.Builder result = SearchResult.builder();
573         result.setIntentAction(Intent.ACTION_VIEW);
574         result.setIntentData(TvContract.buildChannelUriForPassthroughInput(inputId).toString());
575         return result.build();
576     }
577 
578     @WorkerThread
579     private class ChannelComparatorWithSameDisplayNumber implements Comparator<SearchResult> {
580         private final Map<Long, Long> mMaxWatchStartTimeMap = new HashMap<>();
581 
582         @Override
compare(SearchResult lhs, SearchResult rhs)583         public int compare(SearchResult lhs, SearchResult rhs) {
584             // Show recently watched channel first
585             Long lhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(lhs.getChannelId());
586             if (lhsMaxWatchStartTime == null) {
587                 lhsMaxWatchStartTime = getMaxWatchStartTime(lhs.getChannelId());
588                 mMaxWatchStartTimeMap.put(lhs.getChannelId(), lhsMaxWatchStartTime);
589             }
590             Long rhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(rhs.getChannelId());
591             if (rhsMaxWatchStartTime == null) {
592                 rhsMaxWatchStartTime = getMaxWatchStartTime(rhs.getChannelId());
593                 mMaxWatchStartTimeMap.put(rhs.getChannelId(), rhsMaxWatchStartTime);
594             }
595             if (!Objects.equals(lhsMaxWatchStartTime, rhsMaxWatchStartTime)) {
596                 return Long.compare(rhsMaxWatchStartTime, lhsMaxWatchStartTime);
597             }
598             // Show recently added channel first if there's no watch history.
599             return Long.compare(rhs.getChannelId(), lhs.getChannelId());
600         }
601 
getMaxWatchStartTime(long channelId)602         private long getMaxWatchStartTime(long channelId) {
603             Uri uri = WatchedPrograms.CONTENT_URI;
604             String[] projections =
605                     new String[] {
606                         "MAX("
607                                 + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS
608                                 + ") AS max_watch_start_time"
609                     };
610             String selection = WatchedPrograms.COLUMN_CHANNEL_ID + "=?";
611             String[] selectionArgs = new String[] {Long.toString(channelId)};
612             try (Cursor c =
613                     mContentResolver.query(uri, projections, selection, selectionArgs, null)) {
614                 if (c != null && c.moveToNext()) {
615                     return c.getLong(0);
616                 }
617             }
618             return -1;
619         }
620     }
621 }
622