blob: 6b366801e76ad6ee5d50332c009bfb96ffe8c02a [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.android.settingslib.drawer;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TileUtils {
private static final boolean DEBUG = false;
private static final boolean DEBUG_TIMING = true;
private static final String LOG_TAG = "TileUtils";
/**
* Settings will search for system activities of this action and add them as a top level
* settings tile using the following parameters.
*
* <p>A category must be specified in the meta-data for the activity named
* {@link #EXTRA_CATEGORY_KEY}
*
* <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
* otherwise the label for the activity will be used.
*
* <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
* otherwise the icon for the activity will be used.
*
* <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
*/
private static final String EXTRA_SETTINGS_ACTION =
"com.android.settings.action.EXTRA_SETTINGS";
/**
* Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
*/
private static final String SETTINGS_ACTION =
"com.android.settings.action.SETTINGS";
private static final String OPERATOR_SETTINGS =
"com.android.settings.OPERATOR_APPLICATION_SETTING";
private static final String OPERATOR_DEFAULT_CATEGORY =
"com.android.settings.category.wireless";
private static final String MANUFACTURER_SETTINGS =
"com.android.settings.MANUFACTURER_APPLICATION_SETTING";
private static final String MANUFACTURER_DEFAULT_CATEGORY =
"com.android.settings.category.device";
/**
* The key used to get the category from metadata of activities of action
* {@link #EXTRA_SETTINGS_ACTION}
* The value must be one of:
* <li>com.android.settings.category.wireless</li>
* <li>com.android.settings.category.device</li>
* <li>com.android.settings.category.personal</li>
* <li>com.android.settings.category.system</li>
*/
private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the icon that should be displayed for the preference.
*/
public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the title that should be displayed for the preference.
*/
public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the summary text that should be displayed for the preference.
*/
public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
private static final String SETTING_PKG = "com.android.settings";
public static List<DashboardCategory> getCategories(Context context,
HashMap<Pair<String, String>, DashboardTile> cache) {
final long startTime = System.currentTimeMillis();
ArrayList<DashboardTile> tiles = new ArrayList<>();
UserManager userManager = UserManager.get(context);
for (UserHandle user : userManager.getUserProfiles()) {
// TODO: Needs much optimization, too many PM queries going on here.
if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
// Only add Settings for this user.
getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
OPERATOR_DEFAULT_CATEGORY, tiles, false);
getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
}
getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
}
HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
for (DashboardTile tile : tiles) {
DashboardCategory category = categoryMap.get(tile.category);
if (category == null) {
category = createCategory(context, tile.category);
if (category == null) {
Log.w(LOG_TAG, "Couldn't find category " + tile.category);
continue;
}
categoryMap.put(category.key, category);
}
category.addTile(tile);
}
ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
for (DashboardCategory category : categories) {
Collections.sort(category.tiles, TILE_COMPARATOR);
}
Collections.sort(categories, CATEGORY_COMPARATOR);
if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
+ (System.currentTimeMillis() - startTime) + " ms");
return categories;
}
private static DashboardCategory createCategory(Context context, String categoryKey) {
DashboardCategory category = new DashboardCategory();
category.key = categoryKey;
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
if (results.size() == 0) {
return null;
}
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
continue;
}
category.title = resolved.activityInfo.loadLabel(pm);
category.priority = SETTING_PKG.equals(
resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
}
return category;
}
private static void getTilesForAction(Context context,
UserHandle user, String action, Map<Pair<String, String>, DashboardTile> addedCache,
String defaultCategory, ArrayList<DashboardTile> outTiles, boolean requireSettings) {
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(action);
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
for (ResolveInfo resolved : results) {
if (requireSettings) {
if (!SETTING_PKG.equals(resolved.activityInfo.applicationInfo.packageName)) {
continue;
}
} else {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
continue;
}
}
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
if (((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
&& categoryKey == null) {
Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action "
+ action + " missing metadata "
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));
continue;
} else {
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
}
Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
activityInfo.name);
DashboardTile tile = addedCache.get(key);
if (tile == null) {
tile = new DashboardTile();
tile.intent = new Intent().setClassName(
activityInfo.packageName, activityInfo.name);
tile.category = categoryKey;
tile.priority = requireSettings ? resolved.priority : 0;
tile.metaData = activityInfo.metaData;
updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
pm);
if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
addedCache.put(key, tile);
}
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
if (!outTiles.contains(tile)) {
outTiles.add(tile);
}
}
}
private static DashboardCategory getCategory(List<DashboardCategory> target,
String categoryKey) {
for (DashboardCategory category : target) {
if (categoryKey.equals(category.key)) {
return category;
}
}
return null;
}
private static boolean updateTileData(Context context, DashboardTile tile,
ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
if (applicationInfo.isSystemApp()) {
int icon = 0;
CharSequence title = null;
String summary = null;
// Get the activity's meta-data
try {
Resources res = pm.getResourcesForApplication(
applicationInfo.packageName);
Bundle metaData = activityInfo.metaData;
if (res != null && metaData != null) {
if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
}
if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
title = metaData.getString(META_DATA_PREFERENCE_TITLE);
}
if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
}
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
}
// Set the preference title to the activity's label if no
// meta-data is found
if (TextUtils.isEmpty(title)) {
title = activityInfo.loadLabel(pm).toString();
}
if (icon == 0) {
icon = activityInfo.icon;
}
// Set icon, title and summary for the preference
tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
tile.title = title;
tile.summary = summary;
// Replace the intent with this specific activity
tile.intent = new Intent().setClassName(activityInfo.packageName,
activityInfo.name);
return true;
}
return false;
}
private static final Comparator<DashboardTile> TILE_COMPARATOR =
new Comparator<DashboardTile>() {
@Override
public int compare(DashboardTile lhs, DashboardTile rhs) {
return rhs.priority - lhs.priority;
}
};
private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
new Comparator<DashboardCategory>() {
@Override
public int compare(DashboardCategory lhs, DashboardCategory rhs) {
return rhs.priority - lhs.priority;
}
};
}