• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DashboardFeatureProviderImpl(Context context)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
getTilesForCategory(String key)82     public DashboardCategory getTilesForCategory(String key) {
83         return mCategoryManager.getTilesByCategory(mContext, key);
84     }
85 
86     @Override
getPreferencesForCategory(Activity activity, Context context, int sourceMetricsCategory, String key)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
getAllCategories()110     public List<DashboardCategory> getAllCategories() {
111         return mCategoryManager.getCategories(mContext);
112     }
113 
114     @Override
shouldTintIcon()115     public boolean shouldTintIcon() {
116         return mContext.getResources().getBoolean(R.bool.config_tintSettingIcon);
117     }
118 
119     @Override
getDashboardKeyForTile(Tile tile)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
bindPreferenceToTile(Activity activity, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder)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
getExtraIntentAction()196     public String getExtraIntentAction() {
197         return null;
198     }
199 
200     @Override
openTileIntent(Activity activity, Tile tile)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 
bindSummary(Preference preference, Tile tile)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
bindIcon(Preference preference, Tile tile)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 
launchIntentOrSelectProfile(Activity activity, Tile tile, Intent intent, int sourceMetricCategory)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 
isIntentResolvable(Intent intent)290     private boolean isIntentResolvable(Intent intent) {
291         return mPackageManager.resolveActivity(intent, 0) != null;
292     }
293 }
294