118fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze/*
22078bc2358031ef3a191900d9036daf4251911c1Matthew Fritze * Copyright (C) 2017 The Android Open Source Project
318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze *
418fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * Licensed under the Apache License, Version 2.0 (the "License");
518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * you may not use this file except in compliance with the License.
618fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * You may obtain a copy of the License at
718fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze *
818fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze *      http://www.apache.org/licenses/LICENSE-2.0
918fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze *
1018fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * Unless required by applicable law or agreed to in writing, software
1118fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * distributed under the License is distributed on an "AS IS" BASIS,
1218fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * See the License for the specific language governing permissions and
1418fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * limitations under the License.
1518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze *
1618fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze */
1718fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
182078bc2358031ef3a191900d9036daf4251911c1Matthew Fritzepackage com.android.settings.search;
1918fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
2018fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritzeimport android.content.Context;
213746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritzeimport android.content.Intent;
221dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritzeimport android.net.Uri;
233746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritzeimport android.os.Bundle;
241dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritzeimport android.util.ArrayMap;
2518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritzeimport android.util.Log;
2618fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
273746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritzeimport com.android.internal.logging.nano.MetricsProto;
283746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritzeimport com.android.settings.SettingsActivity;
293a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritzeimport com.android.settings.core.BasePreferenceController;
301d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantlerimport com.android.settings.core.PreferenceControllerMixin;
311f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhangimport com.android.settings.core.SubSettingLauncher;
321d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantlerimport com.android.settingslib.core.AbstractPreferenceController;
3318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
3418fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritzeimport java.lang.reflect.Field;
351dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritzeimport java.util.List;
361dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritzeimport java.util.Map;
3718fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
3818fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze/**
3918fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * Utility class for {@like DatabaseIndexingManager} to handle the mapping between Payloads
4018fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze * and Preference controllers, and managing indexable classes.
4118fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze */
4218fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritzepublic class DatabaseIndexingUtils {
4318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
441dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze    private static final String TAG = "IndexingUtil";
4518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
467fddfebf6ceb7bdf2fd0c63e878594f9b98122aeMatthew Fritze    public static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
4718fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze            "SEARCH_INDEX_DATA_PROVIDER";
4818fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
491dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze    /**
5025f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang     * Builds intent that launches the search destination as a sub-setting.
513746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze     */
5225f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang    public static Intent buildSearchResultPageIntent(Context context, String className, String key,
533746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze            String screenTitle) {
5425f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang        return buildSearchResultPageIntent(context, className, key, screenTitle,
5525f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang                MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS);
5625f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang    }
5725f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang
5825f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang    public static Intent  buildSearchResultPageIntent(Context context, String className, String key,
5925f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang            String screenTitle, int sourceMetricsCategory) {
603746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze        final Bundle args = new Bundle();
613746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze        args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
621f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhang        final Intent searchDestination = new SubSettingLauncher(context)
631f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhang                .setDestination(className)
641f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhang                .setArguments(args)
651f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhang                .setTitle(screenTitle)
661f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhang                .setSourceMetricsCategory(sourceMetricsCategory)
671f6d24a991a688110fdd75ac852c51bd882c81e9Fan Zhang                .toIntent();
688068fda0599945b9f8f3029d420f813da900435dFan Zhang        searchDestination.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key)
698068fda0599945b9f8f3029d420f813da900435dFan Zhang                .setAction("com.android.settings.SEARCH_RESULT_TRAMPOLINE")
708068fda0599945b9f8f3029d420f813da900435dFan Zhang                .setComponent(null);
7125f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang        return searchDestination;
723746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze    }
733746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze
743746d9e25f2805b16b69eb7420a629fc66f6a788Matthew Fritze    /**
751dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze     * @param className which wil provide the map between from {@link Uri}s to
7625f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang     *                  {@link PreferenceControllerMixin}
771d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler     * @return A map between {@link Uri}s and {@link PreferenceControllerMixin}s to get the payload
781dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze     * types for Settings.
791dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze     */
80917f101899e0584edde44567f7939a563cc2dc05Fan Zhang    public static Map<String, ResultPayload> getPayloadKeyMap(String className, Context context) {
813a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze        ArrayMap<String, ResultPayload> map = new ArrayMap<>();
8265fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze        if (context == null) {
833a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            return map;
8465fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze        }
8565fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze
861dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        final Class<?> clazz = getIndexableClass(className);
871dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze
881dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        if (clazz == null) {
891dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze            Log.d(TAG, "SearchIndexableResource '" + className +
901dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze                    "' should implement the " + Indexable.class.getName() + " interface!");
913a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            return map;
921dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        }
931dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze
941dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        // Will be non null only for a Local provider implementing a
951dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        // SEARCH_INDEX_DATA_PROVIDER field
961dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        final Indexable.SearchIndexProvider provider = getSearchIndexProvider(clazz);
9765fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze
98917f101899e0584edde44567f7939a563cc2dc05Fan Zhang        final List<AbstractPreferenceController> controllers =
99aed8994f6f50a6bc0adfaa92b499ebde3a8f2a76Fan Zhang                provider.getPreferenceControllers(context);
1001dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze
10125f29bf126b71b94ac180b2c6664b22682886a12Fan Zhang        if (controllers == null) {
1023a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            return map;
1031dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        }
1041dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze
1051d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler        for (AbstractPreferenceController controller : controllers) {
1063a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            ResultPayload payload;
1071d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler            if (controller instanceof PreferenceControllerMixin) {
1083a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze                payload = ((PreferenceControllerMixin) controller).getResultPayload();
1093a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze
1103a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            } else if (controller instanceof BasePreferenceController) {
1113a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze                payload = ((BasePreferenceController) controller).getResultPayload();
1121d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler            } else {
1131d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler                throw new IllegalStateException(controller.getClass().getName()
1141d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler                        + " must implement " + PreferenceControllerMixin.class.getName());
1151d583e125faf3ae4c9cd82636d8f3ecf1cdec3aaTony Mantler            }
1163a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            if (payload != null) {
1173a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze                map.put(controller.getPreferenceKey(), payload);
1183a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze            }
1191dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        }
1201dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze
1211dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze        return map;
1221dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze    }
1231dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze
12418fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze    public static Class<?> getIndexableClass(String className) {
12518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        final Class<?> clazz;
12618fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        try {
12718fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze            clazz = Class.forName(className);
12818fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        } catch (ClassNotFoundException e) {
1291dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze            Log.d(TAG, "Cannot find class: " + className);
13018fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze            return null;
13118fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        }
13218fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        return isIndexableClass(clazz) ? clazz : null;
13318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze    }
13418fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
13518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze    public static boolean isIndexableClass(final Class<?> clazz) {
13618fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        return (clazz != null) && Indexable.class.isAssignableFrom(clazz);
13718fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze    }
13818fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze
13918fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze    public static Indexable.SearchIndexProvider getSearchIndexProvider(final Class<?> clazz) {
14018fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        try {
14118fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze            final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
14218fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze            return (Indexable.SearchIndexProvider) f.get(null);
14318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        } catch (NoSuchFieldException e) {
1441dec073528352fcf036c5ba52d5d535ba32c472cMatthew Fritze            Log.d(TAG, "Cannot find field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
14518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        } catch (SecurityException se) {
14665fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze            Log.d(TAG, "Security exception for field '" +
14765fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze                    FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
14818fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        } catch (IllegalAccessException e) {
14965fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze            Log.d(TAG, "Illegal access to field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
15018fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        } catch (IllegalArgumentException e) {
15165fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze            Log.d(TAG, "Illegal argument when accessing field '" +
15265fb01b520a8eace6c5670c06e6ba5f81ab609afMatthew Fritze                    FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
15318fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        }
15418fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze        return null;
15518fcb08308b50e73bbb380d3c9fc3261a4df7c83Matthew Fritze    }
1563a4168360b94ac3fdefeafa2f4a39169b6905c1bMatthew Fritze}