Home | History | Annotate | Download | only in dashboard
      1 /*
      2  * Copyright (C) 2015 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.dashboard;
     18 
     19 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
     20 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
     21 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
     22 
     23 import android.app.Activity;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.IContentProvider;
     27 import android.content.Intent;
     28 import android.content.pm.PackageManager;
     29 import android.graphics.drawable.Icon;
     30 import android.os.Bundle;
     31 import android.provider.Settings;
     32 import android.support.annotation.VisibleForTesting;
     33 import android.support.v7.preference.Preference;
     34 import android.text.TextUtils;
     35 import android.util.ArrayMap;
     36 import android.util.Log;
     37 import android.util.Pair;
     38 
     39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     40 import com.android.settings.R;
     41 import com.android.settings.SettingsActivity;
     42 import com.android.settings.overlay.FeatureFactory;
     43 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
     44 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
     45 import com.android.settingslib.drawer.CategoryManager;
     46 import com.android.settingslib.drawer.DashboardCategory;
     47 import com.android.settingslib.drawer.ProfileSelectDialog;
     48 import com.android.settingslib.drawer.Tile;
     49 import com.android.settingslib.drawer.TileUtils;
     50 import com.android.settingslib.utils.ThreadUtils;
     51 
     52 import java.util.ArrayList;
     53 import java.util.List;
     54 import java.util.Map;
     55 
     56 /**
     57  * Impl for {@code DashboardFeatureProvider}.
     58  */
     59 public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
     60 
     61     private static final String TAG = "DashboardFeatureImpl";
     62 
     63     private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_";
     64     private static final String META_DATA_KEY_INTENT_ACTION = "com.android.settings.intent.action";
     65     @VisibleForTesting
     66     static final String META_DATA_KEY_ORDER = "com.android.settings.order";
     67 
     68     protected final Context mContext;
     69 
     70     private final MetricsFeatureProvider mMetricsFeatureProvider;
     71     private final CategoryManager mCategoryManager;
     72     private final PackageManager mPackageManager;
     73 
     74     public DashboardFeatureProviderImpl(Context context) {
     75         mContext = context.getApplicationContext();
     76         mCategoryManager = CategoryManager.get(context, getExtraIntentAction());
     77         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     78         mPackageManager = context.getPackageManager();
     79     }
     80 
     81     @Override
     82     public DashboardCategory getTilesForCategory(String key) {
     83         return mCategoryManager.getTilesByCategory(mContext, key);
     84     }
     85 
     86     @Override
     87     public List<Preference> getPreferencesForCategory(Activity activity, Context context,
     88             int sourceMetricsCategory, String key) {
     89         final DashboardCategory category = getTilesForCategory(key);
     90         if (category == null) {
     91             Log.d(TAG, "NO dashboard tiles for " + TAG);
     92             return null;
     93         }
     94         final List<Tile> tiles = category.getTiles();
     95         if (tiles == null || tiles.isEmpty()) {
     96             Log.d(TAG, "tile list is empty, skipping category " + category.title);
     97             return null;
     98         }
     99         final List<Preference> preferences = new ArrayList<>();
    100         for (Tile tile : tiles) {
    101             final Preference pref = new Preference(context);
    102             bindPreferenceToTile(activity, sourceMetricsCategory, pref, tile, null /* key */,
    103                     Preference.DEFAULT_ORDER /* baseOrder */);
    104             preferences.add(pref);
    105         }
    106         return preferences;
    107     }
    108 
    109     @Override
    110     public List<DashboardCategory> getAllCategories() {
    111         return mCategoryManager.getCategories(mContext);
    112     }
    113 
    114     @Override
    115     public boolean shouldTintIcon() {
    116         return mContext.getResources().getBoolean(R.bool.config_tintSettingIcon);
    117     }
    118 
    119     @Override
    120     public String getDashboardKeyForTile(Tile tile) {
    121         if (tile == null || tile.intent == null) {
    122             return null;
    123         }
    124         if (!TextUtils.isEmpty(tile.key)) {
    125             return tile.key;
    126         }
    127         final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
    128         final ComponentName component = tile.intent.getComponent();
    129         sb.append(component.getClassName());
    130         return sb.toString();
    131     }
    132 
    133     @Override
    134     public void bindPreferenceToTile(Activity activity, int sourceMetricsCategory, Preference pref,
    135             Tile tile, String key, int baseOrder) {
    136         if (pref == null) {
    137             return;
    138         }
    139         pref.setTitle(tile.title);
    140         if (!TextUtils.isEmpty(key)) {
    141             pref.setKey(key);
    142         } else {
    143             pref.setKey(getDashboardKeyForTile(tile));
    144         }
    145         bindSummary(pref, tile);
    146         bindIcon(pref, tile);
    147         final Bundle metadata = tile.metaData;
    148         String clsName = null;
    149         String action = null;
    150         Integer order = null;
    151         if (metadata != null) {
    152             clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
    153             action = metadata.getString(META_DATA_KEY_INTENT_ACTION);
    154             if (metadata.containsKey(META_DATA_KEY_ORDER)
    155                     && metadata.get(META_DATA_KEY_ORDER) instanceof Integer) {
    156                 order = metadata.getInt(META_DATA_KEY_ORDER);
    157             }
    158         }
    159         if (!TextUtils.isEmpty(clsName)) {
    160             pref.setFragment(clsName);
    161         } else if (tile.intent != null) {
    162             final Intent intent = new Intent(tile.intent);
    163             intent.putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY,
    164                     sourceMetricsCategory);
    165             if (action != null) {
    166                 intent.setAction(action);
    167             }
    168             pref.setOnPreferenceClickListener(preference -> {
    169                 launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory);
    170                 return true;
    171             });
    172         }
    173         final String skipOffsetPackageName = activity.getPackageName();
    174         // If order is set in the meta data, use that order. Otherwise, check the intent priority.
    175         if (order == null && tile.priority != 0) {
    176             // Use negated priority for order, because tile priority is based on intent-filter
    177             // (larger value has higher priority). However pref order defines smaller value has
    178             // higher priority.
    179             order = -tile.priority;
    180         }
    181         if (order != null) {
    182             boolean shouldSkipBaseOrderOffset = false;
    183             if (tile.intent != null) {
    184                 shouldSkipBaseOrderOffset = TextUtils.equals(
    185                         skipOffsetPackageName, tile.intent.getComponent().getPackageName());
    186             }
    187             if (shouldSkipBaseOrderOffset || baseOrder == Preference.DEFAULT_ORDER) {
    188                 pref.setOrder(order);
    189             } else {
    190                 pref.setOrder(order + baseOrder);
    191             }
    192         }
    193     }
    194 
    195     @Override
    196     public String getExtraIntentAction() {
    197         return null;
    198     }
    199 
    200     @Override
    201     public void openTileIntent(Activity activity, Tile tile) {
    202         if (tile == null) {
    203             Intent intent = new Intent(Settings.ACTION_SETTINGS).addFlags(
    204                     Intent.FLAG_ACTIVITY_CLEAR_TASK);
    205             mContext.startActivity(intent);
    206             return;
    207         }
    208 
    209         if (tile.intent == null) {
    210             return;
    211         }
    212         final Intent intent = new Intent(tile.intent)
    213                 .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY,
    214                         MetricsEvent.DASHBOARD_SUMMARY)
    215                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    216         launchIntentOrSelectProfile(activity, tile, intent, MetricsEvent.DASHBOARD_SUMMARY);
    217     }
    218 
    219     private void bindSummary(Preference preference, Tile tile) {
    220         if (tile.summary != null) {
    221             preference.setSummary(tile.summary);
    222         } else if (tile.metaData != null
    223                 && tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
    224             // Set a placeholder summary before  starting to fetch real summary, this is necessary
    225             // to avoid preference height change.
    226             preference.setSummary(R.string.summary_placeholder);
    227 
    228             ThreadUtils.postOnBackgroundThread(() -> {
    229                 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
    230                 final String uri = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI);
    231                 final String summary = TileUtils.getTextFromUri(
    232                         mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY);
    233                 ThreadUtils.postOnMainThread(() -> preference.setSummary(summary));
    234             });
    235         } else {
    236             preference.setSummary(R.string.summary_placeholder);
    237         }
    238     }
    239 
    240     @VisibleForTesting
    241     void bindIcon(Preference preference, Tile tile) {
    242         if (tile.icon != null) {
    243             preference.setIcon(tile.icon.loadDrawable(preference.getContext()));
    244         } else if (tile.metaData != null
    245                 && tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
    246             ThreadUtils.postOnBackgroundThread(() -> {
    247                 String packageName = null;
    248                 if (tile.intent != null) {
    249                     Intent intent = tile.intent;
    250                     if (!TextUtils.isEmpty(intent.getPackage())) {
    251                         packageName = intent.getPackage();
    252                     } else if (intent.getComponent() != null) {
    253                         packageName = intent.getComponent().getPackageName();
    254                     }
    255                 }
    256                 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
    257                 final String uri = tile.metaData.getString(META_DATA_PREFERENCE_ICON_URI);
    258                 final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri(
    259                         mContext, packageName, uri, providerMap);
    260                 if (iconInfo == null) {
    261                     Log.w(TAG, "Failed to get icon from uri " + uri);
    262                     return;
    263                 }
    264                 final Icon icon = Icon.createWithResource(iconInfo.first, iconInfo.second);
    265                 ThreadUtils.postOnMainThread(() ->
    266                         preference.setIcon(icon.loadDrawable(preference.getContext()))
    267                 );
    268             });
    269         }
    270     }
    271 
    272     private void launchIntentOrSelectProfile(Activity activity, Tile tile, Intent intent,
    273             int sourceMetricCategory) {
    274         if (!isIntentResolvable(intent)) {
    275             Log.w(TAG, "Cannot resolve intent, skipping. " + intent);
    276             return;
    277         }
    278         ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile);
    279         if (tile.userHandle == null) {
    280             mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);
    281             activity.startActivityForResult(intent, 0);
    282         } else if (tile.userHandle.size() == 1) {
    283             mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);
    284             activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0));
    285         } else {
    286             ProfileSelectDialog.show(activity.getFragmentManager(), tile);
    287         }
    288     }
    289 
    290     private boolean isIntentResolvable(Intent intent) {
    291         return mPackageManager.resolveActivity(intent, 0) != null;
    292     }
    293 }
    294