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 */ 16package com.android.settingslib.drawer; 17 18import android.app.ActivityManager; 19import android.content.Context; 20import android.content.Intent; 21import android.content.pm.ActivityInfo; 22import android.content.pm.ApplicationInfo; 23import 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; 30import android.text.TextUtils; 31import android.util.Log; 32import android.util.Pair; 33 34import java.util.ArrayList; 35import java.util.Collections; 36import java.util.Comparator; 37import java.util.HashMap; 38import java.util.List; 39import java.util.Map; 40 41public class TileUtils { 42 43 private static final boolean DEBUG = false; 44 private static final boolean DEBUG_TIMING = false; 45 46 private static final String LOG_TAG = "TileUtils"; 47 48 /** 49 * Settings will search for system activities of this action and add them as a top level 50 * settings tile using the following parameters. 51 * 52 * <p>A category must be specified in the meta-data for the activity named 53 * {@link #EXTRA_CATEGORY_KEY} 54 * 55 * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE} 56 * otherwise the label for the activity will be used. 57 * 58 * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON} 59 * otherwise the icon for the activity will be used. 60 * 61 * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY} 62 */ 63 private static final String EXTRA_SETTINGS_ACTION = 64 "com.android.settings.action.EXTRA_SETTINGS"; 65 66 /** 67 * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities. 68 */ 69 private static final String SETTINGS_ACTION = 70 "com.android.settings.action.SETTINGS"; 71 72 private static final String OPERATOR_SETTINGS = 73 "com.android.settings.OPERATOR_APPLICATION_SETTING"; 74 75 private static final String OPERATOR_DEFAULT_CATEGORY = 76 "com.android.settings.category.wireless"; 77 78 private static final String MANUFACTURER_SETTINGS = 79 "com.android.settings.MANUFACTURER_APPLICATION_SETTING"; 80 81 private static final String MANUFACTURER_DEFAULT_CATEGORY = 82 "com.android.settings.category.device"; 83 84 /** 85 * The key used to get the category from metadata of activities of action 86 * {@link #EXTRA_SETTINGS_ACTION} 87 * The value must be one of: 88 * <li>com.android.settings.category.wireless</li> 89 * <li>com.android.settings.category.device</li> 90 * <li>com.android.settings.category.personal</li> 91 * <li>com.android.settings.category.system</li> 92 */ 93 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category"; 94 95 /** 96 * Name of the meta-data item that should be set in the AndroidManifest.xml 97 * to specify the icon that should be displayed for the preference. 98 */ 99 public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon"; 100 101 /** 102 * Name of the meta-data item that should be set in the AndroidManifest.xml 103 * to specify the title that should be displayed for the preference. 104 */ 105 public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title"; 106 107 /** 108 * Name of the meta-data item that should be set in the AndroidManifest.xml 109 * to specify the summary text that should be displayed for the preference. 110 */ 111 public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary"; 112 113 private static final String SETTING_PKG = "com.android.settings"; 114 115 public static List<DashboardCategory> getCategories(Context context, 116 HashMap<Pair<String, String>, Tile> cache) { 117 final long startTime = System.currentTimeMillis(); 118 ArrayList<Tile> tiles = new ArrayList<>(); 119 UserManager userManager = UserManager.get(context); 120 for (UserHandle user : userManager.getUserProfiles()) { 121 // TODO: Needs much optimization, too many PM queries going on here. 122 if (user.getIdentifier() == ActivityManager.getCurrentUser()) { 123 // Only add Settings for this user. 124 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true); 125 getTilesForAction(context, user, OPERATOR_SETTINGS, cache, 126 OPERATOR_DEFAULT_CATEGORY, tiles, false); 127 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache, 128 MANUFACTURER_DEFAULT_CATEGORY, tiles, false); 129 } 130 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false); 131 } 132 HashMap<String, DashboardCategory> categoryMap = new HashMap<>(); 133 for (Tile tile : tiles) { 134 DashboardCategory category = categoryMap.get(tile.category); 135 if (category == null) { 136 category = createCategory(context, tile.category); 137 if (category == null) { 138 Log.w(LOG_TAG, "Couldn't find category " + tile.category); 139 continue; 140 } 141 categoryMap.put(category.key, category); 142 } 143 category.addTile(tile); 144 } 145 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values()); 146 for (DashboardCategory category : categories) { 147 Collections.sort(category.tiles, TILE_COMPARATOR); 148 } 149 Collections.sort(categories, CATEGORY_COMPARATOR); 150 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took " 151 + (System.currentTimeMillis() - startTime) + " ms"); 152 return categories; 153 } 154 155 private static DashboardCategory createCategory(Context context, String categoryKey) { 156 DashboardCategory category = new DashboardCategory(); 157 category.key = categoryKey; 158 PackageManager pm = context.getPackageManager(); 159 List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0); 160 if (results.size() == 0) { 161 return null; 162 } 163 for (ResolveInfo resolved : results) { 164 if (!resolved.system) { 165 // Do not allow any app to add to settings, only system ones. 166 continue; 167 } 168 category.title = resolved.activityInfo.loadLabel(pm); 169 category.priority = SETTING_PKG.equals( 170 resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0; 171 if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title); 172 } 173 174 return category; 175 } 176 177 private static void getTilesForAction(Context context, 178 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache, 179 String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings) { 180 Intent intent = new Intent(action); 181 if (requireSettings) { 182 intent.setPackage(SETTING_PKG); 183 } 184 getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles, 185 requireSettings, true); 186 } 187 188 public static void getTilesForIntent(Context context, UserHandle user, Intent intent, 189 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, 190 boolean usePriority, boolean checkCategory) { 191 PackageManager pm = context.getPackageManager(); 192 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, 193 PackageManager.GET_META_DATA, user.getIdentifier()); 194 for (ResolveInfo resolved : results) { 195 if (!resolved.system) { 196 // Do not allow any app to add to settings, only system ones. 197 continue; 198 } 199 ActivityInfo activityInfo = resolved.activityInfo; 200 Bundle metaData = activityInfo.metaData; 201 String categoryKey = defaultCategory; 202 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY)) 203 && categoryKey == null) { 204 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent " 205 + intent + " missing metadata " 206 + (metaData == null ? "" : EXTRA_CATEGORY_KEY)); 207 continue; 208 } else { 209 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY); 210 } 211 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName, 212 activityInfo.name); 213 Tile tile = addedCache.get(key); 214 if (tile == null) { 215 tile = new Tile(); 216 tile.intent = new Intent().setClassName( 217 activityInfo.packageName, activityInfo.name); 218 tile.category = categoryKey; 219 tile.priority = usePriority ? resolved.priority : 0; 220 tile.metaData = activityInfo.metaData; 221 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo, 222 pm); 223 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title); 224 225 addedCache.put(key, tile); 226 } 227 if (!tile.userHandle.contains(user)) { 228 tile.userHandle.add(user); 229 } 230 if (!outTiles.contains(tile)) { 231 outTiles.add(tile); 232 } 233 } 234 } 235 236 private static DashboardCategory getCategory(List<DashboardCategory> target, 237 String categoryKey) { 238 for (DashboardCategory category : target) { 239 if (categoryKey.equals(category.key)) { 240 return category; 241 } 242 } 243 return null; 244 } 245 246 private static boolean updateTileData(Context context, Tile tile, 247 ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) { 248 if (applicationInfo.isSystemApp()) { 249 int icon = 0; 250 CharSequence title = null; 251 String summary = null; 252 253 // Get the activity's meta-data 254 try { 255 Resources res = pm.getResourcesForApplication( 256 applicationInfo.packageName); 257 Bundle metaData = activityInfo.metaData; 258 259 if (res != null && metaData != null) { 260 if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) { 261 icon = metaData.getInt(META_DATA_PREFERENCE_ICON); 262 } 263 if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) { 264 if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) { 265 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); 266 } else { 267 title = metaData.getString(META_DATA_PREFERENCE_TITLE); 268 } 269 } 270 if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) { 271 if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) { 272 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY)); 273 } else { 274 summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY); 275 } 276 } 277 } 278 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { 279 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e); 280 } 281 282 // Set the preference title to the activity's label if no 283 // meta-data is found 284 if (TextUtils.isEmpty(title)) { 285 title = activityInfo.loadLabel(pm).toString(); 286 } 287 if (icon == 0) { 288 icon = activityInfo.icon; 289 } 290 291 // Set icon, title and summary for the preference 292 tile.icon = Icon.createWithResource(activityInfo.packageName, icon); 293 tile.title = title; 294 tile.summary = summary; 295 // Replace the intent with this specific activity 296 tile.intent = new Intent().setClassName(activityInfo.packageName, 297 activityInfo.name); 298 299 return true; 300 } 301 302 return false; 303 } 304 305 private static final Comparator<Tile> TILE_COMPARATOR = 306 new Comparator<Tile>() { 307 @Override 308 public int compare(Tile lhs, Tile rhs) { 309 return rhs.priority - lhs.priority; 310 } 311 }; 312 313 private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR = 314 new Comparator<DashboardCategory>() { 315 @Override 316 public int compare(DashboardCategory lhs, DashboardCategory rhs) { 317 return rhs.priority - lhs.priority; 318 } 319 }; 320} 321