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
17package com.android.tv.settings.util;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent.ShortcutIconResource;
22import android.content.pm.PackageManager.NameNotFoundException;
23import android.content.res.Resources;
24import android.graphics.drawable.Drawable;
25import android.net.Uri;
26import android.text.TextUtils;
27
28/**
29 * Utilities for working with URIs.
30 */
31public 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     */
51    private UriUtils() {}
52
53    /**
54     * get resource uri representation for a resource of a package
55     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
133    public static boolean isAccountImageUri(Uri uri) {
134        return uri == null ? false : SCHEME_ACCOUNT_IMAGE.equals(uri.getScheme());
135    }
136
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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