blob: fc0a444915c3e564152aec6b3fb19f0f62de2c70 [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 /**
William Luh4c978a32017-03-31 15:08:16 -0700152 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500153 * to specify the summary text that should be displayed for the preference.
154 */
155 public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
156
Shahriyar Amini676add42016-12-16 11:29:39 -0800157 /**
158 * Name of the meta-data item that should be set in the AndroidManifest.xml
159 * to specify the content provider providing the summary text that should be displayed for the
160 * preference.
161 *
162 * Summary provided by the content provider overrides any static summary.
163 */
164 public static final String META_DATA_PREFERENCE_SUMMARY_URI =
165 "com.android.settings.summary_uri";
166
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700167 /**
168 * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
169 * custom view which should be displayed for the preference. The custom view will be inflated
170 * as a remote view.
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700171 *
Fan Zhangdadfd502017-07-26 11:00:51 -0700172 * This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700173 * of the summary TextView to '@android:id/summary'.
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700174 */
175 public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
176 "com.android.settings.custom_view";
177
roger xue8f06ab02016-12-08 14:09:50 -0800178 public static final String SETTING_PKG = "com.android.settings";
Jason Monk744b6362015-11-03 18:24:29 -0500179
Fan Zhang22a56d72016-09-27 17:52:00 -0700180 /**
181 * Build a list of DashboardCategory. Each category must be defined in manifest.
182 * eg: .Settings$DeviceSettings
183 * @deprecated
184 */
185 @Deprecated
Jason Monk744b6362015-11-03 18:24:29 -0500186 public static List<DashboardCategory> getCategories(Context context,
Fan Zhang22a56d72016-09-27 17:52:00 -0700187 Map<Pair<String, String>, Tile> cache) {
188 return getCategories(context, cache, true /*categoryDefinedInManifest*/);
189 }
190
191 /**
192 * Build a list of DashboardCategory.
193 * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
194 * represent this category (eg: .Settings$DeviceSettings)
195 */
196 public static List<DashboardCategory> getCategories(Context context,
197 Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) {
roger xue8f06ab02016-12-08 14:09:50 -0800198 return getCategories(context, cache, categoryDefinedInManifest, null, SETTING_PKG);
Doris Ling485df112016-12-19 10:45:47 -0800199 }
200
201 /**
202 * Build a list of DashboardCategory.
203 * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
204 * represent this category (eg: .Settings$DeviceSettings)
roger xue8f06ab02016-12-08 14:09:50 -0800205 * @param extraAction additional intent filter action to be usetileutild to build the dashboard
Doris Ling485df112016-12-19 10:45:47 -0800206 * categories
207 */
208 public static List<DashboardCategory> getCategories(Context context,
209 Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,
roger xue8f06ab02016-12-08 14:09:50 -0800210 String extraAction, String settingPkg) {
Jason Monke79790b2015-12-02 15:39:19 -0500211 final long startTime = System.currentTimeMillis();
Jason Monk64600cf2016-06-30 13:15:48 -0400212 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
213 != 0;
Jason Monkf509d7e2016-01-07 16:22:53 -0500214 ArrayList<Tile> tiles = new ArrayList<>();
roger xue8f06ab02016-12-08 14:09:50 -0800215 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
Jason Monk744b6362015-11-03 18:24:29 -0500216 for (UserHandle user : userManager.getUserProfiles()) {
217 // TODO: Needs much optimization, too many PM queries going on here.
Jason Monk0d72d202015-11-04 13:16:00 -0500218 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
219 // Only add Settings for this user.
roger xue8f06ab02016-12-08 14:09:50 -0800220 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true,
221 settingPkg);
Jason Monk0d72d202015-11-04 13:16:00 -0500222 getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
roger xue8f06ab02016-12-08 14:09:50 -0800223 OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
Jason Monk0d72d202015-11-04 13:16:00 -0500224 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
roger xue8f06ab02016-12-08 14:09:50 -0800225 MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
Jason Monk0d72d202015-11-04 13:16:00 -0500226 }
Jason Monk64600cf2016-06-30 13:15:48 -0400227 if (setup) {
roger xue8f06ab02016-12-08 14:09:50 -0800228 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false,
229 settingPkg);
Fan Zhang8bfb3ea2017-01-05 14:19:30 -0800230 if (!categoryDefinedInManifest) {
231 getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false,
roger xue8f06ab02016-12-08 14:09:50 -0800232 settingPkg);
Fan Zhang8bfb3ea2017-01-05 14:19:30 -0800233 if (extraAction != null) {
234 getTilesForAction(context, user, extraAction, cache, null, tiles, false,
235 settingPkg);
236 }
Doris Ling485df112016-12-19 10:45:47 -0800237 }
Jason Monk64600cf2016-06-30 13:15:48 -0400238 }
Jason Monk744b6362015-11-03 18:24:29 -0500239 }
Fan Zhang22a56d72016-09-27 17:52:00 -0700240
Jason Monk744b6362015-11-03 18:24:29 -0500241 HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
Jason Monkf509d7e2016-01-07 16:22:53 -0500242 for (Tile tile : tiles) {
Jason Monk744b6362015-11-03 18:24:29 -0500243 DashboardCategory category = categoryMap.get(tile.category);
244 if (category == null) {
Fan Zhang22a56d72016-09-27 17:52:00 -0700245 category = createCategory(context, tile.category, categoryDefinedInManifest);
Jason Monk744b6362015-11-03 18:24:29 -0500246 if (category == null) {
247 Log.w(LOG_TAG, "Couldn't find category " + tile.category);
248 continue;
249 }
250 categoryMap.put(category.key, category);
251 }
252 category.addTile(tile);
253 }
254 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
255 for (DashboardCategory category : categories) {
256 Collections.sort(category.tiles, TILE_COMPARATOR);
257 }
258 Collections.sort(categories, CATEGORY_COMPARATOR);
Jason Monke79790b2015-12-02 15:39:19 -0500259 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
260 + (System.currentTimeMillis() - startTime) + " ms");
Jason Monk744b6362015-11-03 18:24:29 -0500261 return categories;
262 }
263
Fan Zhang22a56d72016-09-27 17:52:00 -0700264 /**
265 * Create a new DashboardCategory from key.
266 *
267 * @param context Context to query intent
268 * @param categoryKey The category key
269 * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
270 * represent this category (eg: .Settings$DeviceSettings)
271 */
272 private static DashboardCategory createCategory(Context context, String categoryKey,
273 boolean categoryDefinedInManifest) {
Jason Monk744b6362015-11-03 18:24:29 -0500274 DashboardCategory category = new DashboardCategory();
275 category.key = categoryKey;
Fan Zhang22a56d72016-09-27 17:52:00 -0700276 if (!categoryDefinedInManifest) {
277 return category;
278 }
Jason Monk744b6362015-11-03 18:24:29 -0500279 PackageManager pm = context.getPackageManager();
280 List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
281 if (results.size() == 0) {
282 return null;
283 }
284 for (ResolveInfo resolved : results) {
285 if (!resolved.system) {
286 // Do not allow any app to add to settings, only system ones.
287 continue;
288 }
289 category.title = resolved.activityInfo.loadLabel(pm);
290 category.priority = SETTING_PKG.equals(
291 resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
292 if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
293 }
294
295 return category;
296 }
297
298 private static void getTilesForAction(Context context,
Jason Monkf509d7e2016-01-07 16:22:53 -0500299 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
roger xue8f06ab02016-12-08 14:09:50 -0800300 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
301 String settingPkg) {
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900302 getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,
roger xue8f06ab02016-12-08 14:09:50 -0800303 requireSettings, requireSettings, settingPkg);
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900304 }
305
306 private static void getTilesForAction(Context context,
307 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
308 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
roger xue8f06ab02016-12-08 14:09:50 -0800309 boolean usePriority, String settingPkg) {
Jason Monk744b6362015-11-03 18:24:29 -0500310 Intent intent = new Intent(action);
Jason Monkf509d7e2016-01-07 16:22:53 -0500311 if (requireSettings) {
roger xue8f06ab02016-12-08 14:09:50 -0800312 intent.setPackage(settingPkg);
Jason Monkf509d7e2016-01-07 16:22:53 -0500313 }
314 getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700315 usePriority, true, true);
Jason Monkf509d7e2016-01-07 16:22:53 -0500316 }
317
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700318 public static void getTilesForIntent(
319 Context context, UserHandle user, Intent intent,
Jason Monkf509d7e2016-01-07 16:22:53 -0500320 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700321 boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500322 PackageManager pm = context.getPackageManager();
Jason Monk744b6362015-11-03 18:24:29 -0500323 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
324 PackageManager.GET_META_DATA, user.getIdentifier());
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700325 Map<String, IContentProvider> providerMap = new HashMap<>();
Jason Monk744b6362015-11-03 18:24:29 -0500326 for (ResolveInfo resolved : results) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500327 if (!resolved.system) {
328 // Do not allow any app to add to settings, only system ones.
329 continue;
Jason Monk744b6362015-11-03 18:24:29 -0500330 }
331 ActivityInfo activityInfo = resolved.activityInfo;
332 Bundle metaData = activityInfo.metaData;
333 String categoryKey = defaultCategory;
Fan Zhangb12e1972016-09-29 14:43:43 -0700334
335 // Load category
336 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
337 && categoryKey == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500338 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
339 + intent + " missing metadata "
Jason Monk744b6362015-11-03 18:24:29 -0500340 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
341 continue;
Fan Zhangb12e1972016-09-29 14:43:43 -0700342 } else {
343 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
Jason Monk744b6362015-11-03 18:24:29 -0500344 }
Fan Zhangb12e1972016-09-29 14:43:43 -0700345
Jason Monk744b6362015-11-03 18:24:29 -0500346 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
347 activityInfo.name);
Jason Monkf509d7e2016-01-07 16:22:53 -0500348 Tile tile = addedCache.get(key);
Jason Monk744b6362015-11-03 18:24:29 -0500349 if (tile == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500350 tile = new Tile();
Jason Monk744b6362015-11-03 18:24:29 -0500351 tile.intent = new Intent().setClassName(
352 activityInfo.packageName, activityInfo.name);
353 tile.category = categoryKey;
Jason Monkf509d7e2016-01-07 16:22:53 -0500354 tile.priority = usePriority ? resolved.priority : 0;
Jason Monke79790b2015-12-02 15:39:19 -0500355 tile.metaData = activityInfo.metaData;
356 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700357 pm, providerMap, forceTintExternalIcon);
Jason Monk744b6362015-11-03 18:24:29 -0500358 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
359
360 addedCache.put(key, tile);
361 }
362 if (!tile.userHandle.contains(user)) {
363 tile.userHandle.add(user);
364 }
365 if (!outTiles.contains(tile)) {
366 outTiles.add(tile);
367 }
368 }
369 }
370
Jason Monkf509d7e2016-01-07 16:22:53 -0500371 private static boolean updateTileData(Context context, Tile tile,
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700372 ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm,
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700373 Map<String, IContentProvider> providerMap, boolean forceTintExternalIcon) {
Jason Monke79790b2015-12-02 15:39:19 -0500374 if (applicationInfo.isSystemApp()) {
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700375 boolean forceTintIcon = false;
Jason Monke79790b2015-12-02 15:39:19 -0500376 int icon = 0;
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800377 Pair<String, Integer> iconFromUri = null;
Jason Monke79790b2015-12-02 15:39:19 -0500378 CharSequence title = null;
379 String summary = null;
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800380 String keyHint = null;
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700381 boolean isIconTintable = false;
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700382 RemoteViews remoteViews = null;
Jason Monk744b6362015-11-03 18:24:29 -0500383
Jason Monke79790b2015-12-02 15:39:19 -0500384 // Get the activity's meta-data
385 try {
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700386 Resources res = pm.getResourcesForApplication(applicationInfo.packageName);
Jason Monke79790b2015-12-02 15:39:19 -0500387 Bundle metaData = activityInfo.metaData;
Jason Monk744b6362015-11-03 18:24:29 -0500388
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700389 if (forceTintExternalIcon
390 && !context.getPackageName().equals(applicationInfo.packageName)) {
391 isIconTintable = true;
392 forceTintIcon = true;
393 }
394
Jason Monke79790b2015-12-02 15:39:19 -0500395 if (res != null && metaData != null) {
William Luh204af1c2017-02-23 11:10:05 -0800396 if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
Jason Monke79790b2015-12-02 15:39:19 -0500397 icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
Jason Monk744b6362015-11-03 18:24:29 -0500398 }
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700399 if (metaData.containsKey(META_DATA_PREFERENCE_ICON_TINTABLE)) {
400 if (forceTintIcon) {
401 Log.w(LOG_TAG, "Ignoring icon tintable for " + activityInfo);
402 } else {
403 isIconTintable =
404 metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
405 }
406 }
Fan Zhangdadfd502017-07-26 11:00:51 -0700407 if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500408 if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
409 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
410 } else {
411 title = metaData.getString(META_DATA_PREFERENCE_TITLE);
412 }
Jason Monk744b6362015-11-03 18:24:29 -0500413 }
William Luh204af1c2017-02-23 11:10:05 -0800414 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500415 if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
416 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
417 } else {
418 summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
419 }
Jason Monk744b6362015-11-03 18:24:29 -0500420 }
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800421 if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
422 if (metaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
423 keyHint = res.getString(metaData.getInt(META_DATA_PREFERENCE_KEYHINT));
424 } else {
425 keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
426 }
427 }
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700428 if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
429 int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
430 remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
Jaewoong Jung78c5e5d2017-06-15 18:02:44 -0700431 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
432 String uriString = metaData.getString(
433 META_DATA_PREFERENCE_SUMMARY_URI);
434 String overrideSummary = getTextFromUri(context, uriString, providerMap,
435 META_DATA_PREFERENCE_SUMMARY);
436 if (overrideSummary != null) {
437 remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
438 }
439 }
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700440 }
Jason Monk744b6362015-11-03 18:24:29 -0500441 }
Jason Monke79790b2015-12-02 15:39:19 -0500442 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
443 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
Jason Monk744b6362015-11-03 18:24:29 -0500444 }
Jason Monke79790b2015-12-02 15:39:19 -0500445
446 // Set the preference title to the activity's label if no
447 // meta-data is found
448 if (TextUtils.isEmpty(title)) {
449 title = activityInfo.loadLabel(pm).toString();
450 }
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800451
452 // Set the icon
Fan Zhangdadfd502017-07-26 11:00:51 -0700453 if (icon == 0) {
454 // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
455 // ICON_URI should be loaded in app UI when need the icon object.
456 if (!tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800457 icon = activityInfo.icon;
458 }
Fan Zhangdadfd502017-07-26 11:00:51 -0700459 }
460 if (icon != 0) {
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800461 tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
Jason Monke79790b2015-12-02 15:39:19 -0500462 }
463
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800464 // Set title and summary for the preference
Jason Monke79790b2015-12-02 15:39:19 -0500465 tile.title = title;
466 tile.summary = summary;
467 // Replace the intent with this specific activity
468 tile.intent = new Intent().setClassName(activityInfo.packageName,
469 activityInfo.name);
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800470 // Suggest a key for this tile
471 tile.key = keyHint;
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700472 tile.isIconTintable = isIconTintable;
Maurice Lamd8ae77b2017-03-23 21:26:01 -0700473 tile.remoteViews = remoteViews;
Jason Monke79790b2015-12-02 15:39:19 -0500474
475 return true;
Jason Monk744b6362015-11-03 18:24:29 -0500476 }
477
478 return false;
479 }
480
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800481 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800482 * Gets the icon package name and resource id from content provider.
Fan Zhangdadfd502017-07-26 11:00:51 -0700483 * @param context context
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800484 * @param packageName package name of the target activity
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800485 * @param uriString URI for the content provider
486 * @param providerMap Maps URI authorities to providers
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800487 * @return package name and resource id of the icon specified
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800488 */
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800489 public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
490 String uriString, Map<String, IContentProvider> providerMap) {
Shahriyar Amini676add42016-12-16 11:29:39 -0800491 Bundle bundle = getBundleFromUri(context, uriString, providerMap);
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800492 if (bundle == null) {
493 return null;
494 }
495 String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
496 if (TextUtils.isEmpty(iconPackageName)) {
497 return null;
498 }
499 int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
500 if (resId == 0) {
501 return null;
502 }
503 // Icon can either come from the target package or from the Settings app.
504 if (iconPackageName.equals(packageName)
505 || iconPackageName.equals(context.getPackageName())) {
506 return Pair.create(iconPackageName, bundle.getInt(META_DATA_PREFERENCE_ICON, 0));
507 }
508 return null;
Shahriyar Amini676add42016-12-16 11:29:39 -0800509 }
510
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800511 /**
512 * Gets text associated with the input key from the content provider.
Fan Zhangdadfd502017-07-26 11:00:51 -0700513 * @param context context
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800514 * @param uriString URI for the content provider
515 * @param providerMap Maps URI authorities to providers
516 * @param key Key mapping to the text in bundle returned by the content provider
517 * @return Text associated with the key, if returned by the content provider
518 */
519 public static String getTextFromUri(Context context, String uriString,
Shahriyar Amini676add42016-12-16 11:29:39 -0800520 Map<String, IContentProvider> providerMap, String key) {
521 Bundle bundle = getBundleFromUri(context, uriString, providerMap);
522 return (bundle != null) ? bundle.getString(key) : null;
523 }
524
525 private static Bundle getBundleFromUri(Context context, String uriString,
526 Map<String, IContentProvider> providerMap) {
527 if (TextUtils.isEmpty(uriString)) {
528 return null;
529 }
530 Uri uri = Uri.parse(uriString);
531 String method = getMethodFromUri(uri);
532 if (TextUtils.isEmpty(method)) {
533 return null;
534 }
535 IContentProvider provider = getProviderFromUri(context, uri, providerMap);
536 if (provider == null) {
537 return null;
538 }
539 try {
540 return provider.call(context.getPackageName(), method, uriString, null);
541 } catch (RemoteException e) {
542 return null;
543 }
544 }
545
546 private static IContentProvider getProviderFromUri(Context context, Uri uri,
547 Map<String, IContentProvider> providerMap) {
548 if (uri == null) {
549 return null;
550 }
551 String authority = uri.getAuthority();
552 if (TextUtils.isEmpty(authority)) {
553 return null;
554 }
555 if (!providerMap.containsKey(authority)) {
William Luh5a0a0d82017-04-18 11:34:38 -0700556 providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
Shahriyar Amini676add42016-12-16 11:29:39 -0800557 }
558 return providerMap.get(authority);
559 }
560
561 /** Returns the first path segment of the uri if it exists as the method, otherwise null. */
562 static String getMethodFromUri(Uri uri) {
563 if (uri == null) {
564 return null;
565 }
566 List<String> pathSegments = uri.getPathSegments();
567 if ((pathSegments == null) || pathSegments.isEmpty()) {
568 return null;
569 }
570 return pathSegments.get(0);
571 }
572
Hyunyoung Songbe6c4482016-05-04 10:23:06 -0700573 public static final Comparator<Tile> TILE_COMPARATOR =
Jason Monkf509d7e2016-01-07 16:22:53 -0500574 new Comparator<Tile>() {
Jason Monk744b6362015-11-03 18:24:29 -0500575 @Override
Jason Monkf509d7e2016-01-07 16:22:53 -0500576 public int compare(Tile lhs, Tile rhs) {
Jason Monk744b6362015-11-03 18:24:29 -0500577 return rhs.priority - lhs.priority;
578 }
579 };
580
581 private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
582 new Comparator<DashboardCategory>() {
583 @Override
584 public int compare(DashboardCategory lhs, DashboardCategory rhs) {
585 return rhs.priority - lhs.priority;
586 }
587 };
588}