blob: 9620a9153f80fa106822562984a32bda7b30ed6b [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;
Maurice Lamd8ae77b2017-03-23 21:26:01 -070037import android.widget.RemoteViews;
Jason Monk744b6362015-11-03 18:24:29 -050038
39import java.util.ArrayList;
40import java.util.Collections;
41import java.util.Comparator;
42import java.util.HashMap;
43import java.util.List;
44import java.util.Map;
45
46public class TileUtils {
47
Jason Monk0d72d202015-11-04 13:16:00 -050048 private static final boolean DEBUG = false;
Joe Onorato93dcff02016-02-01 17:44:14 -080049 private static final boolean DEBUG_TIMING = false;
Jason Monk744b6362015-11-03 18:24:29 -050050
51 private static final String LOG_TAG = "TileUtils";
52
53 /**
54 * Settings will search for system activities of this action and add them as a top level
55 * settings tile using the following parameters.
56 *
57 * <p>A category must be specified in the meta-data for the activity named
58 * {@link #EXTRA_CATEGORY_KEY}
59 *
60 * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
61 * otherwise the label for the activity will be used.
62 *
63 * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
64 * otherwise the icon for the activity will be used.
65 *
66 * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
67 */
68 private static final String EXTRA_SETTINGS_ACTION =
69 "com.android.settings.action.EXTRA_SETTINGS";
70
71 /**
Fan Zhangca60fac2016-11-02 15:54:53 -070072 * @See {@link #EXTRA_SETTINGS_ACTION}.
73 */
74 private static final String IA_SETTINGS_ACTION =
75 "com.android.settings.action.IA_SETTINGS";
76
77
78 /**
Jason Monk744b6362015-11-03 18:24:29 -050079 * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
80 */
81 private static final String SETTINGS_ACTION =
82 "com.android.settings.action.SETTINGS";
83
84 private static final String OPERATOR_SETTINGS =
85 "com.android.settings.OPERATOR_APPLICATION_SETTING";
86
87 private static final String OPERATOR_DEFAULT_CATEGORY =
88 "com.android.settings.category.wireless";
89
90 private static final String MANUFACTURER_SETTINGS =
91 "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
92
93 private static final String MANUFACTURER_DEFAULT_CATEGORY =
94 "com.android.settings.category.device";
95
96 /**
97 * The key used to get the category from metadata of activities of action
98 * {@link #EXTRA_SETTINGS_ACTION}
99 * The value must be one of:
100 * <li>com.android.settings.category.wireless</li>
101 * <li>com.android.settings.category.device</li>
102 * <li>com.android.settings.category.personal</li>
103 * <li>com.android.settings.category.system</li>
104 */
105 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
106
107 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800108 * The key used to get the package name of the icon resource for the preference.
109 */
110 private static final String EXTRA_PREFERENCE_ICON_PACKAGE =
111 "com.android.settings.icon_package";
112
113 /**
Jason Monk744b6362015-11-03 18:24:29 -0500114 * Name of the meta-data item that should be set in the AndroidManifest.xml
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800115 * to specify the key that should be used for the preference.
116 */
117 public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
118
119 /**
120 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500121 * to specify the icon that should be displayed for the preference.
122 */
123 public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
124
125 /**
126 * Name of the meta-data item that should be set in the AndroidManifest.xml
Shahriyar Amini676add42016-12-16 11:29:39 -0800127 * to specify the content provider providing the icon that should be displayed for
128 * the preference.
129 *
130 * Icon provided by the content provider overrides any static icon.
131 */
132 public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri";
133
134 /**
135 * Name of the meta-data item that should be set in the AndroidManifest.xml
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700136 * to specify whether the icon is tintable. This should be a boolean value {@code true} or
137 * {@code false}, set using {@code android:value}
Jason Monk744b6362015-11-03 18:24:29 -0500138 */
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700139 public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
140 "com.android.settings.icon_tintable";
Jason Monk744b6362015-11-03 18:24:29 -0500141
142 /**
143 * Name of the meta-data item that should be set in the AndroidManifest.xml
William Luh4c978a32017-03-31 15:08:16 -0700144 * to specify the title that should be displayed for the preference.
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700145 *
146 * <p>Note: It is preferred to provide this value using {@code android:resource} with a string
147 * resource for localization.
William Luh4c978a32017-03-31 15:08:16 -0700148 */
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700149 public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
150
151 /**
152 * @deprecated Use {@link #META_DATA_PREFERENCE_TITLE} with {@code android:resource}
153 */
154 @Deprecated
William Luh4c978a32017-03-31 15:08:16 -0700155 public static final String META_DATA_PREFERENCE_TITLE_RES_ID =
156 "com.android.settings.title.resid";
157
158 /**
159 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500160 * to specify the summary text that should be displayed for the preference.
161 */
162 public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
163
Shahriyar Amini676add42016-12-16 11:29:39 -0800164 /**
165 * Name of the meta-data item that should be set in the AndroidManifest.xml
166 * to specify the content provider providing the summary text that should be displayed for the
167 * preference.
168 *
169 * Summary provided by the content provider overrides any static summary.
170 */
171 public static final String META_DATA_PREFERENCE_SUMMARY_URI =
172 "com.android.settings.summary_uri";
173
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700174 /**
175 * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
176 * custom view which should be displayed for the preference. The custom view will be inflated
177 * as a remote view.
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700178 *
179 * This also can be used with {@link META_DATA_PREFERENCE_SUMMARY_URI} above, by setting the id
180 * of the summary TextView to '@android:id/summary'.
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700181 */
182 public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
183 "com.android.settings.custom_view";
184
roger xue8f06ab02016-12-08 14:09:50 -0800185 public static final String SETTING_PKG = "com.android.settings";
Jason Monk744b6362015-11-03 18:24:29 -0500186
Fan Zhang22a56d72016-09-27 17:52:00 -0700187 /**
188 * Build a list of DashboardCategory. Each category must be defined in manifest.
189 * eg: .Settings$DeviceSettings
190 * @deprecated
191 */
192 @Deprecated
Jason Monk744b6362015-11-03 18:24:29 -0500193 public static List<DashboardCategory> getCategories(Context context,
Fan Zhang22a56d72016-09-27 17:52:00 -0700194 Map<Pair<String, String>, Tile> cache) {
195 return getCategories(context, cache, true /*categoryDefinedInManifest*/);
196 }
197
198 /**
199 * Build a list of DashboardCategory.
200 * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
201 * represent this category (eg: .Settings$DeviceSettings)
202 */
203 public static List<DashboardCategory> getCategories(Context context,
204 Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) {
roger xue8f06ab02016-12-08 14:09:50 -0800205 return getCategories(context, cache, categoryDefinedInManifest, null, SETTING_PKG);
Doris Ling485df112016-12-19 10:45:47 -0800206 }
207
208 /**
209 * Build a list of DashboardCategory.
210 * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
211 * represent this category (eg: .Settings$DeviceSettings)
roger xue8f06ab02016-12-08 14:09:50 -0800212 * @param extraAction additional intent filter action to be usetileutild to build the dashboard
Doris Ling485df112016-12-19 10:45:47 -0800213 * categories
214 */
215 public static List<DashboardCategory> getCategories(Context context,
216 Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,
roger xue8f06ab02016-12-08 14:09:50 -0800217 String extraAction, String settingPkg) {
Jason Monke79790b2015-12-02 15:39:19 -0500218 final long startTime = System.currentTimeMillis();
Jason Monk64600cf2016-06-30 13:15:48 -0400219 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
220 != 0;
Jason Monkf509d7e2016-01-07 16:22:53 -0500221 ArrayList<Tile> tiles = new ArrayList<>();
roger xue8f06ab02016-12-08 14:09:50 -0800222 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
Jason Monk744b6362015-11-03 18:24:29 -0500223 for (UserHandle user : userManager.getUserProfiles()) {
224 // TODO: Needs much optimization, too many PM queries going on here.
Jason Monk0d72d202015-11-04 13:16:00 -0500225 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
226 // Only add Settings for this user.
roger xue8f06ab02016-12-08 14:09:50 -0800227 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true,
228 settingPkg);
Jason Monk0d72d202015-11-04 13:16:00 -0500229 getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
roger xue8f06ab02016-12-08 14:09:50 -0800230 OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
Jason Monk0d72d202015-11-04 13:16:00 -0500231 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
roger xue8f06ab02016-12-08 14:09:50 -0800232 MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
Jason Monk0d72d202015-11-04 13:16:00 -0500233 }
Jason Monk64600cf2016-06-30 13:15:48 -0400234 if (setup) {
roger xue8f06ab02016-12-08 14:09:50 -0800235 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false,
236 settingPkg);
Fan Zhang8bfb3ea2017-01-05 14:19:30 -0800237 if (!categoryDefinedInManifest) {
238 getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false,
roger xue8f06ab02016-12-08 14:09:50 -0800239 settingPkg);
Fan Zhang8bfb3ea2017-01-05 14:19:30 -0800240 if (extraAction != null) {
241 getTilesForAction(context, user, extraAction, cache, null, tiles, false,
242 settingPkg);
243 }
Doris Ling485df112016-12-19 10:45:47 -0800244 }
Jason Monk64600cf2016-06-30 13:15:48 -0400245 }
Jason Monk744b6362015-11-03 18:24:29 -0500246 }
Fan Zhang22a56d72016-09-27 17:52:00 -0700247
Jason Monk744b6362015-11-03 18:24:29 -0500248 HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
Jason Monkf509d7e2016-01-07 16:22:53 -0500249 for (Tile tile : tiles) {
Jason Monk744b6362015-11-03 18:24:29 -0500250 DashboardCategory category = categoryMap.get(tile.category);
251 if (category == null) {
Fan Zhang22a56d72016-09-27 17:52:00 -0700252 category = createCategory(context, tile.category, categoryDefinedInManifest);
Jason Monk744b6362015-11-03 18:24:29 -0500253 if (category == null) {
254 Log.w(LOG_TAG, "Couldn't find category " + tile.category);
255 continue;
256 }
257 categoryMap.put(category.key, category);
258 }
259 category.addTile(tile);
260 }
261 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
262 for (DashboardCategory category : categories) {
263 Collections.sort(category.tiles, TILE_COMPARATOR);
264 }
265 Collections.sort(categories, CATEGORY_COMPARATOR);
Jason Monke79790b2015-12-02 15:39:19 -0500266 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
267 + (System.currentTimeMillis() - startTime) + " ms");
Jason Monk744b6362015-11-03 18:24:29 -0500268 return categories;
269 }
270
Fan Zhang22a56d72016-09-27 17:52:00 -0700271 /**
272 * Create a new DashboardCategory from key.
273 *
274 * @param context Context to query intent
275 * @param categoryKey The category key
276 * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
277 * represent this category (eg: .Settings$DeviceSettings)
278 */
279 private static DashboardCategory createCategory(Context context, String categoryKey,
280 boolean categoryDefinedInManifest) {
Jason Monk744b6362015-11-03 18:24:29 -0500281 DashboardCategory category = new DashboardCategory();
282 category.key = categoryKey;
Fan Zhang22a56d72016-09-27 17:52:00 -0700283 if (!categoryDefinedInManifest) {
284 return category;
285 }
Jason Monk744b6362015-11-03 18:24:29 -0500286 PackageManager pm = context.getPackageManager();
287 List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
288 if (results.size() == 0) {
289 return null;
290 }
291 for (ResolveInfo resolved : results) {
292 if (!resolved.system) {
293 // Do not allow any app to add to settings, only system ones.
294 continue;
295 }
296 category.title = resolved.activityInfo.loadLabel(pm);
297 category.priority = SETTING_PKG.equals(
298 resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
299 if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
300 }
301
302 return category;
303 }
304
305 private static void getTilesForAction(Context context,
Jason Monkf509d7e2016-01-07 16:22:53 -0500306 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
roger xue8f06ab02016-12-08 14:09:50 -0800307 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
308 String settingPkg) {
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900309 getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,
roger xue8f06ab02016-12-08 14:09:50 -0800310 requireSettings, requireSettings, settingPkg);
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900311 }
312
313 private static void getTilesForAction(Context context,
314 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
315 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
roger xue8f06ab02016-12-08 14:09:50 -0800316 boolean usePriority, String settingPkg) {
Jason Monk744b6362015-11-03 18:24:29 -0500317 Intent intent = new Intent(action);
Jason Monkf509d7e2016-01-07 16:22:53 -0500318 if (requireSettings) {
roger xue8f06ab02016-12-08 14:09:50 -0800319 intent.setPackage(settingPkg);
Jason Monkf509d7e2016-01-07 16:22:53 -0500320 }
321 getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700322 usePriority, true, true);
Jason Monkf509d7e2016-01-07 16:22:53 -0500323 }
324
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700325 public static void getTilesForIntent(
326 Context context, UserHandle user, Intent intent,
Jason Monkf509d7e2016-01-07 16:22:53 -0500327 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700328 boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500329 PackageManager pm = context.getPackageManager();
Jason Monk744b6362015-11-03 18:24:29 -0500330 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
331 PackageManager.GET_META_DATA, user.getIdentifier());
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700332 Map<String, IContentProvider> providerMap = new HashMap<>();
Jason Monk744b6362015-11-03 18:24:29 -0500333 for (ResolveInfo resolved : results) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500334 if (!resolved.system) {
335 // Do not allow any app to add to settings, only system ones.
336 continue;
Jason Monk744b6362015-11-03 18:24:29 -0500337 }
338 ActivityInfo activityInfo = resolved.activityInfo;
339 Bundle metaData = activityInfo.metaData;
340 String categoryKey = defaultCategory;
Fan Zhangb12e1972016-09-29 14:43:43 -0700341
342 // Load category
343 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
344 && categoryKey == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500345 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
346 + intent + " missing metadata "
Jason Monk744b6362015-11-03 18:24:29 -0500347 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
348 continue;
Fan Zhangb12e1972016-09-29 14:43:43 -0700349 } else {
350 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
Jason Monk744b6362015-11-03 18:24:29 -0500351 }
Fan Zhangb12e1972016-09-29 14:43:43 -0700352
Jason Monk744b6362015-11-03 18:24:29 -0500353 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
354 activityInfo.name);
Jason Monkf509d7e2016-01-07 16:22:53 -0500355 Tile tile = addedCache.get(key);
Jason Monk744b6362015-11-03 18:24:29 -0500356 if (tile == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500357 tile = new Tile();
Jason Monk744b6362015-11-03 18:24:29 -0500358 tile.intent = new Intent().setClassName(
359 activityInfo.packageName, activityInfo.name);
360 tile.category = categoryKey;
Jason Monkf509d7e2016-01-07 16:22:53 -0500361 tile.priority = usePriority ? resolved.priority : 0;
Jason Monke79790b2015-12-02 15:39:19 -0500362 tile.metaData = activityInfo.metaData;
363 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700364 pm, providerMap, forceTintExternalIcon);
Jason Monk744b6362015-11-03 18:24:29 -0500365 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
366
367 addedCache.put(key, tile);
368 }
369 if (!tile.userHandle.contains(user)) {
370 tile.userHandle.add(user);
371 }
372 if (!outTiles.contains(tile)) {
373 outTiles.add(tile);
374 }
375 }
376 }
377
Jason Monkf509d7e2016-01-07 16:22:53 -0500378 private static boolean updateTileData(Context context, Tile tile,
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700379 ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700380 Map<String, IContentProvider> providerMap, boolean forceTintExternalIcon) {
Jason Monke79790b2015-12-02 15:39:19 -0500381 if (applicationInfo.isSystemApp()) {
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700382 boolean forceTintIcon = false;
Jason Monke79790b2015-12-02 15:39:19 -0500383 int icon = 0;
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800384 Pair<String, Integer> iconFromUri = null;
Jason Monke79790b2015-12-02 15:39:19 -0500385 CharSequence title = null;
386 String summary = null;
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800387 String keyHint = null;
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700388 boolean isIconTintable = false;
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700389 RemoteViews remoteViews = null;
Jason Monk744b6362015-11-03 18:24:29 -0500390
Jason Monke79790b2015-12-02 15:39:19 -0500391 // Get the activity's meta-data
392 try {
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700393 Resources res = pm.getResourcesForApplication(applicationInfo.packageName);
Jason Monke79790b2015-12-02 15:39:19 -0500394 Bundle metaData = activityInfo.metaData;
Jason Monk744b6362015-11-03 18:24:29 -0500395
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700396 if (forceTintExternalIcon
397 && !context.getPackageName().equals(applicationInfo.packageName)) {
398 isIconTintable = true;
399 forceTintIcon = true;
400 }
401
Jason Monke79790b2015-12-02 15:39:19 -0500402 if (res != null && metaData != null) {
William Luh204af1c2017-02-23 11:10:05 -0800403 if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
Jason Monke79790b2015-12-02 15:39:19 -0500404 icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
Jason Monk744b6362015-11-03 18:24:29 -0500405 }
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700406 if (metaData.containsKey(META_DATA_PREFERENCE_ICON_TINTABLE)) {
407 if (forceTintIcon) {
408 Log.w(LOG_TAG, "Ignoring icon tintable for " + activityInfo);
409 } else {
410 isIconTintable =
411 metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
412 }
413 }
William Luh4c978a32017-03-31 15:08:16 -0700414 int resId = 0;
415 if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) {
416 resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID);
417 if (resId != 0) {
418 title = res.getString(resId);
419 }
420 }
William Luh4c978a32017-03-31 15:08:16 -0700421 if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500422 if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
423 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
424 } else {
425 title = metaData.getString(META_DATA_PREFERENCE_TITLE);
426 }
Jason Monk744b6362015-11-03 18:24:29 -0500427 }
William Luh204af1c2017-02-23 11:10:05 -0800428 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500429 if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
430 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
431 } else {
432 summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
433 }
Jason Monk744b6362015-11-03 18:24:29 -0500434 }
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800435 if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
436 if (metaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
437 keyHint = res.getString(metaData.getInt(META_DATA_PREFERENCE_KEYHINT));
438 } else {
439 keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
440 }
441 }
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700442 if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
443 int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
444 remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700445 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
446 String uriString = metaData.getString(
447 META_DATA_PREFERENCE_SUMMARY_URI);
448 String overrideSummary = getTextFromUri(context, uriString, providerMap,
449 META_DATA_PREFERENCE_SUMMARY);
450 if (overrideSummary != null) {
451 remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
452 }
453 }
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700454 }
Jason Monk744b6362015-11-03 18:24:29 -0500455 }
Jason Monke79790b2015-12-02 15:39:19 -0500456 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
457 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
Jason Monk744b6362015-11-03 18:24:29 -0500458 }
Jason Monke79790b2015-12-02 15:39:19 -0500459
460 // Set the preference title to the activity's label if no
461 // meta-data is found
462 if (TextUtils.isEmpty(title)) {
463 title = activityInfo.loadLabel(pm).toString();
464 }
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800465
466 // Set the icon
467 if (iconFromUri != null) {
468 tile.icon = Icon.createWithResource(iconFromUri.first, iconFromUri.second);
469 } else {
470 if (icon == 0) {
471 icon = activityInfo.icon;
472 }
473 tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
Jason Monke79790b2015-12-02 15:39:19 -0500474 }
475
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800476 // Set title and summary for the preference
Jason Monke79790b2015-12-02 15:39:19 -0500477 tile.title = title;
478 tile.summary = summary;
479 // Replace the intent with this specific activity
480 tile.intent = new Intent().setClassName(activityInfo.packageName,
481 activityInfo.name);
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800482 // Suggest a key for this tile
483 tile.key = keyHint;
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700484 tile.isIconTintable = isIconTintable;
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700485 tile.remoteViews = remoteViews;
Jason Monke79790b2015-12-02 15:39:19 -0500486
487 return true;
Jason Monk744b6362015-11-03 18:24:29 -0500488 }
489
490 return false;
491 }
492
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800493 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800494 * Gets the icon package name and resource id from content provider.
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800495 * @param Context context
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800496 * @param packageName package name of the target activity
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800497 * @param uriString URI for the content provider
498 * @param providerMap Maps URI authorities to providers
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800499 * @return package name and resource id of the icon specified
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800500 */
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800501 public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
502 String uriString, Map<String, IContentProvider> providerMap) {
Shahriyar Amini676add42016-12-16 11:29:39 -0800503 Bundle bundle = getBundleFromUri(context, uriString, providerMap);
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800504 if (bundle == null) {
505 return null;
506 }
507 String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
508 if (TextUtils.isEmpty(iconPackageName)) {
509 return null;
510 }
511 int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
512 if (resId == 0) {
513 return null;
514 }
515 // Icon can either come from the target package or from the Settings app.
516 if (iconPackageName.equals(packageName)
517 || iconPackageName.equals(context.getPackageName())) {
518 return Pair.create(iconPackageName, bundle.getInt(META_DATA_PREFERENCE_ICON, 0));
519 }
520 return null;
Shahriyar Amini676add42016-12-16 11:29:39 -0800521 }
522
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800523 /**
524 * Gets text associated with the input key from the content provider.
525 * @param Context context
526 * @param uriString URI for the content provider
527 * @param providerMap Maps URI authorities to providers
528 * @param key Key mapping to the text in bundle returned by the content provider
529 * @return Text associated with the key, if returned by the content provider
530 */
531 public static String getTextFromUri(Context context, String uriString,
Shahriyar Amini676add42016-12-16 11:29:39 -0800532 Map<String, IContentProvider> providerMap, String key) {
533 Bundle bundle = getBundleFromUri(context, uriString, providerMap);
534 return (bundle != null) ? bundle.getString(key) : null;
535 }
536
537 private static Bundle getBundleFromUri(Context context, String uriString,
538 Map<String, IContentProvider> providerMap) {
539 if (TextUtils.isEmpty(uriString)) {
540 return null;
541 }
542 Uri uri = Uri.parse(uriString);
543 String method = getMethodFromUri(uri);
544 if (TextUtils.isEmpty(method)) {
545 return null;
546 }
547 IContentProvider provider = getProviderFromUri(context, uri, providerMap);
548 if (provider == null) {
549 return null;
550 }
551 try {
552 return provider.call(context.getPackageName(), method, uriString, null);
553 } catch (RemoteException e) {
554 return null;
555 }
556 }
557
558 private static IContentProvider getProviderFromUri(Context context, Uri uri,
559 Map<String, IContentProvider> providerMap) {
560 if (uri == null) {
561 return null;
562 }
563 String authority = uri.getAuthority();
564 if (TextUtils.isEmpty(authority)) {
565 return null;
566 }
567 if (!providerMap.containsKey(authority)) {
William Luh5a0a0d82017-04-18 11:34:38 -0700568 providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
Shahriyar Amini676add42016-12-16 11:29:39 -0800569 }
570 return providerMap.get(authority);
571 }
572
573 /** Returns the first path segment of the uri if it exists as the method, otherwise null. */
574 static String getMethodFromUri(Uri uri) {
575 if (uri == null) {
576 return null;
577 }
578 List<String> pathSegments = uri.getPathSegments();
579 if ((pathSegments == null) || pathSegments.isEmpty()) {
580 return null;
581 }
582 return pathSegments.get(0);
583 }
584
Hyunyoung Songbe6c4482016-05-04 10:23:06 -0700585 public static final Comparator<Tile> TILE_COMPARATOR =
Jason Monkf509d7e2016-01-07 16:22:53 -0500586 new Comparator<Tile>() {
Jason Monk744b6362015-11-03 18:24:29 -0500587 @Override
Jason Monkf509d7e2016-01-07 16:22:53 -0500588 public int compare(Tile lhs, Tile rhs) {
Jason Monk744b6362015-11-03 18:24:29 -0500589 return rhs.priority - lhs.priority;
590 }
591 };
592
593 private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
594 new Comparator<DashboardCategory>() {
595 @Override
596 public int compare(DashboardCategory lhs, DashboardCategory rhs) {
597 return rhs.priority - lhs.priority;
598 }
599 };
600}