blob: 06f145624449c42eb91d87ae51ea27173b8f9e71 [file] [log] [blame]
Jason Monk744b6362015-11-03 18:24:29 -05001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
Jason Monkf509d7e2016-01-07 16:22:53 -05004 * 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
Jason Monk744b6362015-11-03 18:24:29 -05007 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
Jason Monkf509d7e2016-01-07 16:22:53 -050010 * 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
Jason Monk744b6362015-11-03 18:24:29 -050015 */
Jason Monk744b6362015-11-03 18:24:29 -050016package com.android.settingslib.drawer;
17
Jason Monk0d72d202015-11-04 13:16:00 -050018import android.app.ActivityManager;
Jason Monk744b6362015-11-03 18:24:29 -050019import android.content.Context;
Shahriyar Amini676add42016-12-16 11:29:39 -080020import android.content.IContentProvider;
Jason Monk744b6362015-11-03 18:24:29 -050021import android.content.Intent;
22import android.content.pm.ActivityInfo;
Jason Monke79790b2015-12-02 15:39:19 -050023import android.content.pm.ApplicationInfo;
Jason Monk744b6362015-11-03 18:24:29 -050024import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.res.Resources;
27import android.graphics.drawable.Icon;
Shahriyar Amini676add42016-12-16 11:29:39 -080028import android.net.Uri;
Jason Monk744b6362015-11-03 18:24:29 -050029import android.os.Bundle;
Shahriyar Amini676add42016-12-16 11:29:39 -080030import android.os.RemoteException;
Jason Monk744b6362015-11-03 18:24:29 -050031import android.os.UserHandle;
32import android.os.UserManager;
Jason Monk64600cf2016-06-30 13:15:48 -040033import android.provider.Settings.Global;
Jason Monk744b6362015-11-03 18:24:29 -050034import android.text.TextUtils;
35import android.util.Log;
36import android.util.Pair;
37
Fan Zhang0d7b6cf2018-07-23 13:52:34 -070038import androidx.annotation.VisibleForTesting;
39
Jason Monk744b6362015-11-03 18:24:29 -050040import java.util.ArrayList;
41import java.util.Collections;
42import java.util.Comparator;
43import java.util.HashMap;
44import java.util.List;
45import java.util.Map;
46
47public class TileUtils {
48
Jason Monk0d72d202015-11-04 13:16:00 -050049 private static final boolean DEBUG = false;
Joe Onorato93dcff02016-02-01 17:44:14 -080050 private static final boolean DEBUG_TIMING = false;
Jason Monk744b6362015-11-03 18:24:29 -050051
52 private static final String LOG_TAG = "TileUtils";
Fan Zhang0d7b6cf2018-07-23 13:52:34 -070053 @VisibleForTesting
54 static final String SETTING_PKG = "com.android.settings";
Jason Monk744b6362015-11-03 18:24:29 -050055
56 /**
57 * Settings will search for system activities of this action and add them as a top level
58 * settings tile using the following parameters.
59 *
60 * <p>A category must be specified in the meta-data for the activity named
61 * {@link #EXTRA_CATEGORY_KEY}
62 *
63 * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
64 * otherwise the label for the activity will be used.
65 *
66 * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
67 * otherwise the icon for the activity will be used.
68 *
69 * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
70 */
Lujiang Xue770ca1a2017-10-24 09:49:48 -070071 public static final String EXTRA_SETTINGS_ACTION =
Jason Monk744b6362015-11-03 18:24:29 -050072 "com.android.settings.action.EXTRA_SETTINGS";
73
74 /**
Fan Zhangca60fac2016-11-02 15:54:53 -070075 * @See {@link #EXTRA_SETTINGS_ACTION}.
76 */
77 private static final String IA_SETTINGS_ACTION =
78 "com.android.settings.action.IA_SETTINGS";
79
80
81 /**
Jason Monk744b6362015-11-03 18:24:29 -050082 * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
83 */
84 private static final String SETTINGS_ACTION =
85 "com.android.settings.action.SETTINGS";
86
87 private static final String OPERATOR_SETTINGS =
88 "com.android.settings.OPERATOR_APPLICATION_SETTING";
89
90 private static final String OPERATOR_DEFAULT_CATEGORY =
91 "com.android.settings.category.wireless";
92
93 private static final String MANUFACTURER_SETTINGS =
94 "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
95
96 private static final String MANUFACTURER_DEFAULT_CATEGORY =
97 "com.android.settings.category.device";
98
99 /**
100 * The key used to get the category from metadata of activities of action
101 * {@link #EXTRA_SETTINGS_ACTION}
Fan Zhang6f6e9562018-06-18 11:30:00 -0700102 * The value must be from {@link CategoryKey}.
Jason Monk744b6362015-11-03 18:24:29 -0500103 */
104 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
105
106 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800107 * The key used to get the package name of the icon resource for the preference.
108 */
109 private static final String EXTRA_PREFERENCE_ICON_PACKAGE =
110 "com.android.settings.icon_package";
111
112 /**
Jason Monk744b6362015-11-03 18:24:29 -0500113 * Name of the meta-data item that should be set in the AndroidManifest.xml
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800114 * to specify the key that should be used for the preference.
115 */
116 public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
117
118 /**
119 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500120 * to specify the icon that should be displayed for the preference.
121 */
122 public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
123
124 /**
125 * Name of the meta-data item that should be set in the AndroidManifest.xml
Fan Zhanga8d23aa2018-04-12 14:01:26 -0700126 * to specify the icon background color. The value may or may not be used by Settings app.
127 */
128 public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_HINT =
129 "com.android.settings.bg.hint";
130
131 /**
132 * Name of the meta-data item that should be set in the AndroidManifest.xml
Shahriyar Amini676add42016-12-16 11:29:39 -0800133 * to specify the content provider providing the icon that should be displayed for
134 * the preference.
135 *
136 * Icon provided by the content provider overrides any static icon.
137 */
138 public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri";
139
140 /**
141 * Name of the meta-data item that should be set in the AndroidManifest.xml
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700142 * to specify whether the icon is tintable. This should be a boolean value {@code true} or
143 * {@code false}, set using {@code android:value}
Jason Monk744b6362015-11-03 18:24:29 -0500144 */
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700145 public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
146 "com.android.settings.icon_tintable";
Jason Monk744b6362015-11-03 18:24:29 -0500147
148 /**
149 * Name of the meta-data item that should be set in the AndroidManifest.xml
William Luh4c978a32017-03-31 15:08:16 -0700150 * to specify the title that should be displayed for the preference.
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700151 *
152 * <p>Note: It is preferred to provide this value using {@code android:resource} with a string
153 * resource for localization.
William Luh4c978a32017-03-31 15:08:16 -0700154 */
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700155 public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
156
157 /**
William Luh4c978a32017-03-31 15:08:16 -0700158 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500159 * to specify the summary text that should be displayed for the preference.
160 */
161 public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
162
Shahriyar Amini676add42016-12-16 11:29:39 -0800163 /**
164 * Name of the meta-data item that should be set in the AndroidManifest.xml
165 * to specify the content provider providing the summary text that should be displayed for the
166 * preference.
167 *
168 * Summary provided by the content provider overrides any static summary.
169 */
170 public static final String META_DATA_PREFERENCE_SUMMARY_URI =
171 "com.android.settings.summary_uri";
172
Fan Zhang22a56d72016-09-27 17:52:00 -0700173 /**
arangelov24eec2f2018-05-30 18:24:23 +0100174 * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
175 * the app will always be run in the primary profile.
176 *
177 * @see #META_DATA_KEY_PROFILE
178 */
179 public static final String PROFILE_PRIMARY = "primary_profile_only";
180
181 /**
182 * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, the user
183 * will be presented with a dialog to choose the profile the app will be run in.
184 *
185 * @see #META_DATA_KEY_PROFILE
186 */
187 public static final String PROFILE_ALL = "all_profiles";
188
189 /**
190 * Name of the meta-data item that should be set in the AndroidManifest.xml
191 * to specify the profile in which the app should be run when the device has a managed profile.
192 * The default value is {@link #PROFILE_ALL} which means the user will be presented with a
193 * dialog to choose the profile. If set to {@link #PROFILE_PRIMARY} the app will always be
194 * run in the primary profile.
195 *
196 * @see #PROFILE_PRIMARY
197 * @see #PROFILE_ALL
198 */
199 public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
200
201 /**
Fan Zhang22a56d72016-09-27 17:52:00 -0700202 * Build a list of DashboardCategory.
roger xue8f06ab02016-12-08 14:09:50 -0800203 * @param extraAction additional intent filter action to be usetileutild to build the dashboard
Doris Ling485df112016-12-19 10:45:47 -0800204 * categories
205 */
206 public static List<DashboardCategory> getCategories(Context context,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700207 Map<Pair<String, String>, Tile> cache, String extraAction) {
Jason Monke79790b2015-12-02 15:39:19 -0500208 final long startTime = System.currentTimeMillis();
Jason Monk64600cf2016-06-30 13:15:48 -0400209 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
210 != 0;
Jason Monkf509d7e2016-01-07 16:22:53 -0500211 ArrayList<Tile> tiles = new ArrayList<>();
roger xue8f06ab02016-12-08 14:09:50 -0800212 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
Jason Monk744b6362015-11-03 18:24:29 -0500213 for (UserHandle user : userManager.getUserProfiles()) {
214 // TODO: Needs much optimization, too many PM queries going on here.
Jason Monk0d72d202015-11-04 13:16:00 -0500215 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
216 // Only add Settings for this user.
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700217 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
Jason Monk0d72d202015-11-04 13:16:00 -0500218 getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700219 OPERATOR_DEFAULT_CATEGORY, tiles, false, true);
Jason Monk0d72d202015-11-04 13:16:00 -0500220 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700221 MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true);
Jason Monk0d72d202015-11-04 13:16:00 -0500222 }
Jason Monk64600cf2016-06-30 13:15:48 -0400223 if (setup) {
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700224 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
225 getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
226 if (extraAction != null) {
227 getTilesForAction(context, user, extraAction, cache, null, tiles, false);
228 }
Jason Monk64600cf2016-06-30 13:15:48 -0400229 }
Jason Monk744b6362015-11-03 18:24:29 -0500230 }
Fan Zhang22a56d72016-09-27 17:52:00 -0700231
Jason Monk744b6362015-11-03 18:24:29 -0500232 HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
Jason Monkf509d7e2016-01-07 16:22:53 -0500233 for (Tile tile : tiles) {
Jason Monk744b6362015-11-03 18:24:29 -0500234 DashboardCategory category = categoryMap.get(tile.category);
235 if (category == null) {
Fan Zhangc75174b2018-07-19 14:07:58 -0700236 category = new DashboardCategory();
237 category.key = tile.category;
238
Jason Monk744b6362015-11-03 18:24:29 -0500239 if (category == null) {
240 Log.w(LOG_TAG, "Couldn't find category " + tile.category);
241 continue;
242 }
243 categoryMap.put(category.key, category);
244 }
245 category.addTile(tile);
246 }
247 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
248 for (DashboardCategory category : categories) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800249 category.sortTiles();
Jason Monk744b6362015-11-03 18:24:29 -0500250 }
251 Collections.sort(categories, CATEGORY_COMPARATOR);
Jason Monke79790b2015-12-02 15:39:19 -0500252 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
253 + (System.currentTimeMillis() - startTime) + " ms");
Jason Monk744b6362015-11-03 18:24:29 -0500254 return categories;
255 }
256
Jason Monk744b6362015-11-03 18:24:29 -0500257 private static void getTilesForAction(Context context,
Jason Monkf509d7e2016-01-07 16:22:53 -0500258 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700259 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings) {
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900260 getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700261 requireSettings, requireSettings);
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900262 }
263
264 private static void getTilesForAction(Context context,
265 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
266 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700267 boolean usePriority) {
Jason Monk744b6362015-11-03 18:24:29 -0500268 Intent intent = new Intent(action);
Jason Monkf509d7e2016-01-07 16:22:53 -0500269 if (requireSettings) {
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700270 intent.setPackage(SETTING_PKG);
Jason Monkf509d7e2016-01-07 16:22:53 -0500271 }
272 getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700273 usePriority, true, true);
Jason Monkf509d7e2016-01-07 16:22:53 -0500274 }
275
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700276 public static void getTilesForIntent(
277 Context context, UserHandle user, Intent intent,
Jason Monkf509d7e2016-01-07 16:22:53 -0500278 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700279 boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500280 PackageManager pm = context.getPackageManager();
Jason Monk744b6362015-11-03 18:24:29 -0500281 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
282 PackageManager.GET_META_DATA, user.getIdentifier());
283 for (ResolveInfo resolved : results) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500284 if (!resolved.system) {
285 // Do not allow any app to add to settings, only system ones.
286 continue;
Jason Monk744b6362015-11-03 18:24:29 -0500287 }
288 ActivityInfo activityInfo = resolved.activityInfo;
289 Bundle metaData = activityInfo.metaData;
290 String categoryKey = defaultCategory;
Fan Zhangb12e1972016-09-29 14:43:43 -0700291
292 // Load category
293 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
294 && categoryKey == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500295 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
296 + intent + " missing metadata "
Jason Monk744b6362015-11-03 18:24:29 -0500297 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
298 continue;
Fan Zhangb12e1972016-09-29 14:43:43 -0700299 } else {
300 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
Jason Monk744b6362015-11-03 18:24:29 -0500301 }
Fan Zhangb12e1972016-09-29 14:43:43 -0700302
Fan Zhang1b0dfa32018-07-20 12:57:55 -0700303 Pair<String, String> key = new Pair<>(activityInfo.packageName, activityInfo.name);
Jason Monkf509d7e2016-01-07 16:22:53 -0500304 Tile tile = addedCache.get(key);
Jason Monk744b6362015-11-03 18:24:29 -0500305 if (tile == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500306 tile = new Tile();
Jason Monk744b6362015-11-03 18:24:29 -0500307 tile.intent = new Intent().setClassName(
308 activityInfo.packageName, activityInfo.name);
309 tile.category = categoryKey;
Jason Monkf509d7e2016-01-07 16:22:53 -0500310 tile.priority = usePriority ? resolved.priority : 0;
Jason Monke79790b2015-12-02 15:39:19 -0500311 tile.metaData = activityInfo.metaData;
312 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700313 pm, forceTintExternalIcon);
Jason Monk744b6362015-11-03 18:24:29 -0500314 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
Jason Monk744b6362015-11-03 18:24:29 -0500315 addedCache.put(key, tile);
316 }
Ajay Nadathurd1b730b2017-09-14 17:12:27 -0700317
Jason Monk744b6362015-11-03 18:24:29 -0500318 if (!tile.userHandle.contains(user)) {
319 tile.userHandle.add(user);
320 }
321 if (!outTiles.contains(tile)) {
322 outTiles.add(tile);
323 }
324 }
325 }
326
Jason Monkf509d7e2016-01-07 16:22:53 -0500327 private static boolean updateTileData(Context context, Tile tile,
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700328 ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm,
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700329 boolean forceTintExternalIcon) {
Jason Monke79790b2015-12-02 15:39:19 -0500330 if (applicationInfo.isSystemApp()) {
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700331 boolean forceTintIcon = false;
Jason Monke79790b2015-12-02 15:39:19 -0500332 int icon = 0;
333 CharSequence title = null;
334 String summary = null;
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800335 String keyHint = null;
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700336 boolean isIconTintable = false;
Jason Monk744b6362015-11-03 18:24:29 -0500337
Jason Monke79790b2015-12-02 15:39:19 -0500338 // Get the activity's meta-data
339 try {
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700340 Resources res = pm.getResourcesForApplication(applicationInfo.packageName);
Jason Monke79790b2015-12-02 15:39:19 -0500341 Bundle metaData = activityInfo.metaData;
Jason Monk744b6362015-11-03 18:24:29 -0500342
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700343 if (forceTintExternalIcon
344 && !context.getPackageName().equals(applicationInfo.packageName)) {
345 isIconTintable = true;
346 forceTintIcon = true;
347 }
348
Jason Monke79790b2015-12-02 15:39:19 -0500349 if (res != null && metaData != null) {
William Luh204af1c2017-02-23 11:10:05 -0800350 if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
Jason Monke79790b2015-12-02 15:39:19 -0500351 icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
Jason Monk744b6362015-11-03 18:24:29 -0500352 }
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700353 if (metaData.containsKey(META_DATA_PREFERENCE_ICON_TINTABLE)) {
354 if (forceTintIcon) {
355 Log.w(LOG_TAG, "Ignoring icon tintable for " + activityInfo);
356 } else {
357 isIconTintable =
358 metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
359 }
360 }
Fan Zhangdadfd502017-07-26 11:00:51 -0700361 if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500362 if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
363 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
364 } else {
365 title = metaData.getString(META_DATA_PREFERENCE_TITLE);
366 }
Jason Monk744b6362015-11-03 18:24:29 -0500367 }
William Luh204af1c2017-02-23 11:10:05 -0800368 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500369 if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
370 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
371 } else {
372 summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
373 }
Jason Monk744b6362015-11-03 18:24:29 -0500374 }
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800375 if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
376 if (metaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
377 keyHint = res.getString(metaData.getInt(META_DATA_PREFERENCE_KEYHINT));
378 } else {
379 keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
380 }
381 }
Jason Monk744b6362015-11-03 18:24:29 -0500382 }
Jason Monke79790b2015-12-02 15:39:19 -0500383 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
384 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
Jason Monk744b6362015-11-03 18:24:29 -0500385 }
Jason Monke79790b2015-12-02 15:39:19 -0500386
387 // Set the preference title to the activity's label if no
388 // meta-data is found
389 if (TextUtils.isEmpty(title)) {
390 title = activityInfo.loadLabel(pm).toString();
391 }
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800392
393 // Set the icon
Fan Zhangdadfd502017-07-26 11:00:51 -0700394 if (icon == 0) {
395 // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
396 // ICON_URI should be loaded in app UI when need the icon object.
397 if (!tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800398 icon = activityInfo.icon;
399 }
Fan Zhangdadfd502017-07-26 11:00:51 -0700400 }
401 if (icon != 0) {
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800402 tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
Jason Monke79790b2015-12-02 15:39:19 -0500403 }
404
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800405 // Set title and summary for the preference
Jason Monke79790b2015-12-02 15:39:19 -0500406 tile.title = title;
407 tile.summary = summary;
408 // Replace the intent with this specific activity
409 tile.intent = new Intent().setClassName(activityInfo.packageName,
410 activityInfo.name);
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800411 // Suggest a key for this tile
412 tile.key = keyHint;
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700413 tile.isIconTintable = isIconTintable;
Jason Monke79790b2015-12-02 15:39:19 -0500414
415 return true;
Jason Monk744b6362015-11-03 18:24:29 -0500416 }
417
418 return false;
419 }
420
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800421 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800422 * Gets the icon package name and resource id from content provider.
Fan Zhangdadfd502017-07-26 11:00:51 -0700423 * @param context context
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800424 * @param packageName package name of the target activity
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800425 * @param uriString URI for the content provider
426 * @param providerMap Maps URI authorities to providers
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800427 * @return package name and resource id of the icon specified
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800428 */
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800429 public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
430 String uriString, Map<String, IContentProvider> providerMap) {
Shahriyar Amini676add42016-12-16 11:29:39 -0800431 Bundle bundle = getBundleFromUri(context, uriString, providerMap);
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800432 if (bundle == null) {
433 return null;
434 }
435 String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
436 if (TextUtils.isEmpty(iconPackageName)) {
437 return null;
438 }
439 int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
440 if (resId == 0) {
441 return null;
442 }
443 // Icon can either come from the target package or from the Settings app.
444 if (iconPackageName.equals(packageName)
445 || iconPackageName.equals(context.getPackageName())) {
446 return Pair.create(iconPackageName, bundle.getInt(META_DATA_PREFERENCE_ICON, 0));
447 }
448 return null;
Shahriyar Amini676add42016-12-16 11:29:39 -0800449 }
450
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800451 /**
452 * Gets text associated with the input key from the content provider.
Fan Zhangdadfd502017-07-26 11:00:51 -0700453 * @param context context
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800454 * @param uriString URI for the content provider
455 * @param providerMap Maps URI authorities to providers
456 * @param key Key mapping to the text in bundle returned by the content provider
457 * @return Text associated with the key, if returned by the content provider
458 */
459 public static String getTextFromUri(Context context, String uriString,
Shahriyar Amini676add42016-12-16 11:29:39 -0800460 Map<String, IContentProvider> providerMap, String key) {
461 Bundle bundle = getBundleFromUri(context, uriString, providerMap);
462 return (bundle != null) ? bundle.getString(key) : null;
463 }
464
465 private static Bundle getBundleFromUri(Context context, String uriString,
466 Map<String, IContentProvider> providerMap) {
467 if (TextUtils.isEmpty(uriString)) {
468 return null;
469 }
470 Uri uri = Uri.parse(uriString);
471 String method = getMethodFromUri(uri);
472 if (TextUtils.isEmpty(method)) {
473 return null;
474 }
475 IContentProvider provider = getProviderFromUri(context, uri, providerMap);
476 if (provider == null) {
477 return null;
478 }
479 try {
480 return provider.call(context.getPackageName(), method, uriString, null);
481 } catch (RemoteException e) {
482 return null;
483 }
484 }
485
Ajay Nadathur00932ebb2017-08-30 14:56:46 -0700486 private static String getString(Bundle bundle, String key) {
487 return bundle == null ? null : bundle.getString(key);
488 }
489
Shahriyar Amini676add42016-12-16 11:29:39 -0800490 private static IContentProvider getProviderFromUri(Context context, Uri uri,
491 Map<String, IContentProvider> providerMap) {
492 if (uri == null) {
493 return null;
494 }
495 String authority = uri.getAuthority();
496 if (TextUtils.isEmpty(authority)) {
497 return null;
498 }
499 if (!providerMap.containsKey(authority)) {
William Luh5a0a0d82017-04-18 11:34:38 -0700500 providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
Shahriyar Amini676add42016-12-16 11:29:39 -0800501 }
502 return providerMap.get(authority);
503 }
504
505 /** Returns the first path segment of the uri if it exists as the method, otherwise null. */
506 static String getMethodFromUri(Uri uri) {
507 if (uri == null) {
508 return null;
509 }
510 List<String> pathSegments = uri.getPathSegments();
511 if ((pathSegments == null) || pathSegments.isEmpty()) {
512 return null;
513 }
514 return pathSegments.get(0);
515 }
516
Jason Monk744b6362015-11-03 18:24:29 -0500517 private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
518 new Comparator<DashboardCategory>() {
519 @Override
520 public int compare(DashboardCategory lhs, DashboardCategory rhs) {
521 return rhs.priority - lhs.priority;
522 }
523 };
524}