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.settings.search;
18
19import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
20import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
21import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
22import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
23import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
24import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
25import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
26import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
27import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
28import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
29import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
30
31import android.content.Context;
32import android.database.Cursor;
33import android.database.MatrixCursor;
34import android.provider.SearchIndexableResource;
35import android.provider.SearchIndexablesProvider;
36import android.util.ArraySet;
37import android.util.Log;
38
39import java.util.Collection;
40import java.util.HashSet;
41import java.util.List;
42
43public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
44    public static final boolean DEBUG = false;
45    private static final String TAG = "SettingsSearchProvider";
46
47    private static final Collection<String> INVALID_KEYS;
48
49    static {
50        INVALID_KEYS = new ArraySet<>();
51        INVALID_KEYS.add(null);
52        INVALID_KEYS.add("");
53    }
54
55    @Override
56    public boolean onCreate() {
57        return true;
58    }
59
60    @Override
61    public Cursor queryXmlResources(String[] projection) {
62        MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
63        Collection<SearchIndexableResource> values = SearchIndexableResources.values();
64        for (SearchIndexableResource val : values) {
65            Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length];
66            ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;
67            ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;
68            ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className;
69            ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId;
70            ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = val.intentAction;
71            ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = val.intentTargetPackage;
72            ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
73            cursor.addRow(ref);
74        }
75        return cursor;
76    }
77
78    @Override
79    public Cursor queryRawData(String[] projection) {
80        MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
81        return result;
82    }
83
84    /**
85     * Gets a combined list non-indexable keys that come from providers inside of settings.
86     * The non-indexable keys are used in Settings search at both index and update time to verify
87     * the validity of results in the database.
88     */
89    @Override
90    public Cursor queryNonIndexableKeys(String[] projection) {
91        MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
92        final Collection<String> values = new HashSet<>();
93        final Context context = getContext();
94
95        for (SearchIndexableResource sir : SearchIndexableResources.values()) {
96            if (DEBUG) {
97                Log.d(TAG, "Getting non-indexable from " + sir.className);
98            }
99            final long startTime = System.currentTimeMillis();
100            final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(sir.className);
101            if (clazz == null) {
102                Log.d(TAG, "SearchIndexableResource '" + sir.className +
103                        "' should implement the " + Indexable.class.getName() + " interface!");
104                continue;
105            }
106
107            final Indexable.SearchIndexProvider provider =
108                    DatabaseIndexingUtils.getSearchIndexProvider(clazz);
109
110            if (provider == null) {
111                Log.d(TAG, "Unable to get SearchIndexableProvider from " + clazz);
112                continue;
113            }
114
115            List<String> providerNonIndexableKeys = provider.getNonIndexableKeys(context);
116
117            if (providerNonIndexableKeys == null || providerNonIndexableKeys.isEmpty()) {
118                if (DEBUG) {
119                    final long totalTime = System.currentTimeMillis() - startTime;
120                    Log.d(TAG, "No indexable, total time " + totalTime);
121                }
122                continue;
123            }
124
125            if (providerNonIndexableKeys.removeAll(INVALID_KEYS)) {
126                Log.v(TAG, clazz.getName() + " tried to add an empty non-indexable key");
127            }
128            if (DEBUG) {
129                final long totalTime = System.currentTimeMillis() - startTime;
130                Log.d(TAG, "Non-indexables " + providerNonIndexableKeys.size() + ", total time "
131                        + totalTime);
132            }
133            values.addAll(providerNonIndexableKeys);
134        }
135
136        for (String nik : values) {
137
138            final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length];
139            ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = nik;
140            cursor.addRow(ref);
141        }
142        return cursor;
143    }
144}
145