blob: ace50f30663db288b513cdcb2fcf340a4c75e613 [file] [log] [blame]
Jason Monk744b6362015-11-03 18:24:29 -05001/*
hughchened6a4d22019-03-28 11:20:48 +08002 * Copyright (C) 2019 The Android Open Source Project
Jason Monk744b6362015-11-03 18:24:29 -05003 *
Jason Monkf509d7e2016-01-07 16:22:53 -05004 * 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
Jason Monk744b6362015-11-03 18:24:29 -05007 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
Jason Monkf509d7e2016-01-07 16:22:53 -050010 * 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
hughchened6a4d22019-03-28 11:20:48 +080014 * limitations under the License.
Jason Monk744b6362015-11-03 18:24:29 -050015 */
Jason Monk744b6362015-11-03 18:24:29 -050016package com.android.settingslib.drawer;
17
Jason Monk0d72d202015-11-04 13:16:00 -050018import android.app.ActivityManager;
Jason Chiu499a5792019-10-24 17:35:23 +080019import android.content.ContentResolver;
Jason Monk744b6362015-11-03 18:24:29 -050020import android.content.Context;
Shahriyar Amini676add42016-12-16 11:29:39 -080021import android.content.IContentProvider;
Jason Monk744b6362015-11-03 18:24:29 -050022import android.content.Intent;
23import android.content.pm.ActivityInfo;
Jason Chiu8269e922019-10-01 17:23:23 +080024import android.content.pm.ComponentInfo;
Jason Monk744b6362015-11-03 18:24:29 -050025import android.content.pm.PackageManager;
Jason Chiu499a5792019-10-24 17:35:23 +080026import android.content.pm.ProviderInfo;
Jason Monk744b6362015-11-03 18:24:29 -050027import android.content.pm.ResolveInfo;
Shahriyar Amini676add42016-12-16 11:29:39 -080028import android.net.Uri;
Jason Monk744b6362015-11-03 18:24:29 -050029import android.os.Bundle;
Shahriyar Amini676add42016-12-16 11:29:39 -080030import android.os.RemoteException;
Jason Monk744b6362015-11-03 18:24:29 -050031import android.os.UserHandle;
32import android.os.UserManager;
Jason Monk64600cf2016-06-30 13:15:48 -040033import android.provider.Settings.Global;
Jason Monk744b6362015-11-03 18:24:29 -050034import android.text.TextUtils;
Jason Chiu499a5792019-10-24 17:35:23 +080035import android.util.ArrayMap;
Jason Monk744b6362015-11-03 18:24:29 -050036import android.util.Log;
37import android.util.Pair;
38
Fan Zhang0d7b6cf2018-07-23 13:52:34 -070039import androidx.annotation.VisibleForTesting;
40
Jason Monk744b6362015-11-03 18:24:29 -050041import java.util.ArrayList;
Jason Monk744b6362015-11-03 18:24:29 -050042import java.util.HashMap;
43import java.util.List;
44import java.util.Map;
45
hughchened6a4d22019-03-28 11:20:48 +080046/**
47 * Utils is a helper class that contains profile key, meta data, settings action
48 * and static methods for get icon or text from uri.
49 */
Jason Monk744b6362015-11-03 18:24:29 -050050public class TileUtils {
51
Joe Onorato93dcff02016-02-01 17:44:14 -080052 private static final boolean DEBUG_TIMING = false;
Jason Monk744b6362015-11-03 18:24:29 -050053
54 private static final String LOG_TAG = "TileUtils";
Fan Zhang0d7b6cf2018-07-23 13:52:34 -070055 @VisibleForTesting
56 static final String SETTING_PKG = "com.android.settings";
Jason Monk744b6362015-11-03 18:24:29 -050057
58 /**
59 * Settings will search for system activities of this action and add them as a top level
60 * settings tile using the following parameters.
61 *
62 * <p>A category must be specified in the meta-data for the activity named
63 * {@link #EXTRA_CATEGORY_KEY}
64 *
65 * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
66 * otherwise the label for the activity will be used.
67 *
68 * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
69 * otherwise the icon for the activity will be used.
70 *
71 * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
72 */
Fan Zhang4c07a712018-08-06 10:04:14 -070073 public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS";
Jason Monk744b6362015-11-03 18:24:29 -050074
75 /**
Fan Zhangca60fac2016-11-02 15:54:53 -070076 * @See {@link #EXTRA_SETTINGS_ACTION}.
77 */
Fan Zhang63a67c52018-08-21 15:47:11 -070078 public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS";
Fan Zhangca60fac2016-11-02 15:54:53 -070079
Fan Zhangca60fac2016-11-02 15:54:53 -070080 /**
Jason Monk744b6362015-11-03 18:24:29 -050081 * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
82 */
Fan Zhang4c07a712018-08-06 10:04:14 -070083 private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";
Jason Monk744b6362015-11-03 18:24:29 -050084
85 private static final String OPERATOR_SETTINGS =
86 "com.android.settings.OPERATOR_APPLICATION_SETTING";
87
88 private static final String OPERATOR_DEFAULT_CATEGORY =
89 "com.android.settings.category.wireless";
90
91 private static final String MANUFACTURER_SETTINGS =
92 "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
93
94 private static final String MANUFACTURER_DEFAULT_CATEGORY =
95 "com.android.settings.category.device";
96
97 /**
98 * The key used to get the category from metadata of activities of action
99 * {@link #EXTRA_SETTINGS_ACTION}
Fan Zhang6f6e9562018-06-18 11:30:00 -0700100 * The value must be from {@link CategoryKey}.
Jason Monk744b6362015-11-03 18:24:29 -0500101 */
Jason Chiu499a5792019-10-24 17:35:23 +0800102 static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
Jason Monk744b6362015-11-03 18:24:29 -0500103
104 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800105 * The key used to get the package name of the icon resource for the preference.
106 */
Jason Chiu499a5792019-10-24 17:35:23 +0800107 static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800108
109 /**
Jason Monk744b6362015-11-03 18:24:29 -0500110 * Name of the meta-data item that should be set in the AndroidManifest.xml
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800111 * to specify the key that should be used for the preference.
112 */
113 public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
114
115 /**
Fan Zhang4c07a712018-08-06 10:04:14 -0700116 * Order of the item that should be displayed on screen. Bigger value items displays closer on
117 * top.
118 */
119 public static final String META_DATA_KEY_ORDER = "com.android.settings.order";
120
121 /**
Shahriyar Amini6b32ae32016-11-22 14:49:04 -0800122 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500123 * to specify the icon that should be displayed for the preference.
124 */
125 public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
126
127 /**
128 * Name of the meta-data item that should be set in the AndroidManifest.xml
Fan Zhanga8d23aa2018-04-12 14:01:26 -0700129 * to specify the icon background color. The value may or may not be used by Settings app.
130 */
131 public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_HINT =
132 "com.android.settings.bg.hint";
Jason Chiu8269e922019-10-01 17:23:23 +0800133
Fan Zhang381fa2c2018-08-10 12:06:16 -0700134 /**
135 * Name of the meta-data item that should be set in the AndroidManifest.xml
136 * to specify the icon background color as raw ARGB.
137 */
138 public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB =
139 "com.android.settings.bg.argb";
Fan Zhanga8d23aa2018-04-12 14:01:26 -0700140
141 /**
142 * Name of the meta-data item that should be set in the AndroidManifest.xml
Shahriyar Amini676add42016-12-16 11:29:39 -0800143 * to specify the content provider providing the icon that should be displayed for
144 * the preference.
145 *
146 * Icon provided by the content provider overrides any static icon.
147 */
148 public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri";
149
150 /**
151 * Name of the meta-data item that should be set in the AndroidManifest.xml
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700152 * to specify whether the icon is tintable. This should be a boolean value {@code true} or
153 * {@code false}, set using {@code android:value}
Jason Monk744b6362015-11-03 18:24:29 -0500154 */
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700155 public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
156 "com.android.settings.icon_tintable";
Jason Monk744b6362015-11-03 18:24:29 -0500157
158 /**
159 * Name of the meta-data item that should be set in the AndroidManifest.xml
William Luh4c978a32017-03-31 15:08:16 -0700160 * to specify the title that should be displayed for the preference.
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700161 *
162 * <p>Note: It is preferred to provide this value using {@code android:resource} with a string
163 * resource for localization.
William Luh4c978a32017-03-31 15:08:16 -0700164 */
Maurice Lamb10c6ff2017-06-08 20:29:21 -0700165 public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
166
167 /**
William Luh4c978a32017-03-31 15:08:16 -0700168 * Name of the meta-data item that should be set in the AndroidManifest.xml
Fan Zhang343ae7e2019-06-18 11:17:24 -0700169 * to specify the content provider providing the title text that should be displayed for the
170 * preference.
171 *
172 * Title provided by the content provider overrides any static title.
173 */
174 public static final String META_DATA_PREFERENCE_TITLE_URI =
175 "com.android.settings.title_uri";
176
177 /**
178 * Name of the meta-data item that should be set in the AndroidManifest.xml
Jason Monk744b6362015-11-03 18:24:29 -0500179 * to specify the summary text that should be displayed for the preference.
180 */
181 public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
182
Shahriyar Amini676add42016-12-16 11:29:39 -0800183 /**
184 * Name of the meta-data item that should be set in the AndroidManifest.xml
185 * to specify the content provider providing the summary text that should be displayed for the
186 * preference.
187 *
188 * Summary provided by the content provider overrides any static summary.
189 */
190 public static final String META_DATA_PREFERENCE_SUMMARY_URI =
191 "com.android.settings.summary_uri";
192
Fan Zhang22a56d72016-09-27 17:52:00 -0700193 /**
Jason Chiu499a5792019-10-24 17:35:23 +0800194 * Name of the meta-data item that should be set in the AndroidManifest.xml
195 * to specify the content provider providing the switch that should be displayed for the
196 * preference.
197 *
198 * This works with {@link #META_DATA_PREFERENCE_KEYHINT} which should also be set in the
199 * AndroidManifest.xml
200 */
201 public static final String META_DATA_PREFERENCE_SWITCH_URI =
202 "com.android.settings.switch_uri";
203
204 /**
arangelov24eec2f2018-05-30 18:24:23 +0100205 * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
206 * the app will always be run in the primary profile.
207 *
208 * @see #META_DATA_KEY_PROFILE
209 */
210 public static final String PROFILE_PRIMARY = "primary_profile_only";
211
212 /**
213 * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, the user
214 * will be presented with a dialog to choose the profile the app will be run in.
215 *
216 * @see #META_DATA_KEY_PROFILE
217 */
218 public static final String PROFILE_ALL = "all_profiles";
219
220 /**
221 * Name of the meta-data item that should be set in the AndroidManifest.xml
222 * to specify the profile in which the app should be run when the device has a managed profile.
223 * The default value is {@link #PROFILE_ALL} which means the user will be presented with a
224 * dialog to choose the profile. If set to {@link #PROFILE_PRIMARY} the app will always be
225 * run in the primary profile.
226 *
227 * @see #PROFILE_PRIMARY
228 * @see #PROFILE_ALL
229 */
230 public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
231
232 /**
Fan Zhang22a56d72016-09-27 17:52:00 -0700233 * Build a list of DashboardCategory.
Doris Ling485df112016-12-19 10:45:47 -0800234 */
235 public static List<DashboardCategory> getCategories(Context context,
Fan Zhang5050fd312018-08-20 15:59:20 -0700236 Map<Pair<String, String>, Tile> cache) {
Jason Monke79790b2015-12-02 15:39:19 -0500237 final long startTime = System.currentTimeMillis();
Jason Chiu8269e922019-10-01 17:23:23 +0800238 final boolean setup =
239 Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
240 final ArrayList<Tile> tiles = new ArrayList<>();
241 final UserManager userManager = (UserManager) context.getSystemService(
242 Context.USER_SERVICE);
Jason Monk744b6362015-11-03 18:24:29 -0500243 for (UserHandle user : userManager.getUserProfiles()) {
244 // TODO: Needs much optimization, too many PM queries going on here.
Jason Monk0d72d202015-11-04 13:16:00 -0500245 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
246 // Only add Settings for this user.
Jason Chiu58eda532019-10-24 17:35:23 +0800247 loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
248 loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
Fan Zhang63a67c52018-08-21 15:47:11 -0700249 OPERATOR_DEFAULT_CATEGORY, tiles, false);
Jason Chiu58eda532019-10-24 17:35:23 +0800250 loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
Fan Zhang63a67c52018-08-21 15:47:11 -0700251 MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
Jason Monk0d72d202015-11-04 13:16:00 -0500252 }
Jason Monk64600cf2016-06-30 13:15:48 -0400253 if (setup) {
Jason Chiu58eda532019-10-24 17:35:23 +0800254 loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
255 loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
Jason Monk64600cf2016-06-30 13:15:48 -0400256 }
Jason Monk744b6362015-11-03 18:24:29 -0500257 }
Fan Zhang22a56d72016-09-27 17:52:00 -0700258
Jason Chiu8269e922019-10-01 17:23:23 +0800259 final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
Jason Monkf509d7e2016-01-07 16:22:53 -0500260 for (Tile tile : tiles) {
Fan Zhangf5c1d762018-08-03 09:14:32 -0700261 final String categoryKey = tile.getCategory();
262 DashboardCategory category = categoryMap.get(categoryKey);
Jason Monk744b6362015-11-03 18:24:29 -0500263 if (category == null) {
Fan Zhangf5c1d762018-08-03 09:14:32 -0700264 category = new DashboardCategory(categoryKey);
Fan Zhangc75174b2018-07-19 14:07:58 -0700265
Jason Monk744b6362015-11-03 18:24:29 -0500266 if (category == null) {
Fan Zhangf5c1d762018-08-03 09:14:32 -0700267 Log.w(LOG_TAG, "Couldn't find category " + categoryKey);
Jason Monk744b6362015-11-03 18:24:29 -0500268 continue;
269 }
Fan Zhangf5c1d762018-08-03 09:14:32 -0700270 categoryMap.put(categoryKey, category);
Jason Monk744b6362015-11-03 18:24:29 -0500271 }
272 category.addTile(tile);
273 }
Jason Chiu8269e922019-10-01 17:23:23 +0800274 final ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
Jason Monk744b6362015-11-03 18:24:29 -0500275 for (DashboardCategory category : categories) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800276 category.sortTiles();
Jason Monk744b6362015-11-03 18:24:29 -0500277 }
Fan Zhang23baea92018-08-02 13:20:22 -0700278
279 if (DEBUG_TIMING) {
280 Log.d(LOG_TAG, "getCategories took "
281 + (System.currentTimeMillis() - startTime) + " ms");
282 }
Jason Monk744b6362015-11-03 18:24:29 -0500283 return categories;
284 }
285
Fan Zhang63a67c52018-08-21 15:47:11 -0700286 @VisibleForTesting
Jason Chiu58eda532019-10-24 17:35:23 +0800287 static void loadTilesForAction(Context context,
Jason Monkf509d7e2016-01-07 16:22:53 -0500288 UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
Fan Zhang63a67c52018-08-21 15:47:11 -0700289 String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
290 final Intent intent = new Intent(action);
Jason Monkf509d7e2016-01-07 16:22:53 -0500291 if (requireSettings) {
Fan Zhang0d7b6cf2018-07-23 13:52:34 -0700292 intent.setPackage(SETTING_PKG);
Jason Monkf509d7e2016-01-07 16:22:53 -0500293 }
Jason Chiu58eda532019-10-24 17:35:23 +0800294 loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
295 loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
Jason Chiu8269e922019-10-01 17:23:23 +0800296 }
297
Jason Chiu58eda532019-10-24 17:35:23 +0800298 private static void loadActivityTiles(Context context,
Jason Chiu8269e922019-10-01 17:23:23 +0800299 UserHandle user, Map<Pair<String, String>, Tile> addedCache,
300 String defaultCategory, List<Tile> outTiles, Intent intent) {
Fan Zhang63a67c52018-08-21 15:47:11 -0700301 final PackageManager pm = context.getPackageManager();
Jason Chiu8269e922019-10-01 17:23:23 +0800302 final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
Jason Monk744b6362015-11-03 18:24:29 -0500303 PackageManager.GET_META_DATA, user.getIdentifier());
304 for (ResolveInfo resolved : results) {
Jason Monkf509d7e2016-01-07 16:22:53 -0500305 if (!resolved.system) {
306 // Do not allow any app to add to settings, only system ones.
307 continue;
Jason Monk744b6362015-11-03 18:24:29 -0500308 }
Jason Chiu8269e922019-10-01 17:23:23 +0800309 final ActivityInfo activityInfo = resolved.activityInfo;
310 final Bundle metaData = activityInfo.metaData;
Jason Chiu58eda532019-10-24 17:35:23 +0800311 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
Jason Monk744b6362015-11-03 18:24:29 -0500312 }
313 }
314
Jason Chiu58eda532019-10-24 17:35:23 +0800315 private static void loadProviderTiles(Context context,
Jason Chiu499a5792019-10-24 17:35:23 +0800316 UserHandle user, Map<Pair<String, String>, Tile> addedCache,
317 String defaultCategory, List<Tile> outTiles, Intent intent) {
318 final PackageManager pm = context.getPackageManager();
319 final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
320 0 /* flags */, user.getIdentifier());
321 for (ResolveInfo resolved : results) {
322 if (!resolved.system) {
323 // Do not allow any app to add to settings, only system ones.
324 continue;
325 }
326 final ProviderInfo providerInfo = resolved.providerInfo;
327 final List<Bundle> switchData = getSwitchDataFromProvider(context,
328 providerInfo.authority);
329 if (switchData == null || switchData.isEmpty()) {
330 continue;
331 }
332 for (Bundle metaData : switchData) {
Jason Chiu58eda532019-10-24 17:35:23 +0800333 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
Jason Chiu499a5792019-10-24 17:35:23 +0800334 providerInfo);
335 }
336 }
337 }
338
Jason Chiu58eda532019-10-24 17:35:23 +0800339 private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
Jason Chiu8269e922019-10-01 17:23:23 +0800340 String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
341 ComponentInfo componentInfo) {
342 String categoryKey = defaultCategory;
343 // Load category
344 if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
345 && categoryKey == null) {
346 Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
347 + intent + " missing metadata "
348 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
349 return;
350 } else {
351 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
352 }
353
Jason Chiu499a5792019-10-24 17:35:23 +0800354 final boolean isProvider = componentInfo instanceof ProviderInfo;
355 final Pair<String, String> key = isProvider
356 ? new Pair<>(((ProviderInfo) componentInfo).authority,
357 metaData.getString(META_DATA_PREFERENCE_KEYHINT))
358 : new Pair<>(componentInfo.packageName, componentInfo.name);
Jason Chiu8269e922019-10-01 17:23:23 +0800359 Tile tile = addedCache.get(key);
360 if (tile == null) {
Jason Chiu499a5792019-10-24 17:35:23 +0800361 tile = isProvider
Jason Chiu58eda532019-10-24 17:35:23 +0800362 ? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
363 : new ActivityTile((ActivityInfo) componentInfo, categoryKey);
Jason Chiu8269e922019-10-01 17:23:23 +0800364 addedCache.put(key, tile);
365 } else {
366 tile.setMetaData(metaData);
367 }
368
369 if (!tile.userHandle.contains(user)) {
370 tile.userHandle.add(user);
371 }
372 if (!outTiles.contains(tile)) {
373 outTiles.add(tile);
374 }
375 }
376
Jason Chiu499a5792019-10-24 17:35:23 +0800377 /** Returns the switch data of the key specified from the provider */
378 // TODO(b/144732809): rearrange methods by access level modifiers
379 static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
380 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
381 final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
382 return getBundleFromUri(context, uri, providerMap, null /* bundle */);
383 }
384
385 /** Returns all switch data from the provider */
386 private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
387 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
388 final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
389 final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
390 return result != null
391 ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
392 : null;
393 }
394
Jason Chiu8269e922019-10-01 17:23:23 +0800395 /**
396 * Returns the complete uri from the meta data key of the tile.
Jason Chiu499a5792019-10-24 17:35:23 +0800397 *
398 * A complete uri should contain at least one path segment and be one of the following types:
399 * content://authority/method
400 * content://authority/method/key
401 *
402 * If the uri from the tile is not complete, build a uri by the default method and the
403 * preference key.
404 *
Jason Chiu8269e922019-10-01 17:23:23 +0800405 * @param tile Tile which contains meta data
406 * @param metaDataKey Key mapping to the uri in meta data
Jason Chiu499a5792019-10-24 17:35:23 +0800407 * @param defaultMethod Method to be attached to the uri by default if it has no path segment
Jason Chiu8269e922019-10-01 17:23:23 +0800408 * @return Uri associated with the key
409 */
Jason Chiu499a5792019-10-24 17:35:23 +0800410 public static Uri getCompleteUri(Tile tile, String metaDataKey, String defaultMethod) {
Jason Chiu8269e922019-10-01 17:23:23 +0800411 final String uriString = tile.getMetaData().getString(metaDataKey);
412 if (TextUtils.isEmpty(uriString)) {
413 return null;
414 }
415
Jason Chiu499a5792019-10-24 17:35:23 +0800416 final Uri uri = Uri.parse(uriString);
417 final List<String> pathSegments = uri.getPathSegments();
418 if (pathSegments != null && !pathSegments.isEmpty()) {
419 return uri;
420 }
421
422 final String key = tile.getMetaData().getString(META_DATA_PREFERENCE_KEYHINT);
423 if (TextUtils.isEmpty(key)) {
424 Log.w(LOG_TAG, "Please specify the meta-data " + META_DATA_PREFERENCE_KEYHINT
425 + " in AndroidManifest.xml for " + uriString);
426 return buildUri(uri.getAuthority(), defaultMethod);
427 }
428 return buildUri(uri.getAuthority(), defaultMethod, key);
429 }
430
431 static Uri buildUri(String authority, String method, String key) {
432 return new Uri.Builder()
433 .scheme(ContentResolver.SCHEME_CONTENT)
434 .authority(authority)
435 .appendPath(method)
436 .appendPath(key)
437 .build();
438 }
439
440 private static Uri buildUri(String authority, String method) {
441 return new Uri.Builder()
442 .scheme(ContentResolver.SCHEME_CONTENT)
443 .authority(authority)
444 .appendPath(method)
445 .build();
Jason Chiu8269e922019-10-01 17:23:23 +0800446 }
447
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800448 /**
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800449 * Gets the icon package name and resource id from content provider.
Fan Zhang23baea92018-08-02 13:20:22 -0700450 *
451 * @param context context
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800452 * @param packageName package name of the target activity
Jason Chiu8269e922019-10-01 17:23:23 +0800453 * @param uri URI for the content provider
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800454 * @param providerMap Maps URI authorities to providers
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800455 * @return package name and resource id of the icon specified
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800456 */
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800457 public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
Jason Chiu8269e922019-10-01 17:23:23 +0800458 Uri uri, Map<String, IContentProvider> providerMap) {
Jason Chiu499a5792019-10-24 17:35:23 +0800459 final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800460 if (bundle == null) {
461 return null;
462 }
Jason Chiu8269e922019-10-01 17:23:23 +0800463 final String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800464 if (TextUtils.isEmpty(iconPackageName)) {
465 return null;
466 }
467 int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
468 if (resId == 0) {
469 return null;
470 }
471 // Icon can either come from the target package or from the Settings app.
472 if (iconPackageName.equals(packageName)
473 || iconPackageName.equals(context.getPackageName())) {
Jason Chiu8269e922019-10-01 17:23:23 +0800474 return Pair.create(iconPackageName, resId);
Shahriyar Amini778cf1d2017-01-18 18:52:27 -0800475 }
476 return null;
Shahriyar Amini676add42016-12-16 11:29:39 -0800477 }
478
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800479 /**
480 * Gets text associated with the input key from the content provider.
Fan Zhang23baea92018-08-02 13:20:22 -0700481 *
482 * @param context context
Jason Chiu8269e922019-10-01 17:23:23 +0800483 * @param uri URI for the content provider
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800484 * @param providerMap Maps URI authorities to providers
Fan Zhang23baea92018-08-02 13:20:22 -0700485 * @param key Key mapping to the text in bundle returned by the content provider
Shahriyar Amini1fc3aee2016-12-28 08:51:04 -0800486 * @return Text associated with the key, if returned by the content provider
487 */
Jason Chiu8269e922019-10-01 17:23:23 +0800488 public static String getTextFromUri(Context context, Uri uri,
Shahriyar Amini676add42016-12-16 11:29:39 -0800489 Map<String, IContentProvider> providerMap, String key) {
Jason Chiu499a5792019-10-24 17:35:23 +0800490 final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
Shahriyar Amini676add42016-12-16 11:29:39 -0800491 return (bundle != null) ? bundle.getString(key) : null;
492 }
493
Jason Chiu499a5792019-10-24 17:35:23 +0800494 /**
495 * Gets boolean associated with the input key from the content provider.
496 *
497 * @param context context
498 * @param uri URI for the content provider
499 * @param providerMap Maps URI authorities to providers
500 * @param key Key mapping to the text in bundle returned by the content provider
501 * @return Boolean associated with the key, if returned by the content provider
502 */
503 public static boolean getBooleanFromUri(Context context, Uri uri,
504 Map<String, IContentProvider> providerMap, String key) {
505 final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
506 return (bundle != null) ? bundle.getBoolean(key) : false;
507 }
508
509 /**
510 * Puts boolean associated with the input key to the content provider.
511 *
512 * @param context context
513 * @param uri URI for the content provider
514 * @param providerMap Maps URI authorities to providers
515 * @param key Key mapping to the text in bundle returned by the content provider
516 * @param value Boolean associated with the key
517 * @return Bundle associated with the action, if returned by the content provider
518 */
Jason Chiu58eda532019-10-24 17:35:23 +0800519 public static Bundle putBooleanToUriAndGetResult(Context context, Uri uri,
Jason Chiu499a5792019-10-24 17:35:23 +0800520 Map<String, IContentProvider> providerMap, String key, boolean value) {
521 final Bundle bundle = new Bundle();
522 bundle.putBoolean(key, value);
523 return getBundleFromUri(context, uri, providerMap, bundle);
524 }
525
Jason Chiu8269e922019-10-01 17:23:23 +0800526 private static Bundle getBundleFromUri(Context context, Uri uri,
527 Map<String, IContentProvider> providerMap, Bundle bundle) {
Jason Chiu499a5792019-10-24 17:35:23 +0800528 final Pair<String, String> args = getMethodAndKey(uri);
529 if (args == null) {
Shahriyar Amini676add42016-12-16 11:29:39 -0800530 return null;
531 }
Jason Chiu499a5792019-10-24 17:35:23 +0800532 final String method = args.first;
533 final String key = args.second;
Shahriyar Amini676add42016-12-16 11:29:39 -0800534 if (TextUtils.isEmpty(method)) {
535 return null;
536 }
Jason Chiu8269e922019-10-01 17:23:23 +0800537 final IContentProvider provider = getProviderFromUri(context, uri, providerMap);
Shahriyar Amini676add42016-12-16 11:29:39 -0800538 if (provider == null) {
539 return null;
540 }
Jason Chiu499a5792019-10-24 17:35:23 +0800541 if (!TextUtils.isEmpty(key)) {
542 if (bundle == null) {
543 bundle = new Bundle();
544 }
545 bundle.putString(META_DATA_PREFERENCE_KEYHINT, key);
546 }
Shahriyar Amini676add42016-12-16 11:29:39 -0800547 try {
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800548 return provider.call(context.getPackageName(), context.getAttributionTag(),
Philip P. Moltmann128b7032019-09-27 08:44:12 -0700549 uri.getAuthority(), method, uri.toString(), bundle);
Shahriyar Amini676add42016-12-16 11:29:39 -0800550 } catch (RemoteException e) {
551 return null;
552 }
553 }
554
555 private static IContentProvider getProviderFromUri(Context context, Uri uri,
556 Map<String, IContentProvider> providerMap) {
557 if (uri == null) {
558 return null;
559 }
Jason Chiu8269e922019-10-01 17:23:23 +0800560 final String authority = uri.getAuthority();
Shahriyar Amini676add42016-12-16 11:29:39 -0800561 if (TextUtils.isEmpty(authority)) {
562 return null;
563 }
564 if (!providerMap.containsKey(authority)) {
William Luh5a0a0d82017-04-18 11:34:38 -0700565 providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
Shahriyar Amini676add42016-12-16 11:29:39 -0800566 }
567 return providerMap.get(authority);
568 }
569
Jason Chiu499a5792019-10-24 17:35:23 +0800570 /** Returns method and key of the complete uri. */
571 private static Pair<String, String> getMethodAndKey(Uri uri) {
Shahriyar Amini676add42016-12-16 11:29:39 -0800572 if (uri == null) {
573 return null;
574 }
Jason Chiu499a5792019-10-24 17:35:23 +0800575 final List<String> pathSegments = uri.getPathSegments();
576 if (pathSegments == null || pathSegments.isEmpty()) {
Shahriyar Amini676add42016-12-16 11:29:39 -0800577 return null;
578 }
Jason Chiu499a5792019-10-24 17:35:23 +0800579 final String method = pathSegments.get(0);
580 final String key = pathSegments.size() > 1 ? pathSegments.get(1) : null;
581 return Pair.create(method, key);
Shahriyar Amini676add42016-12-16 11:29:39 -0800582 }
Jason Monk744b6362015-11-03 18:24:29 -0500583}