blob: 4f986e1ce2271bf21a45447ad6eb7c9fe545c1e4 [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;
20import android.content.Intent;
21import android.content.pm.ActivityInfo;
Jason Monke79790b2015-12-02 15:39:19 -050022import android.content.pm.ApplicationInfo;
Jason Monk744b6362015-11-03 18:24:29 -050023import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.content.res.Resources;
26import android.graphics.drawable.Icon;
27import android.os.Bundle;
28import android.os.UserHandle;
29import android.os.UserManager;
Jason Monk64600cf2016-06-30 13:15:48 -040030import android.provider.Settings.Global;
Jason Monk744b6362015-11-03 18:24:29 -050031import android.text.TextUtils;
32import android.util.Log;
33import android.util.Pair;
34
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.HashMap;
39import java.util.List;
40import java.util.Map;
41
42public class TileUtils {
43
Jason Monk0d72d202015-11-04 13:16:00 -050044 private static final boolean DEBUG = false;
Joe Onorato93dcff02016-02-01 17:44:14 -080045 private static final boolean DEBUG_TIMING = false;
Jason Monk744b6362015-11-03 18:24:29 -050046
47 private static final String LOG_TAG = "TileUtils";
48
49 /**
50 * Settings will search for system activities of this action and add them as a top level
51 * settings tile using the following parameters.
52 *
53 * <p>A category must be specified in the meta-data for the activity named
54 * {@link #EXTRA_CATEGORY_KEY}
55 *
56 * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
57 * otherwise the label for the activity will be used.
58 *
59 * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
60 * otherwise the icon for the activity will be used.
61 *
62 * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
63 */
64 private static final String EXTRA_SETTINGS_ACTION =
65 "com.android.settings.action.EXTRA_SETTINGS";
66
67 /**
68 * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
69 */
70 private static final String SETTINGS_ACTION =
71 "com.android.settings.action.SETTINGS";
72
73 private static final String OPERATOR_SETTINGS =
74 "com.android.settings.OPERATOR_APPLICATION_SETTING";
75
76 private static final String OPERATOR_DEFAULT_CATEGORY =
77 "com.android.settings.category.wireless";
78
79 private static final String MANUFACTURER_SETTINGS =
80 "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
81
82 private static final String MANUFACTURER_DEFAULT_CATEGORY =
83 "com.android.settings.category.device";
84
85 /**
86 * The key used to get the category from metadata of activities of action
87 * {@link #EXTRA_SETTINGS_ACTION}
88 * The value must be one of:
89 * <li>com.android.settings.category.wireless</li>
90 * <li>com.android.settings.category.device</li>
91 * <li>com.android.settings.category.personal</li>
92 * <li>com.android.settings.category.system</li>
93 */
94 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
95
96 /**
97 * Name of the meta-data item that should be set in the AndroidManifest.xml
98 * to specify the icon that should be displayed for the preference.
99 */
100 public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
101
102 /**
103 * Name of the meta-data item that should be set in the AndroidManifest.xml
104 * to specify the title that should be displayed for the preference.
105 */
106 public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
107
108 /**
109 * Name of the meta-data item that should be set in the AndroidManifest.xml
110 * to specify the summary text that should be displayed for the preference.
111 */
112 public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
113
114 private static final String SETTING_PKG = "com.android.settings";
115
Jason Monk744b6362015-11-03 18:24:29 -0500116 public static List<DashboardCategory> getCategories(Context context,
Jason Monkf509d7e2016-01-07 16:22:53 -0500117 HashMap<Pair<String, String>, Tile> cache) {
Jason Monke79790b2015-12-02 15:39:19 -0500118 final long startTime = System.currentTimeMillis();
Jason Monk64600cf2016-06-30 13:15:48 -0400119 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
120 != 0;
Jason Monkf509d7e2016-01-07 16:22:53 -0500121 ArrayList<Tile> tiles = new ArrayList<>();
Jason Monk744b6362015-11-03 18:24:29 -0500122 UserManager userManager = UserManager.get(context);
123 for (UserHandle user : userManager.getUserProfiles()) {
124 // TODO: Needs much optimization, too many PM queries going on here.
Jason Monk0d72d202015-11-04 13:16:00 -0500125 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
126 // Only add Settings for this user.
127 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
128 getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900129 OPERATOR_DEFAULT_CATEGORY, tiles, false, true);
Jason Monk0d72d202015-11-04 13:16:00 -0500130 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900131 MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true);
Jason Monk0d72d202015-11-04 13:16:00 -0500132 }
Jason Monk64600cf2016-06-30 13:15:48 -0400133 if (setup) {
134 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
135 }
Jason Monk744b6362015-11-03 18:24:29 -0500136 }
137 HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
Jason Monkf509d7e2016-01-07 16:22:53 -0500138 for (Tile tile : tiles) {
Jason Monk744b6362015-11-03 18:24:29 -0500139 DashboardCategory category = categoryMap.get(tile.category);
140 if (category == null) {
141 category = createCategory(context, tile.category);
142 if (category == null) {
143 Log.w(LOG_TAG, "Couldn't find category " + tile.category);
144 continue;
145 }
146 categoryMap.put(category.key, category);
147 }
148 category.addTile(tile);
149 }
150 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
151 for (DashboardCategory category : categories) {
152 Collections.sort(category.tiles, TILE_COMPARATOR);
153 }
154 Collections.sort(categories, CATEGORY_COMPARATOR);
Jason Monke79790b2015-12-02 15:39:19 -0500155 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
156 + (System.currentTimeMillis() - startTime) + " ms");
Jason Monk744b6362015-11-03 18:24:29 -0500157 return categories;
158 }
159
160 private static DashboardCategory createCategory(Context context, String categoryKey) {
161 DashboardCategory category = new DashboardCategory();
162 category.key = categoryKey;
163 PackageManager pm = context.getPackageManager();
164 List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
165 if (results.size() == 0) {
166 return null;
167 }
168 for (ResolveInfo resolved : results) {
169 if (!resolved.system) {
170 // Do not allow any app to add to settings, only system ones.
171 continue;
172 }
173 category.title = resolved.activityInfo.loadLabel(pm);
174 category.priority = SETTING_PKG.equals(
175 resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
176 if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
177 }
178
179 return category;
180 }
181
182 private static void getTilesForAction(Context context,
Jason Monkf509d7e2016-01-07 16:22:53 -0500183 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
184 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings) {
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900185 getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,
186 requireSettings, requireSettings);
187 }
188
189 private static void getTilesForAction(Context context,
190 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
191 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
192 boolean usePriority) {
Jason Monk744b6362015-11-03 18:24:29 -0500193 Intent intent = new Intent(action);
Jason Monkf509d7e2016-01-07 16:22:53 -0500194 if (requireSettings) {
195 intent.setPackage(SETTING_PKG);
196 }
197 getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
Yoshinori Hirano4adbbfc2016-06-06 15:47:47 +0900198 usePriority, true);
Jason Monkf509d7e2016-01-07 16:22:53 -0500199 }
200
201 public static void getTilesForIntent(Context context, UserHandle user, Intent intent,
202 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
203 boolean usePriority, boolean checkCategory) {
204 PackageManager pm = context.getPackageManager();
Jason Monk744b6362015-11-03 18:24:29 -0500205 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
206 PackageManager.GET_META_DATA, user.getIdentifier());
207 for (ResolveInfo resolved : results) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500208 if (!resolved.system) {
209 // Do not allow any app to add to settings, only system ones.
210 continue;
Jason Monk744b6362015-11-03 18:24:29 -0500211 }
212 ActivityInfo activityInfo = resolved.activityInfo;
213 Bundle metaData = activityInfo.metaData;
214 String categoryKey = defaultCategory;
Jason Monkf509d7e2016-01-07 16:22:53 -0500215 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
Jason Monk744b6362015-11-03 18:24:29 -0500216 && categoryKey == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500217 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
218 + intent + " missing metadata "
Jason Monk744b6362015-11-03 18:24:29 -0500219 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
220 continue;
221 } else {
222 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
223 }
224 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
225 activityInfo.name);
Jason Monkf509d7e2016-01-07 16:22:53 -0500226 Tile tile = addedCache.get(key);
Jason Monk744b6362015-11-03 18:24:29 -0500227 if (tile == null) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500228 tile = new Tile();
Jason Monk744b6362015-11-03 18:24:29 -0500229 tile.intent = new Intent().setClassName(
230 activityInfo.packageName, activityInfo.name);
231 tile.category = categoryKey;
Jason Monkf509d7e2016-01-07 16:22:53 -0500232 tile.priority = usePriority ? resolved.priority : 0;
Jason Monke79790b2015-12-02 15:39:19 -0500233 tile.metaData = activityInfo.metaData;
234 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
235 pm);
Jason Monk744b6362015-11-03 18:24:29 -0500236 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
237
238 addedCache.put(key, tile);
239 }
240 if (!tile.userHandle.contains(user)) {
241 tile.userHandle.add(user);
242 }
243 if (!outTiles.contains(tile)) {
244 outTiles.add(tile);
245 }
246 }
247 }
248
249 private static DashboardCategory getCategory(List<DashboardCategory> target,
250 String categoryKey) {
251 for (DashboardCategory category : target) {
252 if (categoryKey.equals(category.key)) {
253 return category;
254 }
255 }
256 return null;
257 }
258
Jason Monkf509d7e2016-01-07 16:22:53 -0500259 private static boolean updateTileData(Context context, Tile tile,
Jason Monke79790b2015-12-02 15:39:19 -0500260 ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
261 if (applicationInfo.isSystemApp()) {
262 int icon = 0;
263 CharSequence title = null;
264 String summary = null;
Jason Monk744b6362015-11-03 18:24:29 -0500265
Jason Monke79790b2015-12-02 15:39:19 -0500266 // Get the activity's meta-data
267 try {
268 Resources res = pm.getResourcesForApplication(
269 applicationInfo.packageName);
270 Bundle metaData = activityInfo.metaData;
Jason Monk744b6362015-11-03 18:24:29 -0500271
Jason Monke79790b2015-12-02 15:39:19 -0500272 if (res != null && metaData != null) {
273 if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
274 icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
Jason Monk744b6362015-11-03 18:24:29 -0500275 }
Jason Monke79790b2015-12-02 15:39:19 -0500276 if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500277 if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
278 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
279 } else {
280 title = metaData.getString(META_DATA_PREFERENCE_TITLE);
281 }
Jason Monk744b6362015-11-03 18:24:29 -0500282 }
Jason Monke79790b2015-12-02 15:39:19 -0500283 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500284 if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
285 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
286 } else {
287 summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
288 }
Jason Monk744b6362015-11-03 18:24:29 -0500289 }
Jason Monk744b6362015-11-03 18:24:29 -0500290 }
Jason Monke79790b2015-12-02 15:39:19 -0500291 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
292 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
Jason Monk744b6362015-11-03 18:24:29 -0500293 }
Jason Monke79790b2015-12-02 15:39:19 -0500294
295 // Set the preference title to the activity's label if no
296 // meta-data is found
297 if (TextUtils.isEmpty(title)) {
298 title = activityInfo.loadLabel(pm).toString();
299 }
300 if (icon == 0) {
301 icon = activityInfo.icon;
302 }
303
304 // Set icon, title and summary for the preference
305 tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
306 tile.title = title;
307 tile.summary = summary;
308 // Replace the intent with this specific activity
309 tile.intent = new Intent().setClassName(activityInfo.packageName,
310 activityInfo.name);
311
312 return true;
Jason Monk744b6362015-11-03 18:24:29 -0500313 }
314
315 return false;
316 }
317
Hyunyoung Songbe6c4482016-05-04 10:23:06 -0700318 public static final Comparator<Tile> TILE_COMPARATOR =
Jason Monkf509d7e2016-01-07 16:22:53 -0500319 new Comparator<Tile>() {
Jason Monk744b6362015-11-03 18:24:29 -0500320 @Override
Jason Monkf509d7e2016-01-07 16:22:53 -0500321 public int compare(Tile lhs, Tile rhs) {
Jason Monk744b6362015-11-03 18:24:29 -0500322 return rhs.priority - lhs.priority;
323 }
324 };
325
326 private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
327 new Comparator<DashboardCategory>() {
328 @Override
329 public int compare(DashboardCategory lhs, DashboardCategory rhs) {
330 return rhs.priority - lhs.priority;
331 }
332 };
333}