Home | History | Annotate | Download | only in security
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.security;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.IContentProvider;
     22 import android.content.pm.PackageManager;
     23 import android.content.res.Resources;
     24 import android.graphics.drawable.Drawable;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import com.android.settings.R;
     28 import com.android.settings.trustagent.TrustAgentManager;
     29 import com.android.settings.trustagent.TrustAgentManagerImpl;
     30 import com.android.settingslib.drawer.DashboardCategory;
     31 import android.support.annotation.VisibleForTesting;
     32 import android.support.v4.content.ContextCompat;
     33 import android.support.v7.preference.Preference;
     34 import android.support.v7.preference.PreferenceScreen;
     35 import android.text.TextUtils;
     36 import android.util.ArrayMap;
     37 import android.util.Pair;
     38 
     39 import com.android.settingslib.drawer.Tile;
     40 import com.android.settingslib.drawer.TileUtils;
     41 
     42 import java.util.concurrent.Executors;
     43 import java.util.TreeMap;
     44 import java.util.Map;
     45 
     46 /** Implementation for {@code SecurityFeatureProvider}. */
     47 public class SecurityFeatureProviderImpl implements SecurityFeatureProvider {
     48 
     49     private TrustAgentManager mTrustAgentManager;
     50 
     51     @VisibleForTesting
     52     static final Drawable DEFAULT_ICON = null;
     53 
     54     @VisibleForTesting
     55     static Map<String, Pair<String, Integer>> sIconCache = new TreeMap<>();
     56 
     57     @VisibleForTesting
     58     static Map<String, String> sSummaryCache = new TreeMap<>();
     59 
     60     /** Update preferences with data from associated tiles. */
     61     public void updatePreferences(final Context context, final PreferenceScreen preferenceScreen,
     62             final DashboardCategory dashboardCategory) {
     63         if (preferenceScreen == null) {
     64             return;
     65         }
     66         int tilesCount = (dashboardCategory != null) ? dashboardCategory.getTilesCount() : 0;
     67         if (tilesCount == 0) {
     68             return;
     69         }
     70 
     71         initPreferences(context, preferenceScreen, dashboardCategory);
     72 
     73         // Fetching the summary and icon from the provider introduces latency, so do this on a
     74         // separate thread.
     75         Executors.newSingleThreadExecutor().execute(new Runnable() {
     76             @Override
     77             public void run() {
     78                 updatePreferencesToRunOnWorkerThread(context, preferenceScreen, dashboardCategory);
     79             }
     80         });
     81     }
     82 
     83     @VisibleForTesting
     84     static void initPreferences(Context context, PreferenceScreen preferenceScreen,
     85             DashboardCategory dashboardCategory) {
     86         int tilesCount = (dashboardCategory != null) ? dashboardCategory.getTilesCount() : 0;
     87         for (int i = 0; i < tilesCount; i++) {
     88             Tile tile = dashboardCategory.getTile(i);
     89             // If the tile does not have a key or appropriate meta data, skip it.
     90             if (TextUtils.isEmpty(tile.key) || (tile.metaData == null)) {
     91                 continue;
     92             }
     93             Preference matchingPref = preferenceScreen.findPreference(tile.key);
     94             // If the tile does not have a matching preference, skip it.
     95             if (matchingPref == null) {
     96                 continue;
     97             }
     98             // Either remove an icon by replacing them with nothing, or use the cached one since
     99             // there is a delay in fetching the injected icon, and we don't want an inappropriate
    100             // icon to be displayed while waiting for the injected icon.
    101             final String iconUri =
    102                     tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_URI, null);
    103             Drawable drawable = DEFAULT_ICON;
    104             if ((iconUri != null) && sIconCache.containsKey(iconUri)) {
    105                 Pair<String, Integer> icon = sIconCache.get(iconUri);
    106                 try {
    107                     drawable = context.getPackageManager()
    108                             .getResourcesForApplication(icon.first /* package name */)
    109                                     .getDrawable(icon.second /* res id */,
    110                                             context.getTheme());
    111                 } catch (PackageManager.NameNotFoundException e) {
    112                     // Ignore and just load the default icon.
    113                 }
    114             }
    115             matchingPref.setIcon(drawable);
    116             // Either reserve room for the summary or load the cached one. This prevents the title
    117             // from shifting when the final summary is injected.
    118             final String summaryUri =
    119                     tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, null);
    120             String summary = context.getString(R.string.summary_placeholder);
    121             if ((summaryUri != null) && sSummaryCache.containsKey(summaryUri)) {
    122                 summary = sSummaryCache.get(summaryUri);
    123             }
    124             matchingPref.setSummary(summary);
    125         }
    126     }
    127 
    128     @VisibleForTesting
    129     void updatePreferencesToRunOnWorkerThread(Context context, PreferenceScreen preferenceScreen,
    130             DashboardCategory dashboardCategory) {
    131 
    132         int tilesCount = (dashboardCategory != null) ? dashboardCategory.getTilesCount() : 0;
    133         Map<String, IContentProvider> providerMap = new ArrayMap<>();
    134         for (int i = 0; i < tilesCount; i++) {
    135             Tile tile = dashboardCategory.getTile(i);
    136             // If the tile does not have a key or appropriate meta data, skip it.
    137             if (TextUtils.isEmpty(tile.key) || (tile.metaData == null)) {
    138                 continue;
    139             }
    140             Preference matchingPref = preferenceScreen.findPreference(tile.key);
    141             // If the tile does not have a matching preference, skip it.
    142             if (matchingPref == null) {
    143                 continue;
    144             }
    145             // Check if the tile has content providers for dynamically updatable content.
    146             final String iconUri =
    147                     tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_URI, null);
    148             final String summaryUri =
    149                     tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, null);
    150             if (!TextUtils.isEmpty(iconUri)) {
    151                 String packageName = null;
    152                 if (tile.intent != null) {
    153                     Intent intent = tile.intent;
    154                     if (!TextUtils.isEmpty(intent.getPackage())) {
    155                         packageName = intent.getPackage();
    156                     } else if (intent.getComponent() != null) {
    157                         packageName = intent.getComponent().getPackageName();
    158                     }
    159                 }
    160                 Pair<String, Integer> icon =
    161                         TileUtils.getIconFromUri(context, packageName, iconUri, providerMap);
    162                 if (icon != null) {
    163                     sIconCache.put(iconUri, icon);
    164                     // Icon is only returned if the icon belongs to Settings or the target app.
    165                     // setIcon must be called on the UI thread.
    166                     new Handler(Looper.getMainLooper()).post(new Runnable() {
    167                         @Override
    168                         public void run() {
    169                             try {
    170                                 matchingPref.setIcon(context.getPackageManager()
    171                                         .getResourcesForApplication(icon.first /* package name */)
    172                                                 .getDrawable(icon.second /* res id */,
    173                                                         context.getTheme()));
    174                             } catch (PackageManager.NameNotFoundException
    175                                     | Resources.NotFoundException e) {
    176                                 // Intentionally ignored. If icon resources cannot be found, do not
    177                                 // update.
    178                             }
    179                         }
    180                     });
    181                 }
    182             }
    183             if (!TextUtils.isEmpty(summaryUri)) {
    184                 String summary = TileUtils.getTextFromUri(context, summaryUri, providerMap,
    185                         TileUtils.META_DATA_PREFERENCE_SUMMARY);
    186                 sSummaryCache.put(summaryUri, summary);
    187                 // setSummary must be called on UI thread.
    188                 new Handler(Looper.getMainLooper()).post(new Runnable() {
    189                     @Override
    190                     public void run() {
    191                         // Only update the summary if it has actually changed.
    192                         if (summary == null) {
    193                             if (matchingPref.getSummary() != null) {
    194                                 matchingPref.setSummary(summary);
    195                             }
    196                         } else if (!summary.equals(matchingPref.getSummary())) {
    197                             matchingPref.setSummary(summary);
    198                         }
    199                     }
    200                 });
    201             }
    202         }
    203     }
    204 
    205     @Override
    206     public TrustAgentManager getTrustAgentManager() {
    207         if (mTrustAgentManager == null) {
    208             mTrustAgentManager = new TrustAgentManagerImpl();
    209         }
    210         return mTrustAgentManager;
    211     }
    212 }
    213