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