Allow system apps to add to settings dashboard
Allow system apps to add a tile to the top level of settings that
links to an activity through adding a filter for a specific action.
Determine the info for the tile based off manifest info for the
activity. Also allow the same for managed profiles, but show a dialog
in between to select which profile.
The category in which the item is to be placed must be in meta-data.
The icon and title can be specified through meta-data as well or
if unspecified the activity's label and icon will be used.
Also added an optional <external-tiles> tag to the dashboard
category xml, this allows Settings to put external tiles
in the middle of some categories (Personal does this).
Bug: 19443117
Change-Id: Idc9938d1549d181103a3030a8784b527215a8399
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 0f0c897..eecf7a2 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -16,6 +16,8 @@
package com.android.settings;
+import static com.android.settings.dashboard.DashboardTile.TILE_ID_UNDEFINED;
+
import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
@@ -49,8 +51,10 @@
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.transition.TransitionManager;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.util.TypedValue;
import android.util.Xml;
import android.view.Menu;
@@ -81,9 +85,6 @@
import com.android.settings.deviceinfo.UsbSettings;
import com.android.settings.fuelgauge.BatterySaverSettings;
import com.android.settings.fuelgauge.PowerUsageSummary;
-import com.android.settings.notification.OtherSoundSettings;
-import com.android.settings.search.DynamicIndexableContentMonitor;
-import com.android.settings.search.Index;
import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
import com.android.settings.inputmethod.SpellCheckersSettings;
@@ -96,9 +97,12 @@
import com.android.settings.notification.NotificationAccessSettings;
import com.android.settings.notification.NotificationSettings;
import com.android.settings.notification.NotificationStation;
+import com.android.settings.notification.OtherSoundSettings;
import com.android.settings.notification.ZenModeSettings;
import com.android.settings.print.PrintJobSettingsFragment;
import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.search.DynamicIndexableContentMonitor;
+import com.android.settings.search.Index;
import com.android.settings.sim.SimSettings;
import com.android.settings.tts.TextToSpeechSettings;
import com.android.settings.users.UserSettings;
@@ -110,16 +114,16 @@
import com.android.settings.wifi.SavedAccessPointsWifiSettings;
import com.android.settings.wifi.WifiSettings;
import com.android.settings.wifi.p2p.WifiP2pSettings;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
-import static com.android.settings.dashboard.DashboardTile.TILE_ID_UNDEFINED;
-
public class SettingsActivity extends Activity
implements PreferenceManager.OnPreferenceTreeClickListener,
PreferenceFragment.OnPreferenceStartFragmentCallback,
@@ -201,6 +205,35 @@
private static final String EMPTY_QUERY = "";
+ /**
+ * 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 Utils#META_DATA_PREFERENCE_TITLE}
+ * otherwise the label for the activity will be used.
+ *
+ * <p>The icon may be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_ICON}
+ * otherwise the icon for the activity will be used.
+ *
+ * <p>A summary my be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_SUMMARY}
+ */
+ private static final String EXTRA_SETTINGS_ACTION =
+ "com.android.settings.action.EXTRA_SETTINGS";
+
+ /**
+ * 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";
+
private static boolean sShowNoHomeNotice = false;
private String mFragmentClass;
@@ -1035,6 +1068,17 @@
}
}
sa.recycle();
+ sa = obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference);
+ tv = sa.peekValue(
+ com.android.internal.R.styleable.Preference_key);
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ if (tv.resourceId != 0) {
+ category.key = getString(tv.resourceId);
+ } else {
+ category.key = tv.string.toString();
+ }
+ }
+ sa.recycle();
final int innerDepth = parser.getDepth();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1110,6 +1154,8 @@
category.addTile(tile);
}
+ } else if (innerNodeName.equals("external-tiles")) {
+ category.externalIndex = category.getTilesCount();
} else {
XmlUtils.skipCurrentTag(parser);
}
@@ -1231,6 +1277,73 @@
n--;
}
}
+ addExternalTiles(target);
+ }
+
+ private void addExternalTiles(List<DashboardCategory> target) {
+ Map<Pair<String, String>, DashboardTile> addedCache =
+ new ArrayMap<Pair<String, String>, DashboardTile>();
+ UserManager userManager = UserManager.get(this);
+ for (UserHandle user : userManager.getUserProfiles()) {
+ addExternalTiles(target, user, addedCache);
+ }
+ }
+
+ private void addExternalTiles(List<DashboardCategory> target, UserHandle user,
+ Map<Pair<String, String>, DashboardTile> addedCache) {
+ PackageManager pm = getPackageManager();
+ Intent intent = new Intent(EXTRA_SETTINGS_ACTION);
+ List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
+ PackageManager.GET_META_DATA, user.getIdentifier());
+ for (ResolveInfo resolved : results) {
+ if (!resolved.system) {
+ // Do not allow any app to add to settings, only system ones.
+ continue;
+ }
+ ActivityInfo activityInfo = resolved.activityInfo;
+ Bundle metaData = activityInfo.metaData;
+ if ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY)) {
+ Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action "
+ + EXTRA_SETTINGS_ACTION + " missing metadata " +
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));
+ continue;
+ }
+ String categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
+ DashboardCategory category = getCategory(target, categoryKey);
+ if (category == null) {
+ Log.w(LOG_TAG, "Activity " + resolved.activityInfo.name + " has unknown "
+ + "category key " + categoryKey);
+ continue;
+ }
+ 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);
+ Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile);
+
+ if (category.externalIndex == -1) {
+ // If no location for external tiles has been specified for this category,
+ // then just put them at the end.
+ category.addTile(tile);
+ } else {
+ category.addTile(category.externalIndex, tile);
+ }
+ addedCache.put(key, tile);
+ }
+ tile.userHandle.add(user);
+ }
+ }
+
+ private DashboardCategory getCategory(List<DashboardCategory> target, String categoryKey) {
+ for (DashboardCategory category : target) {
+ if (categoryKey.equals(category.key)) {
+ return category;
+ }
+ }
+ return null;
}
private boolean updateHomeSettingTiles(DashboardTile tile) {