1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 package com.android.settingslib.drawer; 17 18 import android.app.ActivityManager; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.ActivityInfo; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.content.res.Resources; 26 import android.graphics.drawable.Icon; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.provider.Settings.Global; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.util.Pair; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.Comparator; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 42 public class TileUtils { 43 44 private static final boolean DEBUG = false; 45 private static final boolean DEBUG_TIMING = false; 46 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 116 public static List<DashboardCategory> getCategories(Context context, 117 HashMap<Pair<String, String>, Tile> cache) { 118 final long startTime = System.currentTimeMillis(); 119 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) 120 != 0; 121 ArrayList<Tile> tiles = new ArrayList<>(); 122 UserManager userManager = UserManager.get(context); 123 for (UserHandle user : userManager.getUserProfiles()) { 124 // TODO: Needs much optimization, too many PM queries going on here. 125 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, 129 OPERATOR_DEFAULT_CATEGORY, tiles, false); 130 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache, 131 MANUFACTURER_DEFAULT_CATEGORY, tiles, false); 132 } 133 if (setup) { 134 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false); 135 } 136 } 137 HashMap<String, DashboardCategory> categoryMap = new HashMap<>(); 138 for (Tile tile : tiles) { 139 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); 155 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took " 156 + (System.currentTimeMillis() - startTime) + " ms"); 157 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, 183 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache, 184 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings) { 185 Intent intent = new Intent(action); 186 if (requireSettings) { 187 intent.setPackage(SETTING_PKG); 188 } 189 getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles, 190 requireSettings, true); 191 } 192 193 public static void getTilesForIntent(Context context, UserHandle user, Intent intent, 194 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, 195 boolean usePriority, boolean checkCategory) { 196 PackageManager pm = context.getPackageManager(); 197 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, 198 PackageManager.GET_META_DATA, user.getIdentifier()); 199 for (ResolveInfo resolved : results) { 200 if (!resolved.system) { 201 // Do not allow any app to add to settings, only system ones. 202 continue; 203 } 204 ActivityInfo activityInfo = resolved.activityInfo; 205 Bundle metaData = activityInfo.metaData; 206 String categoryKey = defaultCategory; 207 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY)) 208 && categoryKey == null) { 209 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent " 210 + intent + " missing metadata " 211 + (metaData == null ? "" : EXTRA_CATEGORY_KEY)); 212 continue; 213 } else { 214 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY); 215 } 216 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName, 217 activityInfo.name); 218 Tile tile = addedCache.get(key); 219 if (tile == null) { 220 tile = new Tile(); 221 tile.intent = new Intent().setClassName( 222 activityInfo.packageName, activityInfo.name); 223 tile.category = categoryKey; 224 tile.priority = usePriority ? resolved.priority : 0; 225 tile.metaData = activityInfo.metaData; 226 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo, 227 pm); 228 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title); 229 230 addedCache.put(key, tile); 231 } 232 if (!tile.userHandle.contains(user)) { 233 tile.userHandle.add(user); 234 } 235 if (!outTiles.contains(tile)) { 236 outTiles.add(tile); 237 } 238 } 239 } 240 241 private static DashboardCategory getCategory(List<DashboardCategory> target, 242 String categoryKey) { 243 for (DashboardCategory category : target) { 244 if (categoryKey.equals(category.key)) { 245 return category; 246 } 247 } 248 return null; 249 } 250 251 private static boolean updateTileData(Context context, Tile tile, 252 ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) { 253 if (applicationInfo.isSystemApp()) { 254 int icon = 0; 255 CharSequence title = null; 256 String summary = null; 257 258 // Get the activity's meta-data 259 try { 260 Resources res = pm.getResourcesForApplication( 261 applicationInfo.packageName); 262 Bundle metaData = activityInfo.metaData; 263 264 if (res != null && metaData != null) { 265 if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) { 266 icon = metaData.getInt(META_DATA_PREFERENCE_ICON); 267 } 268 if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) { 269 if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) { 270 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); 271 } else { 272 title = metaData.getString(META_DATA_PREFERENCE_TITLE); 273 } 274 } 275 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) { 276 if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) { 277 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY)); 278 } else { 279 summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY); 280 } 281 } 282 } 283 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { 284 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e); 285 } 286 287 // Set the preference title to the activity's label if no 288 // meta-data is found 289 if (TextUtils.isEmpty(title)) { 290 title = activityInfo.loadLabel(pm).toString(); 291 } 292 if (icon == 0) { 293 icon = activityInfo.icon; 294 } 295 296 // Set icon, title and summary for the preference 297 tile.icon = Icon.createWithResource(activityInfo.packageName, icon); 298 tile.title = title; 299 tile.summary = summary; 300 // Replace the intent with this specific activity 301 tile.intent = new Intent().setClassName(activityInfo.packageName, 302 activityInfo.name); 303 304 return true; 305 } 306 307 return false; 308 } 309 310 public static final Comparator<Tile> TILE_COMPARATOR = 311 new Comparator<Tile>() { 312 @Override 313 public int compare(Tile lhs, Tile rhs) { 314 return rhs.priority - lhs.priority; 315 } 316 }; 317 318 private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR = 319 new Comparator<DashboardCategory>() { 320 @Override 321 public int compare(DashboardCategory lhs, DashboardCategory rhs) { 322 return rhs.priority - lhs.priority; 323 } 324 }; 325 } 326