• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getCategories(Context context, HashMap<Pair<String, String>, Tile> cache)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 
createCategory(Context context, String categoryKey)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 
getTilesForAction(Context context, UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings)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 
getTilesForIntent(Context context, UserHandle user, Intent intent, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, boolean usePriority, boolean checkCategory)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 
getCategory(List<DashboardCategory> target, String categoryKey)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 
updateTileData(Context context, Tile tile, ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm)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