Home | History | Annotate | Download | only in drawer
      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