Code example for Uri

Methods: getPathSegments, getQueryParameter, toString

0
        return true; 
    } 
 
    private static int matchUri(final Uri uri) {
        int protocolVersion = 1;
        final String protocolVersionArg = uri.getQueryParameter(QUERY_PARAMETER_PROTOCOL_VERSION);
        if ("2".equals(protocolVersionArg)) protocolVersion = 2;
        switch (protocolVersion) {
            case 1: return sUriMatcherV1.match(uri);
            case 2: return sUriMatcherV2.match(uri);
            default: return NO_MATCH;
        } 
    } 
 
    private static String getClientId(final Uri uri) {
        int protocolVersion = 1;
        final String protocolVersionArg = uri.getQueryParameter(QUERY_PARAMETER_PROTOCOL_VERSION);
        if ("2".equals(protocolVersionArg)) protocolVersion = 2;
        switch (protocolVersion) {
            case 1: return null; // In protocol 1, the client ID is always null. 
            case 2: return uri.getPathSegments().get(0);
            default: return null; 
        } 
    } 
 
    /** 
     * Returns the MIME type of the content associated with an Uri 
     * 
     * @see android.content.ContentProvider#getType(android.net.Uri) 
     * 
     * @param uri the URI of the content the type of which should be returned. 
     * @return the MIME type, or null if the URL is not recognized. 
     */ 
    @Override 
    public String getType(final Uri uri) {
        PrivateLog.log("Asked for type of : " + uri);
        final int match = matchUri(uri);
        switch (match) {
            case NO_MATCH: return null;
            case DICTIONARY_V1_WHOLE_LIST:
            case DICTIONARY_V1_DICT_INFO:
            case DICTIONARY_V2_WHOLE_LIST:
            case DICTIONARY_V2_DICT_INFO: return DICT_LIST_MIME_TYPE;
            case DICTIONARY_V2_DATAFILE: return DICT_DATAFILE_MIME_TYPE;
            default: return null; 
        } 
    } 
 
    /** 
     * Query the provider for dictionary files. 
     * 
     * This version dispatches the query according to the protocol version found in the 
     * ?protocol= query parameter. If absent or not well-formed, it defaults to 1. 
     * @see android.content.ContentProvider#query(Uri, String[], String, String[], String) 
     * 
     * @param uri a content uri (see sUriMatcherV{1,2} at the top of this file for format) 
     * @param projection ignored. All columns are always returned. 
     * @param selection ignored. 
     * @param selectionArgs ignored. 
     * @param sortOrder ignored. The results are always returned in no particular order. 
     * @return a cursor matching the uri, or null if the URI was not recognized. 
     */ 
    @Override 
    public Cursor query(final Uri uri, final String[] projection, final String selection,
            final String[] selectionArgs, final String sortOrder) {
        Utils.l("Uri =", uri);
        PrivateLog.log("Query : " + uri);
        final String clientId = getClientId(uri);
        final int match = matchUri(uri);
        switch (match) {
            case DICTIONARY_V1_WHOLE_LIST:
            case DICTIONARY_V2_WHOLE_LIST:
                final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId);
                Utils.l("List of dictionaries with count", c.getCount());
                PrivateLog.log("Returned a list of " + c.getCount() + " items");
                return c;
            case DICTIONARY_V2_DICT_INFO:
                // In protocol version 2, we return null if the client is unknown. Otherwise 
                // we behave exactly like for protocol 1. 
                if (!MetadataDbHelper.isClientKnown(getContext(), clientId)) return null;
                // Fall through 
            case DICTIONARY_V1_DICT_INFO:
                final String locale = uri.getLastPathSegment();
                // If LatinIME does not have a dictionary for this locale at all, it will 
                // send us true for this value. In this case, we may prompt the user for 
                // a decision about downloading a dictionary even over a metered connection. 
                final String mayPromptValue =
                        uri.getQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER);
                final boolean mayPrompt = QUERY_PARAMETER_TRUE.equals(mayPromptValue);
                final Collection<WordListInfo> dictFiles =
                        getDictionaryWordListsForLocale(clientId, locale, mayPrompt);
                // TODO: pass clientId to the following function 
                DictionaryService.updateNowIfNotUpdatedInAVeryLongTime(getContext());
                if (null != dictFiles && dictFiles.size() > 0) {
                    PrivateLog.log("Returned " + dictFiles.size() + " files");
                    return new ResourcePathCursor(dictFiles);
                } else { 
                    PrivateLog.log("No dictionary files for this URL"); 
                    return new ResourcePathCursor(Collections.<WordListInfo>emptyList());
                } 
            // V2_METADATA and V2_DATAFILE are not supported for query() 
            default: 
                return null; 
        } 
    } 
 
    /** 
     * Helper method to get the wordlist metadata associated with a wordlist ID. 
     * 
     * @param clientId the ID of the client 
     * @param wordlistId the ID of the wordlist for which to get the metadata. 
     * @return the metadata for this wordlist ID, or null if none could be found. 
     */ 
    private ContentValues getWordlistMetadataForWordlistId(final String clientId,
            final String wordlistId) {
        final Context context = getContext();
        if (TextUtils.isEmpty(wordlistId)) return null;
        final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
        return MetadataDbHelper.getInstalledOrDeletingWordListContentValuesByWordListId( 
                db, wordlistId);
    } 
 
    /** 
     * Opens an asset file for an URI. 
     * 
     * Called by {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} or 
     * {@link android.content.ContentResolver#openInputStream(Uri)} from a client requesting a 
     * dictionary. 
     * @see android.content.ContentProvider#openAssetFile(Uri, String) 
     * 
     * @param uri the URI the file is for. 
     * @param mode the mode to read the file. MUST be "r" for readonly. 
     * @return the descriptor, or null if the file is not found or if mode is not equals to "r". 
     */ 
    @Override 
    public AssetFileDescriptor openAssetFile(final Uri uri, final String mode) {
        if (null == mode || !"r".equals(mode)) return null;
 
        final int match = matchUri(uri);
        if (DICTIONARY_V1_DICT_INFO != match && DICTIONARY_V2_DATAFILE != match) {
            // Unsupported URI for openAssetFile 
            Log.w(TAG, "Unsupported URI for openAssetFile : " + uri);
            return null; 
        } 
        final String wordlistId = uri.getLastPathSegment();
        final String clientId = getClientId(uri);
        final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId);
 
        if (null == wordList) return null;
 
        try { 
            final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
            if (MetadataDbHelper.STATUS_DELETING == status) {
                // This will return an empty file (R.raw.empty points at an empty dictionary) 
                // This is how we "delete" the files. It allows Android Keyboard to fake deleting 
                // a default dictionary - which is actually in its assets and can't be really 
                // deleted. 
                final AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(
                        R.raw.empty); 
                return afd;
            } else { 
                final String localFilename =
                        wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
                final File f = getContext().getFileStreamPath(localFilename);
                final ParcelFileDescriptor pfd =
                        ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
                return new AssetFileDescriptor(pfd, 0, pfd.getStatSize());
            } 
        } catch (FileNotFoundException e) {
            // No file : fall through and return null 
        } 
        return null; 
    } 
 
    /** 
     * Reads the metadata and returns the collection of dictionaries for a given locale. 
     * 
     * Word list IDs are expected to be in the form category:manual_id. This method 
     * will select only one word list for each category: the one with the most specific 
     * locale matching the locale specified in the URI. The manual id serves only to 
     * distinguish a word list from another for the purpose of updating, and is arbitrary 
     * but may not contain a colon. 
     * 
     * @param clientId the ID of the client requesting the list 
     * @param locale the locale for which we want the list, as a String 
     * @param mayPrompt true if we are allowed to prompt the user for arbitration via notification 
     * @return a collection of ids. It is guaranteed to be non-null, but may be empty. 
     */ 
    private Collection<WordListInfo> getDictionaryWordListsForLocale(final String clientId,
            final String locale, final boolean mayPrompt) {
        final Context context = getContext();
        final Cursor results =
                MetadataDbHelper.queryInstalledOrDeletingOrAvailableDictionaryMetadata(context,
                        clientId);
        if (null == results) {
            return Collections.<WordListInfo>emptyList();
        } else { 
            final HashMap<String, WordListInfo> dicts = new HashMap<String, WordListInfo>();
            final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
            final int localeIndex = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
            final int localFileNameIndex =
                    results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
            final int statusIndex = results.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
            if (results.moveToFirst()) {
                do { 
                    final String wordListId = results.getString(idIndex);
                    if (TextUtils.isEmpty(wordListId)) continue;
                    final String[] wordListIdArray =
                            TextUtils.split(wordListId, ID_CATEGORY_SEPARATOR);
                    final String wordListCategory;
                    if (2 == wordListIdArray.length) {
                        // This is at the category:manual_id format. 
                        wordListCategory = wordListIdArray[0];
                        // We don't need to read wordListIdArray[1] here, because it's irrelevant to 
                        // word list selection - it's just a name we use to identify which data file 
                        // is a newer version of which word list. We do however return the full id 
                        // string for each selected word list, so in this sense we are 'using' it. 
                    } else { 
                        // This does not contain a colon, like the old format does. Old-format IDs 
                        // always point to main dictionaries, so we force the main category upon it. 
                        wordListCategory = UpdateHandler.MAIN_DICTIONARY_CATEGORY;
                    } 
                    final String wordListLocale = results.getString(localeIndex);
                    final String wordListLocalFilename = results.getString(localFileNameIndex);
                    final int wordListStatus = results.getInt(statusIndex);
                    // Test the requested locale against this wordlist locale. The requested locale 
                    // has to either match exactly or be more specific than the dictionary - a 
                    // dictionary for "en" would match both a request for "en" or for "en_US", but a 
                    // dictionary for "en_GB" would not match a request for "en_US". Thus if all 
                    // three of "en" "en_US" and "en_GB" dictionaries are installed, a request for 
                    // "en_US" would match "en" and "en_US", and a request for "en" only would only 
                    // match the generic "en" dictionary. For more details, see the documentation 
                    // for LocaleUtils#getMatchLevel. 
                    final int matchLevel = LocaleUtils.getMatchLevel(wordListLocale, locale);
                    if (!LocaleUtils.isMatch(matchLevel)) {
                        // The locale of this wordlist does not match the required locale. 
                        // Skip this wordlist and go to the next. 
                        continue; 
                    } 
                    if (MetadataDbHelper.STATUS_INSTALLED == wordListStatus) {
                        // If the file does not exist, it has been deleted and the IME should 
                        // already have it. Do not return it. However, this only applies if the 
                        // word list is INSTALLED, for if it is DELETING we should return it always 
                        // so that Android Keyboard can perform the actual deletion. 
                        final File f = getContext().getFileStreamPath(wordListLocalFilename);
                        if (!f.isFile()) {
                            continue; 
                        } 
                    } else if (MetadataDbHelper.STATUS_AVAILABLE == wordListStatus) {
                        // The locale is the id for the main dictionary. 
                        UpdateHandler.installIfNeverRequested(context, clientId, wordListId,
                                mayPrompt);
                        continue; 
                    } 
                    final WordListInfo currentBestMatch = dicts.get(wordListCategory);
                    if (null == currentBestMatch
                            || currentBestMatch.mMatchLevel < matchLevel) {
                        dicts.put(wordListCategory,
                                new WordListInfo(wordListId, wordListLocale, matchLevel));
                    } 
                } while (results.moveToNext());
            } 
            results.close();
            return Collections.unmodifiableCollection(dicts.values());
        } 
    } 
 
    /** 
     * Deletes the file pointed by Uri, as returned by openAssetFile. 
     * 
     * @param uri the URI the file is for. 
     * @param selection ignored 
     * @param selectionArgs ignored 
     * @return the number of files deleted (0 or 1 in the current implementation) 
     * @see android.content.ContentProvider#delete(Uri, String, String[]) 
     */ 
    @Override 
    public int delete(final Uri uri, final String selection, final String[] selectionArgs)
            throws UnsupportedOperationException { 
        final int match = matchUri(uri);
        if (DICTIONARY_V1_DICT_INFO == match || DICTIONARY_V2_DATAFILE == match) {
            return deleteDataFile(uri);
        } 
        if (DICTIONARY_V2_METADATA == match) {
            if (MetadataDbHelper.deleteClient(getContext(), getClientId(uri))) {
                return 1; 
            } 
            return 0; 
        } 
        // Unsupported URI for delete 
        return 0; 
    } 
 
    private int deleteDataFile(final Uri uri) {
        final String wordlistId = uri.getLastPathSegment();
        final String clientId = getClientId(uri);
        final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId);
        if (null == wordList) return 0;
        final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
        final int version = wordList.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
        if (MetadataDbHelper.STATUS_DELETING == status) {
            UpdateHandler.markAsDeleted(getContext(), clientId, wordlistId, version, status);
            return 1; 
        } else if (MetadataDbHelper.STATUS_INSTALLED == status) {
            final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT);
            if (QUERY_PARAMETER_FAILURE.equals(result)) {
                UpdateHandler.markAsBroken(getContext(), clientId, wordlistId, version);
            } 
            final String localFilename =
                    wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
            final File f = getContext().getFileStreamPath(localFilename);
            // f.delete() returns true if the file was successfully deleted, false otherwise 
            if (f.delete()) {
                return 1; 
            } else { 
                return 0; 
            } 
        } else { 
            Log.e(TAG, "Attempt to delete a file whose status is " + status);
            return 0; 
        } 
    } 
 
    /** 
     * Insert data into the provider. May be either a metadata source URL or some dictionary info. 
     * 
     * @param uri the designated content URI. See sUriMatcherV{1,2} for available URIs. 
     * @param values the values to insert for this content uri 
     * @return the URI for the newly inserted item. May be null if arguments don't allow for insert 
     */ 
    @Override 
    public Uri insert(final Uri uri, final ContentValues values)
            throws UnsupportedOperationException { 
        if (null == uri || null == values) return null; // Should never happen but let's be safe
        PrivateLog.log("Insert, uri = " + uri.toString());
        final String clientId = getClientId(uri);
        switch (matchUri(uri)) {
            case DICTIONARY_V2_METADATA:
                // The values should contain a valid client ID and a valid URI for the metadata. 
                // The client ID may not be null, nor may it be empty because the empty client ID