163f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee/*
2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project
363f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee *
463f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * Licensed under the Apache License, Version 2.0 (the "License");
563f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * you may not use this file except in compliance with the License.
663f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * You may obtain a copy of the License at
763f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee *
863f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee *      http://www.apache.org/licenses/LICENSE-2.0
963f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee *
1063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * Unless required by applicable law or agreed to in writing, software
1163f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * distributed under the License is distributed on an "AS IS" BASIS,
1263f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1363f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * See the License for the specific language governing permissions and
1463f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee * limitations under the License.
1563f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee */
1663f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
1763f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leepackage com.android.tv.search;
1863f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver;
2063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leeimport android.content.Context;
2163f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leeimport android.content.Intent;
2263f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leeimport android.database.Cursor;
23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContentRating;
24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract;
259035590d94d136a020e499ce720b9d2cf1f6f45cJae Seoimport android.media.tv.TvContract.Channels;
269035590d94d136a020e499ce720b9d2cf1f6f45cJae Seoimport android.media.tv.TvContract.Programs;
27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract.WatchedPrograms;
28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvInputInfo;
29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvInputManager;
3063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leeimport android.net.Uri;
31919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Choimport android.os.SystemClock;
3207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport android.support.annotation.WorkerThread;
33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.text.TextUtils;
34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log;
35ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.common.TvContentRatingCache;
36944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.util.PermissionUtils;
37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.search.LocalSearchProvider.SearchResult;
38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils;
3963f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leeimport java.util.ArrayList;
40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections;
41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Comparator;
428294723aefc8341646c66f378a02098db95716deChulwoo Leeimport java.util.HashMap;
438294723aefc8341646c66f378a02098db95716deChulwoo Leeimport java.util.HashSet;
4463f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Leeimport java.util.List;
45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Locale;
468294723aefc8341646c66f378a02098db95716deChulwoo Leeimport java.util.Map;
47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Objects;
488294723aefc8341646c66f378a02098db95716deChulwoo Leeimport java.util.Set;
490645b17e2818b69b996da48fd93731bc8a01f114Live Channels Teamimport java.util.concurrent.TimeUnit;
5063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
5195961816a768da387f0b5523cf4363ace2044089Nick Chalko/** An implementation of {@link SearchInterface} to search query from TvProvider directly. */
527d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkopublic class TvProviderSearch implements SearchInterface {
53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String TAG = "TvProviderSearch";
54919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho    private static final boolean DEBUG = false;
55a4e80f54bf945b6601982ee2a74f44fa3f132889Chulwoo Lee
560645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team    private static final long SEARCH_TIME_FRAME_MS = TimeUnit.DAYS.toMillis(14);
570645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team
58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int NO_LIMIT = 0;
59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Context mContext;
61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ContentResolver mContentResolver;
62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final TvInputManager mTvInputManager;
63ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    TvProviderSearch(Context context) {
66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContext = context;
67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContentResolver = context.getContentResolver();
68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
7295961816a768da387f0b5523cf4363ace2044089Nick Chalko     * Search channels, inputs, or programs from TvProvider. This assumes that parental control
7395961816a768da387f0b5523cf4363ace2044089Nick Chalko     * settings will not be change while searching.
74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT},
7695961816a768da387f0b5523cf4363ace2044089Nick Chalko     *     or {@link #ACTION_TYPE_AMBIGUOUS},
77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
787d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    @Override
7907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @WorkerThread
80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public List<SearchResult> search(String query, int limit, int action) {
810645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        // TODO(b/72499463): add a test.
82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<SearchResult> results = new ArrayList<>();
837d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (!PermissionUtils.hasAccessAllEpg(mContext)) {
847d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            // TODO: support this feature for non-system LC app. b/23939816
857d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            return results;
867d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        }
87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Set<Long> channelsFound = new HashSet<>();
88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (action == ACTION_TYPE_SWITCH_CHANNEL) {
89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            results.addAll(searchChannels(query, channelsFound, limit));
90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else if (action == ACTION_TYPE_SWITCH_INPUT) {
91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            results.addAll(searchInputs(query, limit));
92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Search channels first.
94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            results.addAll(searchChannels(query, channelsFound, limit));
95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (results.size() >= limit) {
96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return results;
97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // In case the user wanted to perform the action "switch to XXX", which is indicated by
100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // setting the limit to 1, search inputs.
101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (limit == 1) {
102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                results.addAll(searchInputs(query, limit));
103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!results.isEmpty()) {
104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return results;
105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Lastly, search programs.
109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            limit -= results.size();
11095961816a768da387f0b5523cf4363ace2044089Nick Chalko            results.addAll(
11195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    searchPrograms(
11295961816a768da387f0b5523cf4363ace2044089Nick Chalko                            query,
11395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            null,
11495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            new String[] {Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION},
11595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            channelsFound,
11695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            limit));
117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
11863f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        return results;
11963f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee    }
12063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
12195961816a768da387f0b5523cf4363ace2044089Nick Chalko    private void appendSelectionString(
12295961816a768da387f0b5523cf4363ace2044089Nick Chalko            StringBuilder sb, String[] columnForExactMatching, String[] columnForPartialMatching) {
123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean firstColumn = true;
124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (columnForExactMatching != null) {
125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (String column : columnForExactMatching) {
126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!firstColumn) {
127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    sb.append(" OR ");
128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    firstColumn = false;
130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                sb.append(column).append("=?");
132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (columnForPartialMatching != null) {
135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (String column : columnForPartialMatching) {
136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!firstColumn) {
137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    sb.append(" OR ");
138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    firstColumn = false;
140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                sb.append(column).append(" LIKE ?");
142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
14695961816a768da387f0b5523cf4363ace2044089Nick Chalko    private void insertSelectionArgumentStrings(
14795961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] selectionArgs,
14895961816a768da387f0b5523cf4363ace2044089Nick Chalko            int pos,
14995961816a768da387f0b5523cf4363ace2044089Nick Chalko            String query,
15095961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] columnForExactMatching,
15195961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] columnForPartialMatching) {
152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (columnForExactMatching != null) {
153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            int until = pos + columnForExactMatching.length;
154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (; pos < until; ++pos) {
155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                selectionArgs[pos] = query;
156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String selectionArg = "%" + query + "%";
159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (columnForPartialMatching != null) {
160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            int until = pos + columnForPartialMatching.length;
161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (; pos < until; ++pos) {
162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                selectionArgs[pos] = selectionArg;
163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
16707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @WorkerThread
168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private List<SearchResult> searchChannels(String query, Set<Long> channels, int limit) {
169919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        if (DEBUG) Log.d(TAG, "Searching channels: '" + query + "'");
170919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        long time = SystemClock.elapsedRealtime();
171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<SearchResult> results = new ArrayList<>();
172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (TextUtils.isDigitsOnly(query)) {
17395961816a768da387f0b5523cf4363ace2044089Nick Chalko            results.addAll(
17495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    searchChannels(
17595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            query,
17695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            new String[] {Channels.COLUMN_DISPLAY_NUMBER},
17795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            null,
17895961816a768da387f0b5523cf4363ace2044089Nick Chalko                            channels,
17995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            NO_LIMIT));
180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (results.size() > 1) {
181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Collections.sort(results, new ChannelComparatorWithSameDisplayNumber());
182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (results.size() < limit) {
18595961816a768da387f0b5523cf4363ace2044089Nick Chalko            results.addAll(
18695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    searchChannels(
18795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            query,
18895961816a768da387f0b5523cf4363ace2044089Nick Chalko                            null,
18995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            new String[] {
19095961816a768da387f0b5523cf4363ace2044089Nick Chalko                                Channels.COLUMN_DISPLAY_NAME, Channels.COLUMN_DESCRIPTION
19195961816a768da387f0b5523cf4363ace2044089Nick Chalko                            },
19295961816a768da387f0b5523cf4363ace2044089Nick Chalko                            channels,
19395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            limit - results.size()));
1948294723aefc8341646c66f378a02098db95716deChulwoo Lee        }
195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (results.size() > limit) {
196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            results = results.subList(0, limit);
197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1980645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        for (int i = 0; i < results.size(); i++) {
1990645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team            results.set(i, fillProgramInfo(results.get(i)));
200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
201919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        if (DEBUG) {
20295961816a768da387f0b5523cf4363ace2044089Nick Chalko            Log.d(
20395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    TAG,
20495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    "Found "
20595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + results.size()
20695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + " channels. Elapsed time for searching"
20795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + " channels: "
20895961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + (SystemClock.elapsedRealtime() - time)
20995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + "(msec)");
210919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        }
211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return results;
2128294723aefc8341646c66f378a02098db95716deChulwoo Lee    }
2138294723aefc8341646c66f378a02098db95716deChulwoo Lee
21407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @WorkerThread
21595961816a768da387f0b5523cf4363ace2044089Nick Chalko    private List<SearchResult> searchChannels(
21695961816a768da387f0b5523cf4363ace2044089Nick Chalko            String query,
21795961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] columnForExactMatching,
21895961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] columnForPartialMatching,
21995961816a768da387f0b5523cf4363ace2044089Nick Chalko            Set<Long> channelsFound,
22095961816a768da387f0b5523cf4363ace2044089Nick Chalko            int limit) {
22163f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        String[] projection = {
22295961816a768da387f0b5523cf4363ace2044089Nick Chalko            Channels._ID,
22395961816a768da387f0b5523cf4363ace2044089Nick Chalko            Channels.COLUMN_DISPLAY_NUMBER,
22495961816a768da387f0b5523cf4363ace2044089Nick Chalko            Channels.COLUMN_DISPLAY_NAME,
22595961816a768da387f0b5523cf4363ace2044089Nick Chalko            Channels.COLUMN_DESCRIPTION
22663f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        };
227a4e80f54bf945b6601982ee2a74f44fa3f132889Chulwoo Lee
2288294723aefc8341646c66f378a02098db95716deChulwoo Lee        StringBuilder sb = new StringBuilder();
22995961816a768da387f0b5523cf4363ace2044089Nick Chalko        sb.append(Channels.COLUMN_BROWSABLE)
23095961816a768da387f0b5523cf4363ace2044089Nick Chalko                .append("=1 AND ")
23195961816a768da387f0b5523cf4363ace2044089Nick Chalko                .append(Channels.COLUMN_SEARCHABLE)
23295961816a768da387f0b5523cf4363ace2044089Nick Chalko                .append("=1");
233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mTvInputManager.isParentalControlsEnabled()) {
234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0");
2358294723aefc8341646c66f378a02098db95716deChulwoo Lee        }
236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        sb.append(" AND (");
237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        appendSelectionString(sb, columnForExactMatching, columnForPartialMatching);
2388294723aefc8341646c66f378a02098db95716deChulwoo Lee        sb.append(")");
2398294723aefc8341646c66f378a02098db95716deChulwoo Lee        String selection = sb.toString();
2408294723aefc8341646c66f378a02098db95716deChulwoo Lee
24195961816a768da387f0b5523cf4363ace2044089Nick Chalko        int len =
24295961816a768da387f0b5523cf4363ace2044089Nick Chalko                (columnForExactMatching == null ? 0 : columnForExactMatching.length)
24395961816a768da387f0b5523cf4363ace2044089Nick Chalko                        + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String[] selectionArgs = new String[len];
24595961816a768da387f0b5523cf4363ace2044089Nick Chalko        insertSelectionArgumentStrings(
24695961816a768da387f0b5523cf4363ace2044089Nick Chalko                selectionArgs, 0, query, columnForExactMatching, columnForPartialMatching);
247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<SearchResult> searchResults = new ArrayList<>();
249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
25095961816a768da387f0b5523cf4363ace2044089Nick Chalko        try (Cursor c =
25195961816a768da387f0b5523cf4363ace2044089Nick Chalko                mContentResolver.query(
25295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Channels.CONTENT_URI, projection, selection, selectionArgs, null)) {
253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (c != null) {
254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                int count = 0;
255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (c.moveToNext()) {
256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    long id = c.getLong(0);
257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Filter out the channel which has been already searched.
258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (channelsFound.contains(id)) {
259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        continue;
260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channelsFound.add(id);
262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
2630645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    SearchResult.Builder result = SearchResult.builder();
2640645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setChannelId(id);
2650645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setChannelNumber(c.getString(1));
2660645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setTitle(c.getString(2));
2670645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setDescription(c.getString(3));
2680645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setImageUri(TvContract.buildChannelLogoUri(id).toString());
2690645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setIntentAction(Intent.ACTION_VIEW);
2700645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setIntentData(buildIntentData(id));
2710645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setContentType(Programs.CONTENT_ITEM_TYPE);
2720645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setIsLive(true);
2730645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    result.setProgressPercentage(LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE);
274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
2750645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    searchResults.add(result.build());
276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (limit != NO_LIMIT && ++count >= limit) {
278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return searchResults;
284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Replaces the channel information - title, description, channel logo - with the current
288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * program information of the channel if the current program information exists and it is not
289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * blocked.
290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
29107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @WorkerThread
2920645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team    private SearchResult fillProgramInfo(SearchResult result) {
293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long now = System.currentTimeMillis();
2940645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        Uri uri = TvContract.buildProgramsUriForChannel(result.getChannelId(), now, now);
29595961816a768da387f0b5523cf4363ace2044089Nick Chalko        String[] projection =
29695961816a768da387f0b5523cf4363ace2044089Nick Chalko                new String[] {
29795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_TITLE,
29895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_POSTER_ART_URI,
29995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_CONTENT_RATING,
30095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_VIDEO_WIDTH,
30195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_VIDEO_HEIGHT,
30295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_START_TIME_UTC_MILLIS,
30395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Programs.COLUMN_END_TIME_UTC_MILLIS
30495961816a768da387f0b5523cf4363ace2044089Nick Chalko                };
305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        try (Cursor c = mContentResolver.query(uri, projection, null, null, null)) {
307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (c != null && c.moveToNext() && !isRatingBlocked(c.getString(2))) {
3080645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                String channelName = result.getTitle();
3090645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                String channelNumber = result.getChannelNumber();
3100645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                SearchResult.Builder builder = SearchResult.builder();
311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long startUtcMillis = c.getLong(5);
312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long endUtcMillis = c.getLong(6);
3130645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                builder.setTitle(c.getString(0));
3140645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                builder.setDescription(
31595961816a768da387f0b5523cf4363ace2044089Nick Chalko                        buildProgramDescription(
3160645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                                channelNumber, channelName, startUtcMillis, endUtcMillis));
317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                String imageUri = c.getString(1);
318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (imageUri != null) {
3190645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                    builder.setImageUri(imageUri);
320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
3210645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                builder.setVideoWidth(c.getInt(3));
3220645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                builder.setVideoHeight(c.getInt(4));
3230645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                builder.setDuration(endUtcMillis - startUtcMillis);
3240645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                builder.setProgressPercentage(getProgressPercentage(startUtcMillis, endUtcMillis));
3250645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                return builder.build();
326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
3278294723aefc8341646c66f378a02098db95716deChulwoo Lee        }
3280645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        return result;
329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
33195961816a768da387f0b5523cf4363ace2044089Nick Chalko    private String buildProgramDescription(
33295961816a768da387f0b5523cf4363ace2044089Nick Chalko            String channelNumber,
33395961816a768da387f0b5523cf4363ace2044089Nick Chalko            String channelName,
33495961816a768da387f0b5523cf4363ace2044089Nick Chalko            long programStartUtcMillis,
33595961816a768da387f0b5523cf4363ace2044089Nick Chalko            long programEndUtcMillis) {
336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
33795961816a768da387f0b5523cf4363ace2044089Nick Chalko                + System.lineSeparator()
33895961816a768da387f0b5523cf4363ace2044089Nick Chalko                + channelNumber
33995961816a768da387f0b5523cf4363ace2044089Nick Chalko                + " "
34095961816a768da387f0b5523cf4363ace2044089Nick Chalko                + channelName;
341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
3428294723aefc8341646c66f378a02098db95716deChulwoo Lee
343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long current = System.currentTimeMillis();
345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (startUtcMillis > current || endUtcMillis <= current) {
346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
34895961816a768da387f0b5523cf4363ace2044089Nick Chalko        return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
34963f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee    }
35063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
35107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @WorkerThread
35295961816a768da387f0b5523cf4363ace2044089Nick Chalko    private List<SearchResult> searchPrograms(
35395961816a768da387f0b5523cf4363ace2044089Nick Chalko            String query,
35495961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] columnForExactMatching,
35595961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] columnForPartialMatching,
35695961816a768da387f0b5523cf4363ace2044089Nick Chalko            Set<Long> channelsFound,
35795961816a768da387f0b5523cf4363ace2044089Nick Chalko            int limit) {
358919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
359919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        long time = SystemClock.elapsedRealtime();
36063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        String[] projection = {
36195961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_CHANNEL_ID,
36295961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_TITLE,
36395961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_POSTER_ART_URI,
36495961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_CONTENT_RATING,
36595961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_VIDEO_WIDTH,
36695961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_VIDEO_HEIGHT,
36795961816a768da387f0b5523cf4363ace2044089Nick Chalko            Programs.COLUMN_START_TIME_UTC_MILLIS,
3680645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team            Programs.COLUMN_END_TIME_UTC_MILLIS,
3690645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team            Programs._ID
37063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        };
371a4e80f54bf945b6601982ee2a74f44fa3f132889Chulwoo Lee
3728294723aefc8341646c66f378a02098db95716deChulwoo Lee        StringBuilder sb = new StringBuilder();
3738294723aefc8341646c66f378a02098db95716deChulwoo Lee        // Search among the programs which are now being on the air.
3748294723aefc8341646c66f378a02098db95716deChulwoo Lee        sb.append(Programs.COLUMN_START_TIME_UTC_MILLIS).append("<=? AND ");
3758294723aefc8341646c66f378a02098db95716deChulwoo Lee        sb.append(Programs.COLUMN_END_TIME_UTC_MILLIS).append(">=? AND (");
376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        appendSelectionString(sb, columnForExactMatching, columnForPartialMatching);
3778294723aefc8341646c66f378a02098db95716deChulwoo Lee        sb.append(")");
37863f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        String selection = sb.toString();
379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
38095961816a768da387f0b5523cf4363ace2044089Nick Chalko        int len =
38195961816a768da387f0b5523cf4363ace2044089Nick Chalko                (columnForExactMatching == null ? 0 : columnForExactMatching.length)
38295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String[] selectionArgs = new String[len + 2];
3840645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        long now = System.currentTimeMillis();
3850645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        selectionArgs[0] = String.valueOf(now + SEARCH_TIME_FRAME_MS);
3860645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        selectionArgs[1] = String.valueOf(now);
38795961816a768da387f0b5523cf4363ace2044089Nick Chalko        insertSelectionArgumentStrings(
38895961816a768da387f0b5523cf4363ace2044089Nick Chalko                selectionArgs, 2, query, columnForExactMatching, columnForPartialMatching);
38963f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<SearchResult> searchResults = new ArrayList<>();
3918294723aefc8341646c66f378a02098db95716deChulwoo Lee
39295961816a768da387f0b5523cf4363ace2044089Nick Chalko        try (Cursor c =
39395961816a768da387f0b5523cf4363ace2044089Nick Chalko                mContentResolver.query(
39495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Programs.CONTENT_URI, projection, selection, selectionArgs, null)) {
395816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (c != null) {
396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                int count = 0;
397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (c.moveToNext()) {
398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    long id = c.getLong(0);
399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Filter out the program whose channel is already searched.
400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (channelsFound.contains(id)) {
401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        continue;
402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channelsFound.add(id);
4048294723aefc8341646c66f378a02098db95716deChulwoo Lee
405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Don't know whether the channel is searchable or not.
406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    String[] channelProjection = {
40795961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Channels._ID, Channels.COLUMN_DISPLAY_NUMBER, Channels.COLUMN_DISPLAY_NAME
408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    };
409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    sb = new StringBuilder();
41095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    sb.append(Channels._ID)
41195961816a768da387f0b5523cf4363ace2044089Nick Chalko                            .append("=? AND ")
41295961816a768da387f0b5523cf4363ace2044089Nick Chalko                            .append(Channels.COLUMN_BROWSABLE)
41395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            .append("=1 AND ")
41495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            .append(Channels.COLUMN_SEARCHABLE)
41595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            .append("=1");
416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mTvInputManager.isParentalControlsEnabled()) {
417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0");
418816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
419816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    String selectionChannel = sb.toString();
42095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    try (Cursor cChannel =
42195961816a768da387f0b5523cf4363ace2044089Nick Chalko                            mContentResolver.query(
42295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    Channels.CONTENT_URI,
42395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    channelProjection,
42495961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    selectionChannel,
42595961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    new String[] {String.valueOf(id)},
42695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    null)) {
42795961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (cChannel != null
42895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                && cChannel.moveToNext()
429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                && !isRatingBlocked(c.getString(3))) {
430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            long startUtcMillis = c.getLong(6);
431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            long endUtcMillis = c.getLong(7);
4320645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            SearchResult.Builder result = SearchResult.builder();
4330645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setChannelId(c.getLong(0));
4340645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setTitle(c.getString(1));
4350645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setDescription(
43695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    buildProgramDescription(
43795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                            cChannel.getString(1),
43895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                            cChannel.getString(2),
43995961816a768da387f0b5523cf4363ace2044089Nick Chalko                                            startUtcMillis,
4400645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                                            endUtcMillis));
4410645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setImageUri(c.getString(2));
4420645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setIntentAction(Intent.ACTION_VIEW);
4430645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setIntentData(buildIntentData(id));
4440645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setIntentExtraData(
4450645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                                    TvContract.buildProgramUri(c.getLong(8)).toString());
4460645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setContentType(Programs.CONTENT_ITEM_TYPE);
4470645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setIsLive(true);
4480645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setVideoWidth(c.getInt(4));
4490645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setVideoHeight(c.getInt(5));
4500645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setDuration(endUtcMillis - startUtcMillis);
4510645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            result.setProgressPercentage(
4520645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                                    getProgressPercentage(startUtcMillis, endUtcMillis));
4530645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                            searchResults.add(result.build());
454816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
455816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            if (limit != NO_LIMIT && ++count >= limit) {
456816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                break;
4578294723aefc8341646c66f378a02098db95716deChulwoo Lee                            }
4588294723aefc8341646c66f378a02098db95716deChulwoo Lee                        }
4598294723aefc8341646c66f378a02098db95716deChulwoo Lee                    }
460816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
461816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
462816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
463919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        if (DEBUG) {
46495961816a768da387f0b5523cf4363ace2044089Nick Chalko            Log.d(
46595961816a768da387f0b5523cf4363ace2044089Nick Chalko                    TAG,
46695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    "Found "
46795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + searchResults.size()
46895961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + " programs. Elapsed time for searching"
46995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + " programs: "
47095961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + (SystemClock.elapsedRealtime() - time)
47195961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + "(msec)");
472919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        }
473816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return searchResults;
4748294723aefc8341646c66f378a02098db95716deChulwoo Lee    }
4758294723aefc8341646c66f378a02098db95716deChulwoo Lee
476816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private String buildIntentData(long channelId) {
477633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        return TvContract.buildChannelUri(channelId).toString();
478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
4798294723aefc8341646c66f378a02098db95716deChulwoo Lee
480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean isRatingBlocked(String ratings) {
481ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (TextUtils.isEmpty(ratings) || !mTvInputManager.isParentalControlsEnabled()) {
482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return false;
483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
484ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        TvContentRating[] ratingArray = mTvContentRatingCache.getRatings(ratings);
485ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (ratingArray != null) {
486ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            for (TvContentRating r : ratingArray) {
487ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                if (mTvInputManager.isRatingBlocked(r)) {
488816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return true;
48963f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee                }
49063f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee            }
491816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return false;
493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private List<SearchResult> searchInputs(String query, int limit) {
496919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        if (DEBUG) Log.d(TAG, "Searching inputs: '" + query + "'");
497919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        long time = SystemClock.elapsedRealtime();
498816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        query = canonicalizeLabel(query);
500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<TvInputInfo> inputList = mTvInputManager.getTvInputList();
501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<SearchResult> results = new ArrayList<>();
502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Find exact matches first.
504816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (TvInputInfo input : inputList) {
505633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            if (input.getType() == TvInputInfo.TYPE_TUNER) {
506633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                continue;
507633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            }
508816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            String label = canonicalizeLabel(input.loadLabel(mContext));
509816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
510816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (TextUtils.equals(query, label) || TextUtils.equals(query, customLabel)) {
511816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                results.add(buildSearchResultForInput(input.getId()));
512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (results.size() >= limit) {
513919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho                    if (DEBUG) {
51495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Log.d(
51595961816a768da387f0b5523cf4363ace2044089Nick Chalko                                TAG,
51695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                "Found "
51795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + results.size()
51895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + " inputs. Elapsed time for"
51995961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + " searching inputs: "
52095961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + (SystemClock.elapsedRealtime() - time)
52195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + "(msec)");
522919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho                    }
523816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return results;
524816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
52563f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee            }
52663f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        }
52763f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee
528816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Then look for partial matches.
529816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (TvInputInfo input : inputList) {
530633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            if (input.getType() == TvInputInfo.TYPE_TUNER) {
531633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                continue;
532633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            }
533816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            String label = canonicalizeLabel(input.loadLabel(mContext));
534816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
53595961816a768da387f0b5523cf4363ace2044089Nick Chalko            if ((label != null && label.contains(query))
53695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    || (customLabel != null && customLabel.contains(query))) {
537816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                results.add(buildSearchResultForInput(input.getId()));
538816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (results.size() >= limit) {
539919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho                    if (DEBUG) {
54095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Log.d(
54195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                TAG,
54295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                "Found "
54395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + results.size()
54495961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + " inputs. Elapsed time for"
54595961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + " searching inputs: "
54695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + (SystemClock.elapsedRealtime() - time)
54795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        + "(msec)");
548919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho                    }
549816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return results;
550816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
552816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
553919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        if (DEBUG) {
55495961816a768da387f0b5523cf4363ace2044089Nick Chalko            Log.d(
55595961816a768da387f0b5523cf4363ace2044089Nick Chalko                    TAG,
55695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    "Found "
55795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + results.size()
55895961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + " inputs. Elapsed time for searching"
55995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + " inputs: "
56095961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + (SystemClock.elapsedRealtime() - time)
56195961816a768da387f0b5523cf4363ace2044089Nick Chalko                            + "(msec)");
562919e1ed7e914029a1a0054237d86dc7b19ced898Youngsang Cho        }
56363f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee        return results;
56463f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee    }
5658294723aefc8341646c66f378a02098db95716deChulwoo Lee
566816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private String canonicalizeLabel(CharSequence cs) {
567816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Locale locale = mContext.getResources().getConfiguration().locale;
568816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return cs != null ? cs.toString().replaceAll("[ -]", "").toLowerCase(locale) : null;
569816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
570816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
571816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private SearchResult buildSearchResultForInput(String inputId) {
5720645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        SearchResult.Builder result = SearchResult.builder();
5730645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        result.setIntentAction(Intent.ACTION_VIEW);
5740645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        result.setIntentData(TvContract.buildChannelUriForPassthroughInput(inputId).toString());
5750645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team        return result.build();
576816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
577816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
57807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @WorkerThread
579816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class ChannelComparatorWithSameDisplayNumber implements Comparator<SearchResult> {
580816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final Map<Long, Long> mMaxWatchStartTimeMap = new HashMap<>();
581816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
582816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
583816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public int compare(SearchResult lhs, SearchResult rhs) {
584816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Show recently watched channel first
5850645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team            Long lhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(lhs.getChannelId());
586816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (lhsMaxWatchStartTime == null) {
5870645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                lhsMaxWatchStartTime = getMaxWatchStartTime(lhs.getChannelId());
5880645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                mMaxWatchStartTimeMap.put(lhs.getChannelId(), lhsMaxWatchStartTime);
589816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
5900645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team            Long rhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(rhs.getChannelId());
591816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (rhsMaxWatchStartTime == null) {
5920645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                rhsMaxWatchStartTime = getMaxWatchStartTime(rhs.getChannelId());
5930645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team                mMaxWatchStartTimeMap.put(rhs.getChannelId(), rhsMaxWatchStartTime);
594816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
595816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (!Objects.equals(lhsMaxWatchStartTime, rhsMaxWatchStartTime)) {
596816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return Long.compare(rhsMaxWatchStartTime, lhsMaxWatchStartTime);
597816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
598816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Show recently added channel first if there's no watch history.
5990645b17e2818b69b996da48fd93731bc8a01f114Live Channels Team            return Long.compare(rhs.getChannelId(), lhs.getChannelId());
600816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
601816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
602816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private long getMaxWatchStartTime(long channelId) {
603816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Uri uri = WatchedPrograms.CONTENT_URI;
60495961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] projections =
60595961816a768da387f0b5523cf4363ace2044089Nick Chalko                    new String[] {
60695961816a768da387f0b5523cf4363ace2044089Nick Chalko                        "MAX("
60795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS
60895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                + ") AS max_watch_start_time"
60995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    };
610816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            String selection = WatchedPrograms.COLUMN_CHANNEL_ID + "=?";
61195961816a768da387f0b5523cf4363ace2044089Nick Chalko            String[] selectionArgs = new String[] {Long.toString(channelId)};
61295961816a768da387f0b5523cf4363ace2044089Nick Chalko            try (Cursor c =
61395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mContentResolver.query(uri, projections, selection, selectionArgs, null)) {
614816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (c != null && c.moveToNext()) {
615816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return c.getLong(0);
616816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
617816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
618816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return -1;
619816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
6208294723aefc8341646c66f378a02098db95716deChulwoo Lee    }
62163f6ab4e8240eef8b078651f2e7b3c92ff5f196bChulwoo Lee}
622