Home | History | Annotate | Download | only in indexing
      1 /*
      2  * Copyright (C) 2017 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 
     18 package com.android.settings.intelligence.search.indexing;
     19 
     20 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
     21 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME;
     22 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES;
     23 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
     24 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
     25 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
     26 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
     27 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
     28 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
     29 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE;
     30 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF;
     31 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
     32 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
     33 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
     34 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
     35 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
     36 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
     37 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
     38 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
     39 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
     40 
     41 import android.Manifest;
     42 import android.content.ContentResolver;
     43 import android.content.Context;
     44 import android.content.pm.ApplicationInfo;
     45 import android.content.pm.PackageInfo;
     46 import android.content.pm.PackageManager;
     47 import android.content.pm.ResolveInfo;
     48 import android.database.Cursor;
     49 import android.net.Uri;
     50 import android.provider.SearchIndexableResource;
     51 import android.provider.SearchIndexablesContract;
     52 import android.support.annotation.VisibleForTesting;
     53 import android.text.TextUtils;
     54 import android.util.ArraySet;
     55 import android.util.Log;
     56 import android.util.Pair;
     57 
     58 import com.android.settings.intelligence.search.SearchFeatureProvider;
     59 import com.android.settings.intelligence.search.SearchIndexableRaw;
     60 
     61 import java.util.ArrayList;
     62 import java.util.Collections;
     63 import java.util.List;
     64 import java.util.Set;
     65 
     66 /**
     67  * Collects all data from {@link android.provider.SearchIndexablesProvider} to be indexed.
     68  */
     69 public class PreIndexDataCollector {
     70 
     71     private static final String TAG = "IndexableDataCollector";
     72 
     73     private static final List<String> EMPTY_LIST = Collections.emptyList();
     74 
     75     private Context mContext;
     76 
     77     private PreIndexData mIndexData;
     78 
     79     public PreIndexDataCollector(Context context) {
     80         mContext = context;
     81     }
     82 
     83     public PreIndexData collectIndexableData(List<ResolveInfo> providers, boolean isFullIndex) {
     84         mIndexData = new PreIndexData();
     85 
     86         for (final ResolveInfo info : providers) {
     87             if (!isWellKnownProvider(info)) {
     88                 continue;
     89             }
     90             final String authority = info.providerInfo.authority;
     91             final String packageName = info.providerInfo.packageName;
     92 
     93             if (isFullIndex) {
     94                 addIndexablesFromRemoteProvider(packageName, authority);
     95             }
     96 
     97             final long nonIndexableStartTime = System.currentTimeMillis();
     98             addNonIndexablesKeysFromRemoteProvider(packageName, authority);
     99             if (SearchFeatureProvider.DEBUG) {
    100                 final long nonIndexableTime = System.currentTimeMillis() - nonIndexableStartTime;
    101                 Log.d(TAG, "performIndexing update non-indexable for package " + packageName
    102                         + " took time: " + nonIndexableTime);
    103             }
    104         }
    105 
    106         return mIndexData;
    107     }
    108 
    109     private void addIndexablesFromRemoteProvider(String packageName, String authority) {
    110         try {
    111             final Context context = mContext.createPackageContext(packageName, 0);
    112 
    113             final Uri uriForResources = buildUriForXmlResources(authority);
    114             mIndexData.addDataToUpdate(getIndexablesForXmlResourceUri(context, packageName,
    115                     uriForResources, SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS));
    116 
    117             final Uri uriForRawData = buildUriForRawData(authority);
    118             mIndexData.addDataToUpdate(getIndexablesForRawDataUri(context, packageName,
    119                     uriForRawData, SearchIndexablesContract.INDEXABLES_RAW_COLUMNS));
    120 
    121             final Uri uriForSiteMap = buildUriForSiteMap(authority);
    122             mIndexData.addSiteMapPairs(getSiteMapFromProvider(context, uriForSiteMap));
    123         } catch (PackageManager.NameNotFoundException e) {
    124             Log.w(TAG, "Could not create context for " + packageName + ": "
    125                     + Log.getStackTraceString(e));
    126         }
    127     }
    128 
    129     @VisibleForTesting
    130     List<SearchIndexableResource> getIndexablesForXmlResourceUri(Context packageContext,
    131             String packageName, Uri uri, String[] projection) {
    132 
    133         final ContentResolver resolver = packageContext.getContentResolver();
    134         final Cursor cursor = resolver.query(uri, projection, null, null, null);
    135         List<SearchIndexableResource> resources = new ArrayList<>();
    136 
    137         if (cursor == null) {
    138             Log.w(TAG, "Cannot add index data for Uri: " + uri.toString());
    139             return resources;
    140         }
    141 
    142         try {
    143             final int count = cursor.getCount();
    144             if (count > 0) {
    145                 while (cursor.moveToNext()) {
    146                     SearchIndexableResource sir = new SearchIndexableResource(packageContext);
    147                     sir.packageName = packageName;
    148                     sir.xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);
    149                     sir.className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);
    150                     sir.iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);
    151                     sir.intentAction = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);
    152                     sir.intentTargetPackage = cursor.getString(
    153                             COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);
    154                     sir.intentTargetClass = cursor.getString(
    155                             COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);
    156                     resources.add(sir);
    157                 }
    158             }
    159         } finally {
    160             cursor.close();
    161         }
    162         return resources;
    163     }
    164 
    165     private void addNonIndexablesKeysFromRemoteProvider(String packageName, String authority) {
    166         final List<String> keys =
    167                 getNonIndexablesKeysFromRemoteProvider(packageName, authority);
    168 
    169         if (keys != null && !keys.isEmpty()) {
    170             Set<String> keySet = new ArraySet<>();
    171             keySet.addAll(keys);
    172             mIndexData.addNonIndexableKeysForAuthority(authority, keySet);
    173         }
    174     }
    175 
    176     @VisibleForTesting
    177     List<String> getNonIndexablesKeysFromRemoteProvider(String packageName,
    178             String authority) {
    179         try {
    180             final Context packageContext = mContext.createPackageContext(packageName, 0);
    181 
    182             final Uri uriForNonIndexableKeys = buildUriForNonIndexableKeys(authority);
    183             return getNonIndexablesKeys(packageContext, uriForNonIndexableKeys,
    184                     SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS);
    185         } catch (PackageManager.NameNotFoundException e) {
    186             Log.w(TAG, "Could not create context for " + packageName + ": "
    187                     + Log.getStackTraceString(e));
    188             return EMPTY_LIST;
    189         }
    190     }
    191 
    192     private Uri buildUriForXmlResources(String authority) {
    193         return Uri.parse("content://" + authority + "/" +
    194                 SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
    195     }
    196 
    197     private Uri buildUriForRawData(String authority) {
    198         return Uri.parse("content://" + authority + "/" +
    199                 SearchIndexablesContract.INDEXABLES_RAW_PATH);
    200     }
    201 
    202     private Uri buildUriForNonIndexableKeys(String authority) {
    203         return Uri.parse("content://" + authority + "/" +
    204                 SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
    205     }
    206 
    207     @VisibleForTesting
    208     Uri buildUriForSiteMap(String authority) {
    209         return Uri.parse("content://" + authority + "/settings/site_map_pairs");
    210     }
    211 
    212     @VisibleForTesting
    213     List<SearchIndexableRaw> getIndexablesForRawDataUri(Context packageContext, String packageName,
    214             Uri uri, String[] projection) {
    215         final ContentResolver resolver = packageContext.getContentResolver();
    216         final Cursor cursor = resolver.query(uri, projection, null, null, null);
    217         List<SearchIndexableRaw> rawData = new ArrayList<>();
    218 
    219         if (cursor == null) {
    220             Log.w(TAG, "Cannot add index data for Uri: " + uri.toString());
    221             return rawData;
    222         }
    223 
    224         try {
    225             final int count = cursor.getCount();
    226             if (count > 0) {
    227                 while (cursor.moveToNext()) {
    228                     final String title = cursor.getString(COLUMN_INDEX_RAW_TITLE);
    229                     final String summaryOn = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
    230                     final String summaryOff = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_OFF);
    231                     final String entries = cursor.getString(COLUMN_INDEX_RAW_ENTRIES);
    232                     final String keywords = cursor.getString(COLUMN_INDEX_RAW_KEYWORDS);
    233 
    234                     final String screenTitle = cursor.getString(COLUMN_INDEX_RAW_SCREEN_TITLE);
    235 
    236                     final String className = cursor.getString(COLUMN_INDEX_RAW_CLASS_NAME);
    237                     final int iconResId = cursor.getInt(COLUMN_INDEX_RAW_ICON_RESID);
    238 
    239                     final String action = cursor.getString(COLUMN_INDEX_RAW_INTENT_ACTION);
    240                     final String targetPackage = cursor.getString(
    241                             COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE);
    242                     final String targetClass = cursor.getString(
    243                             COLUMN_INDEX_RAW_INTENT_TARGET_CLASS);
    244 
    245                     final String key = cursor.getString(COLUMN_INDEX_RAW_KEY);
    246                     final int userId = cursor.getInt(COLUMN_INDEX_RAW_USER_ID);
    247 
    248                     SearchIndexableRaw data = new SearchIndexableRaw(packageContext);
    249                     data.title = title;
    250                     data.summaryOn = summaryOn;
    251                     data.summaryOff = summaryOff;
    252                     data.entries = entries;
    253                     data.keywords = keywords;
    254                     data.screenTitle = screenTitle;
    255                     data.className = className;
    256                     data.packageName = packageName;
    257                     data.iconResId = iconResId;
    258                     data.intentAction = action;
    259                     data.intentTargetPackage = targetPackage;
    260                     data.intentTargetClass = targetClass;
    261                     data.key = key;
    262                     data.userId = userId;
    263 
    264                     rawData.add(data);
    265                 }
    266             }
    267         } finally {
    268             cursor.close();
    269         }
    270 
    271         return rawData;
    272     }
    273 
    274     @VisibleForTesting
    275     List<Pair<String, String>> getSiteMapFromProvider(Context packageContext, Uri uri) {
    276         final ContentResolver resolver = packageContext.getContentResolver();
    277         final Cursor cursor = resolver.query(uri, null, null, null, null);
    278         if (cursor == null) {
    279             Log.d(TAG, "No site map information from " + packageContext.getPackageName());
    280             return null;
    281         }
    282         final List<Pair<String, String>> siteMapPairs = new ArrayList<>();
    283         try {
    284             final int count = cursor.getCount();
    285             if (count > 0) {
    286                 while (cursor.moveToNext()) {
    287                     final String parentClass = cursor.getString(cursor.getColumnIndex(
    288                             IndexDatabaseHelper.SiteMapColumns.PARENT_CLASS));
    289                     final String childClass = cursor.getString(cursor.getColumnIndex(
    290                             IndexDatabaseHelper.SiteMapColumns.CHILD_CLASS));
    291                     if (TextUtils.isEmpty(parentClass)  || TextUtils.isEmpty(childClass)) {
    292                         Log.w(TAG, "Incomplete site map pair: " + parentClass + "/" + childClass);
    293                         continue;
    294                     }
    295                     siteMapPairs.add(Pair.create(parentClass, childClass));
    296                 }
    297             }
    298             return siteMapPairs;
    299         } finally {
    300             cursor.close();
    301         }
    302 
    303     }
    304 
    305     private List<String> getNonIndexablesKeys(Context packageContext, Uri uri,
    306             String[] projection) {
    307 
    308         final ContentResolver resolver = packageContext.getContentResolver();
    309         final Cursor cursor = resolver.query(uri, projection, null, null, null);
    310         final List<String> result = new ArrayList<>();
    311 
    312         if (cursor == null) {
    313             Log.w(TAG, "Cannot add index data for Uri: " + uri.toString());
    314             return result;
    315         }
    316 
    317         try {
    318             final int count = cursor.getCount();
    319             if (count > 0) {
    320                 while (cursor.moveToNext()) {
    321                     final String key = cursor.getString(COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE);
    322 
    323                     if (TextUtils.isEmpty(key) && Log.isLoggable(TAG, Log.VERBOSE)) {
    324                         Log.v(TAG, "Empty non-indexable key from: "
    325                                 + packageContext.getPackageName());
    326                         continue;
    327                     }
    328 
    329                     result.add(key);
    330                 }
    331             }
    332             return result;
    333         } finally {
    334             cursor.close();
    335         }
    336     }
    337 
    338     /**
    339      * Only allow a "well known" SearchIndexablesProvider. The provider should:
    340      *
    341      * - have read/write {@link Manifest.permission#READ_SEARCH_INDEXABLES}
    342      * - be from a privileged package
    343      */
    344     @VisibleForTesting
    345     boolean isWellKnownProvider(ResolveInfo info) {
    346         final String authority = info.providerInfo.authority;
    347         final String packageName = info.providerInfo.applicationInfo.packageName;
    348 
    349         if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(packageName)) {
    350             return false;
    351         }
    352 
    353         final String readPermission = info.providerInfo.readPermission;
    354         final String writePermission = info.providerInfo.writePermission;
    355 
    356         if (TextUtils.isEmpty(readPermission) || TextUtils.isEmpty(writePermission)) {
    357             return false;
    358         }
    359 
    360         if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(readPermission) ||
    361                 !android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(writePermission)) {
    362             return false;
    363         }
    364 
    365         return isPrivilegedPackage(packageName, mContext);
    366     }
    367 
    368     /**
    369      * @return true if the {@param packageName} is privileged.
    370      */
    371     private boolean isPrivilegedPackage(String packageName, Context context) {
    372         final PackageManager pm = context.getPackageManager();
    373         try {
    374             PackageInfo packInfo = pm.getPackageInfo(packageName, 0);
    375             // TODO REFACTOR Changed privileged check
    376             return ((packInfo.applicationInfo.flags
    377                     & ApplicationInfo.FLAG_SYSTEM) != 0);
    378         } catch (PackageManager.NameNotFoundException e) {
    379             return false;
    380         }
    381     }
    382 }
    383