1 /* 2 * Copyright (C) 2016 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.car.apps.common; 17 18 import android.content.ContentResolver; 19 import android.content.Context; 20 import android.content.Intent.ShortcutIconResource; 21 import android.content.pm.PackageManager.NameNotFoundException; 22 import android.content.res.Resources; 23 import android.graphics.drawable.Drawable; 24 import android.net.Uri; 25 import android.text.TextUtils; 26 27 /** 28 * Utilities for working with URIs. 29 */ 30 public final class UriUtils { 31 32 private static final String SCHEME_SHORTCUT_ICON_RESOURCE = "shortcut.icon.resource"; 33 private static final String SCHEME_DELIMITER = "://"; 34 private static final String URI_PATH_DELIMITER = "/"; 35 private static final String URI_PACKAGE_DELIMITER = ":"; 36 private static final String HTTP_PREFIX = "http"; 37 private static final String HTTPS_PREFIX = "https"; 38 private static final String SCHEME_ACCOUNT_IMAGE = "image.account"; 39 private static final String ACCOUNT_IMAGE_CHANGE_NOTIFY_URI = "change_notify_uri"; 40 private static final String DETAIL_DIALOG_URI_DIALOG_TITLE = "detail_dialog_title"; 41 private static final String DETAIL_DIALOG_URI_DIALOG_DESCRIPTION = "detail_dialog_description"; 42 private static final String DETAIL_DIALOG_URI_DIALOG_ACTION_START_INDEX = 43 "detail_dialog_action_start_index"; 44 private static final String DETAIL_DIALOG_URI_DIALOG_ACTION_START_NAME = 45 "detail_dialog_action_start_name"; 46 47 /** 48 * Non instantiable. 49 */ 50 private UriUtils() {} 51 52 /** 53 * Gets resource uri representation for a resource of a package 54 */ 55 public static String getAndroidResourceUri(Context context, int resourceId) { 56 return getAndroidResourceUri(context.getResources(), resourceId); 57 } 58 59 /** 60 * Gets resource uri representation for a resource 61 */ 62 public static String getAndroidResourceUri(Resources resources, int resourceId) { 63 return ContentResolver.SCHEME_ANDROID_RESOURCE 64 + SCHEME_DELIMITER + resources.getResourceName(resourceId) 65 .replace(URI_PACKAGE_DELIMITER, URI_PATH_DELIMITER); 66 } 67 68 /** 69 * Loads drawable from resource 70 */ 71 public static Drawable getDrawable(Context context, ShortcutIconResource r) 72 throws NameNotFoundException { 73 Resources resources = context.getPackageManager().getResourcesForApplication(r.packageName); 74 if (resources == null) { 75 return null; 76 } 77 resources.updateConfiguration(context.getResources().getConfiguration(), 78 context.getResources().getDisplayMetrics()); 79 final int id = resources.getIdentifier(r.resourceName, null, null); 80 return resources.getDrawable(id); 81 } 82 83 /** 84 * Gets a URI with short cut icon scheme. 85 */ 86 public static Uri getShortcutIconResourceUri(ShortcutIconResource iconResource) { 87 return Uri.parse(SCHEME_SHORTCUT_ICON_RESOURCE + SCHEME_DELIMITER + iconResource.packageName 88 + URI_PATH_DELIMITER 89 + iconResource.resourceName.replace(URI_PACKAGE_DELIMITER, URI_PATH_DELIMITER)); 90 } 91 92 /** 93 * Gets a URI with scheme = {@link ContentResolver#SCHEME_ANDROID_RESOURCE} for 94 * a full resource name. This name is a single string of the form "package:type/entry". 95 */ 96 public static Uri getAndroidResourceUri(String resourceName) { 97 Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + SCHEME_DELIMITER 98 + resourceName.replace(URI_PACKAGE_DELIMITER, URI_PATH_DELIMITER)); 99 return uri; 100 } 101 102 /** 103 * Checks if the URI refers to an Android resource. 104 */ 105 public static boolean isAndroidResourceUri(Uri uri) { 106 return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme()); 107 } 108 109 /** 110 * Gets a URI with the account image scheme. 111 * @hide 112 */ 113 public static Uri getAccountImageUri(String accountName) { 114 Uri uri = Uri.parse(SCHEME_ACCOUNT_IMAGE + SCHEME_DELIMITER + accountName); 115 return uri; 116 } 117 118 /** 119 * Gets a URI with the account image scheme, and specifying an URI to be 120 * used in notifyChange() when the image pointed to by the returned URI is 121 * updated. 122 * @hide 123 */ 124 public static Uri getAccountImageUri(String accountName, Uri changeNotifyUri) { 125 Uri uri = Uri.parse(SCHEME_ACCOUNT_IMAGE + SCHEME_DELIMITER + accountName); 126 if (changeNotifyUri != null) { 127 uri = uri.buildUpon().appendQueryParameter(ACCOUNT_IMAGE_CHANGE_NOTIFY_URI, 128 changeNotifyUri.toString()).build(); 129 } 130 return uri; 131 } 132 133 /** 134 * Checks if the URI refers to an account image. 135 * @hide 136 */ 137 public static boolean isAccountImageUri(Uri uri) { 138 return uri == null ? false : SCHEME_ACCOUNT_IMAGE.equals(uri.getScheme()); 139 } 140 141 /** 142 * @hide 143 */ 144 public static String getAccountName(Uri uri) { 145 if (isAccountImageUri(uri)) { 146 String accountName = uri.getAuthority() + uri.getPath(); 147 return accountName; 148 } else { 149 throw new IllegalArgumentException("Invalid account image URI. " + uri); 150 } 151 } 152 153 /** 154 * @hide 155 */ 156 public static Uri getAccountImageChangeNotifyUri(Uri uri) { 157 if (isAccountImageUri(uri)) { 158 String notifyUri = uri.getQueryParameter(ACCOUNT_IMAGE_CHANGE_NOTIFY_URI); 159 if (notifyUri == null) { 160 return null; 161 } else { 162 return Uri.parse(notifyUri); 163 } 164 } else { 165 throw new IllegalArgumentException("Invalid account image URI. " + uri); 166 } 167 } 168 169 /** 170 * Returns {@code true} if the URI refers to a content URI which can be opened via 171 * {@link ContentResolver#openInputStream(Uri)}. 172 */ 173 public static boolean isContentUri(Uri uri) { 174 return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) || 175 ContentResolver.SCHEME_FILE.equals(uri.getScheme()); 176 } 177 178 /** 179 * Checks if the URI refers to an shortcut icon resource. 180 */ 181 public static boolean isShortcutIconResourceUri(Uri uri) { 182 return SCHEME_SHORTCUT_ICON_RESOURCE.equals(uri.getScheme()); 183 } 184 185 /** 186 * Creates a shortcut icon resource object from an Android resource URI. 187 */ 188 public static ShortcutIconResource getIconResource(Uri uri) { 189 if(isAndroidResourceUri(uri)) { 190 ShortcutIconResource iconResource = new ShortcutIconResource(); 191 iconResource.packageName = uri.getAuthority(); 192 // Trim off the scheme + 3 extra for "://", then replace the first "/" with a ":" 193 iconResource.resourceName = uri.toString().substring( 194 ContentResolver.SCHEME_ANDROID_RESOURCE.length() + SCHEME_DELIMITER.length()) 195 .replaceFirst(URI_PATH_DELIMITER, URI_PACKAGE_DELIMITER); 196 return iconResource; 197 } else if(isShortcutIconResourceUri(uri)) { 198 ShortcutIconResource iconResource = new ShortcutIconResource(); 199 iconResource.packageName = uri.getAuthority(); 200 iconResource.resourceName = uri.toString().substring( 201 SCHEME_SHORTCUT_ICON_RESOURCE.length() + SCHEME_DELIMITER.length() 202 + iconResource.packageName.length() + URI_PATH_DELIMITER.length()) 203 .replaceFirst(URI_PATH_DELIMITER, URI_PACKAGE_DELIMITER); 204 return iconResource; 205 } else { 206 throw new IllegalArgumentException("Invalid resource URI. " + uri); 207 } 208 } 209 210 /** 211 * Returns {@code true} if this is a web URI. 212 */ 213 public static boolean isWebUri(Uri resourceUri) { 214 String scheme = resourceUri.getScheme() == null ? null 215 : resourceUri.getScheme().toLowerCase(); 216 return HTTP_PREFIX.equals(scheme) || HTTPS_PREFIX.equals(scheme); 217 } 218 219 /** 220 * Build a Uri for canvas details subactions dialog given content uri and optional parameters. 221 * @param uri the subactions ContentUri 222 * @param dialogTitle the custom subactions dialog title. If the value is null, canvas will 223 * fall back to use previous action's name as the subactions dialog title. 224 * @param dialogDescription the custom subactions dialog description. If the value is null, 225 * canvas will fall back to use previous action's subname as the subactions dialog 226 * description. 227 * @hide 228 */ 229 public static Uri getSubactionDialogUri(Uri uri, String dialogTitle, String dialogDescription) { 230 return getSubactionDialogUri(uri, dialogTitle, dialogDescription, null, -1); 231 } 232 233 /** 234 * Build a Uri for canvas details subactions dialog given content uri and optional parameters. 235 * @param uri the subactions ContentUri 236 * @param dialogTitle the custom subactions dialog title. If the value is null, canvas will 237 * fall back to use previous action's name as the subactions dialog title. 238 * @param dialogDescription the custom subactions dialog description. If the value is null, 239 * canvas will fall back to use previous action's subname as the subactions dialog 240 * description. 241 * @param startIndex the focused action in actions list when started. 242 * @hide 243 */ 244 public static Uri getSubactionDialogUri(Uri uri, String dialogTitle, String dialogDescription, 245 int startIndex) { 246 return getSubactionDialogUri(uri, dialogTitle, dialogDescription, null, startIndex); 247 } 248 249 /** 250 * Build a Uri for canvas details subactions dialog given content uri and optional parameters. 251 * @param uri the subactions ContentUri 252 * @param dialogTitle the custom subactions dialog title. If the value is null, canvas will 253 * fall back to use previous action's name as the subactions dialog title. 254 * @param dialogDescription the custom subactions dialog description. If the value is null, 255 * canvas will fall back to use previous action's subname as the subactions dialog 256 * description. 257 * @param startName the name of action that is focused in actions list when started. 258 * @hide 259 */ 260 public static Uri getSubactionDialogUri(Uri uri, String dialogTitle, String dialogDescription, 261 String startName) { 262 return getSubactionDialogUri(uri, dialogTitle, dialogDescription, startName, -1); 263 } 264 265 /** 266 * Build a Uri for canvas details subactions dialog given content uri and optional parameters. 267 * @param uri the subactions ContentUri 268 * @param dialogTitle the custom subactions dialog title. If the value is null, canvas will 269 * fall back to use previous action's name as the subactions dialog title. 270 * @param dialogDescription the custom subactions dialog description. If the value is null, 271 * canvas will fall back to use previous action's subname as the subactions dialog 272 * description. 273 * @param startIndex the focused action in actions list when started. 274 * @param startName the name of action that is focused in actions list when started. startName 275 * takes priority over start index. 276 * @hide 277 */ 278 public static Uri getSubactionDialogUri(Uri uri, String dialogTitle, String dialogDescription, 279 String startName, int startIndex) { 280 if (uri == null || !isContentUri(uri)) { 281 // If given uri is null, or it is not of contentUri type, return null. 282 return null; 283 } 284 285 Uri.Builder builder = uri.buildUpon(); 286 if (!TextUtils.isEmpty(dialogTitle)) { 287 builder.appendQueryParameter(DETAIL_DIALOG_URI_DIALOG_TITLE, dialogTitle); 288 } 289 290 if (!TextUtils.isEmpty(DETAIL_DIALOG_URI_DIALOG_DESCRIPTION)) { 291 builder.appendQueryParameter(DETAIL_DIALOG_URI_DIALOG_DESCRIPTION, dialogDescription); 292 } 293 294 if (startIndex != -1) { 295 builder.appendQueryParameter(DETAIL_DIALOG_URI_DIALOG_ACTION_START_INDEX, 296 Integer.toString(startIndex)); 297 } 298 299 if (!TextUtils.isEmpty(startName)) { 300 builder.appendQueryParameter(DETAIL_DIALOG_URI_DIALOG_ACTION_START_NAME, startName); 301 } 302 303 return builder.build(); 304 } 305 306 /** 307 * Get subaction dialog title parameter from URI 308 * @param uri ContentUri for canvas details subactions 309 * @return custom dialog title if this parameter is available in URI. Otherwise, return null. 310 * @hide 311 */ 312 public static String getSubactionDialogTitle(Uri uri) { 313 if (uri == null || !isContentUri(uri)) { 314 return null; 315 } 316 317 return uri.getQueryParameter(DETAIL_DIALOG_URI_DIALOG_TITLE); 318 } 319 320 /** 321 * Get subaction dialog description parameter from URI 322 * @param uri ContentUri for canvas details subactions 323 * @return custom dialog description if this parameter is available in URI. 324 * Otherwise, return null. 325 * @hide 326 */ 327 public static String getSubactionDialogDescription(Uri uri) { 328 if (uri == null || !isContentUri(uri)) { 329 return null; 330 } 331 332 return uri.getQueryParameter(DETAIL_DIALOG_URI_DIALOG_DESCRIPTION); 333 } 334 335 /** 336 * Get subaction dialog action list focused index when started from URI 337 * @param uri ContentUri for canvas details subactions 338 * @return action starting index if this parameter is available in URI. Otherwise, return -1. 339 * @hide 340 */ 341 public static int getSubactionDialogActionStartIndex(Uri uri) { 342 if (uri == null || !isContentUri(uri)) { 343 return -1; 344 } 345 346 String startIndexStr = uri.getQueryParameter(DETAIL_DIALOG_URI_DIALOG_ACTION_START_INDEX); 347 if (!TextUtils.isEmpty(startIndexStr) && TextUtils.isDigitsOnly(startIndexStr)) { 348 return Integer.parseInt(startIndexStr); 349 } else { 350 return -1; 351 } 352 } 353 354 /** 355 * Get subaction dialog action list focused action name when started from URI 356 * @param uri ContentUri for canvas details subactions 357 * @return that name of starting action if this parameter is available in URI. 358 * Otherwise, return null. 359 * @hide 360 */ 361 public static String getSubactionDialogActionStartName(Uri uri) { 362 if (uri == null || !isContentUri(uri)) { 363 return null; 364 } 365 366 return uri.getQueryParameter(DETAIL_DIALOG_URI_DIALOG_ACTION_START_NAME); 367 } 368 } 369