10cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard/**
20cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Copyright (C) 2011 The Android Open Source Project
30cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
40cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Licensed under the Apache License, Version 2.0 (the "License"); you may not
50cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * use this file except in compliance with the License. You may obtain a copy of
60cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the License at
70cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
80cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * http://www.apache.org/licenses/LICENSE-2.0
90cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Unless required by applicable law or agreed to in writing, software
110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * License for the specific language governing permissions and limitations under
140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the License.
150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */
160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardpackage com.android.inputmethod.dictionarypack;
180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.ContentProvider;
200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.ContentResolver;
210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.ContentValues;
220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.Context;
230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.UriMatcher;
240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.res.AssetFileDescriptor;
250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.database.AbstractCursor;
260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.database.Cursor;
270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.database.sqlite.SQLiteDatabase;
280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.net.Uri;
290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.os.ParcelFileDescriptor;
300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.text.TextUtils;
310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.util.Log;
320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport com.android.inputmethod.latin.R;
340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.File;
360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.FileNotFoundException;
370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.Collection;
380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.Collections;
390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.HashMap;
400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard/**
420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Provider for dictionaries.
430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This class is a ContentProvider exposing all available dictionary data as managed by
450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the dictionary pack.
460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */
470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardpublic final class DictionaryProvider extends ContentProvider {
480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final String TAG = DictionaryProvider.class.getSimpleName();
490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final boolean DEBUG = false;
500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final Uri CONTENT_URI =
521061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard            Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + DictionaryPackConstants.AUTHORITY);
530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final String QUERY_PARAMETER_TRUE = "true";
550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final String QUERY_PARAMETER_DELETE_RESULT = "result";
560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final String QUERY_PARAMETER_SUCCESS = "success";
570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final String QUERY_PARAMETER_FAILURE = "failure";
580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol";
590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int NO_MATCH = 0;
600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int DICTIONARY_V1_WHOLE_LIST = 1;
610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int DICTIONARY_V1_DICT_INFO = 2;
620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int DICTIONARY_V2_METADATA = 3;
630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int DICTIONARY_V2_WHOLE_LIST = 4;
640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int DICTIONARY_V2_DICT_INFO = 5;
650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final int DICTIONARY_V2_DATAFILE = 6;
660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final UriMatcher sUriMatcherV1 = new UriMatcher(NO_MATCH);
670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final UriMatcher sUriMatcherV2 = new UriMatcher(NO_MATCH);
680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    static
690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    {
701061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard        sUriMatcherV1.addURI(DictionaryPackConstants.AUTHORITY, "list", DICTIONARY_V1_WHOLE_LIST);
711061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard        sUriMatcherV1.addURI(DictionaryPackConstants.AUTHORITY, "*", DICTIONARY_V1_DICT_INFO);
721061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard        sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/metadata",
731061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard                DICTIONARY_V2_METADATA);
741061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard        sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/list", DICTIONARY_V2_WHOLE_LIST);
751061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard        sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/dict/*",
761061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard                DICTIONARY_V2_DICT_INFO);
771061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard        sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/datafile/*",
781061bfdb34bbcb63bf0046eec42313d264ac33faJean Chalard                DICTIONARY_V2_DATAFILE);
790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    // MIME types for dictionary and dictionary list, as required by ContentProvider contract.
820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final String DICT_LIST_MIME_TYPE =
830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            "vnd.android.cursor.item/vnd.google.dictionarylist";
840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final String DICT_DATAFILE_MIME_TYPE =
850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            "vnd.android.cursor.item/vnd.google.dictionary";
860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final String ID_CATEGORY_SEPARATOR = ":";
880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final class WordListInfo {
900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public final String mId;
910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public final String mLocale;
920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public final int mMatchLevel;
930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public WordListInfo(final String id, final String locale, final int matchLevel) {
940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mId = id;
950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mLocale = locale;
960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mMatchLevel = matchLevel;
970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
1010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * A cursor for returning a list of file ids from a List of strings.
1020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
1030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * This simulates only the necessary methods. It has no error handling to speak of,
1040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * and does not support everything a database does, only a few select necessary methods.
1050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
1060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static final class ResourcePathCursor extends AbstractCursor {
1070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // Column names for the cursor returned by this content provider.
1090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static private final String[] columnNames = { "id", "locale" };
1100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The list of word lists served by this provider that match the client request.
1120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListInfo[] mWordLists;
1130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // Note : the cursor also uses mPos, which is defined in AbstractCursor.
1140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public ResourcePathCursor(final Collection<WordListInfo> wordLists) {
1160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // Allocating a 0-size WordListInfo here allows the toArray() method
1170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // to ensure we have a strongly-typed array. It's thrown out. That's
1180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // what the documentation of #toArray says to do in order to get a
1190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // new strongly typed array of the correct size.
1200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordLists = wordLists.toArray(new WordListInfo[0]);
1210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mPos = 0;
1220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
1250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public String[] getColumnNames() {
1260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return columnNames;
1270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
1300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public int getCount() {
1310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return mWordLists.length;
1320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override public double getDouble(int column) { return 0; }
1350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override public float getFloat(int column) { return 0; }
1360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override public int getInt(int column) { return 0; }
1370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override public short getShort(int column) { return 0; }
1380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override public long getLong(int column) { return 0; }
1390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override public String getString(final int column) {
1410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            switch (column) {
1420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                case 0: return mWordLists[mPos].mId;
1430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                case 1: return mWordLists[mPos].mLocale;
1440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                default : return null;
1450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
1460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
1490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public boolean isNull(final int column) {
1500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (mPos >= mWordLists.length) return true;
1510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return column != 0;
1520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
1540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
1560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public boolean onCreate() {
1570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        return true;
1580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
1590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static int matchUri(final Uri uri) {
1610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        int protocolVersion = 1;
1620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String protocolVersionArg = uri.getQueryParameter(QUERY_PARAMETER_PROTOCOL_VERSION);
1630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if ("2".equals(protocolVersionArg)) protocolVersion = 2;
1640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        switch (protocolVersion) {
1650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case 1: return sUriMatcherV1.match(uri);
1660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case 2: return sUriMatcherV2.match(uri);
1670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            default: return NO_MATCH;
1680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
1700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private static String getClientId(final Uri uri) {
1720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        int protocolVersion = 1;
1730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String protocolVersionArg = uri.getQueryParameter(QUERY_PARAMETER_PROTOCOL_VERSION);
1740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if ("2".equals(protocolVersionArg)) protocolVersion = 2;
1750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        switch (protocolVersion) {
1760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case 1: return null; // In protocol 1, the client ID is always null.
1770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case 2: return uri.getPathSegments().get(0);
1780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            default: return null;
1790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
1810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
1830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Returns the MIME type of the content associated with an Uri
1840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
1850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @see android.content.ContentProvider#getType(android.net.Uri)
1860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
1870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param uri the URI of the content the type of which should be returned.
1880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return the MIME type, or null if the URL is not recognized.
1890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
1900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
1910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public String getType(final Uri uri) {
192f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard        PrivateLog.log("Asked for type of : " + uri);
1930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final int match = matchUri(uri);
1940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        switch (match) {
1950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case NO_MATCH: return null;
1960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V1_WHOLE_LIST:
1970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V1_DICT_INFO:
1980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_WHOLE_LIST:
1990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_DICT_INFO: return DICT_LIST_MIME_TYPE;
2000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_DATAFILE: return DICT_DATAFILE_MIME_TYPE;
2010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            default: return null;
2020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
2040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
2060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Query the provider for dictionary files.
2070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
2080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * This version dispatches the query according to the protocol version found in the
2090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * ?protocol= query parameter. If absent or not well-formed, it defaults to 1.
2100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @see android.content.ContentProvider#query(Uri, String[], String, String[], String)
2110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
2120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param uri a content uri (see sUriMatcherV{1,2} at the top of this file for format)
2130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param projection ignored. All columns are always returned.
2140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param selection ignored.
2150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param selectionArgs ignored.
2160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param sortOrder ignored. The results are always returned in no particular order.
2170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return a cursor matching the uri, or null if the URI was not recognized.
2180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
2190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
2200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public Cursor query(final Uri uri, final String[] projection, final String selection,
2210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String[] selectionArgs, final String sortOrder) {
2220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        Utils.l("Uri =", uri);
223f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard        PrivateLog.log("Query : " + uri);
2240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String clientId = getClientId(uri);
2250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final int match = matchUri(uri);
2260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        switch (match) {
2270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V1_WHOLE_LIST:
2280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_WHOLE_LIST:
2290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId);
2300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Utils.l("List of dictionaries with count", c.getCount());
231f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                PrivateLog.log("Returned a list of " + c.getCount() + " items");
2320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return c;
2330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_DICT_INFO:
2340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // In protocol version 2, we return null if the client is unknown. Otherwise
2350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // we behave exactly like for protocol 1.
2360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                if (!MetadataDbHelper.isClientKnown(getContext(), clientId)) return null;
2370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // Fall through
2380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V1_DICT_INFO:
2390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final String locale = uri.getLastPathSegment();
2400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // If LatinIME does not have a dictionary for this locale at all, it will
2410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // send us true for this value. In this case, we may prompt the user for
2420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // a decision about downloading a dictionary even over a metered connection.
2430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final String mayPromptValue =
2440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        uri.getQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER);
2450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final boolean mayPrompt = QUERY_PARAMETER_TRUE.equals(mayPromptValue);
2460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final Collection<WordListInfo> dictFiles =
2470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        getDictionaryWordListsForLocale(clientId, locale, mayPrompt);
2480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // TODO: pass clientId to the following function
2490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                DictionaryService.updateNowIfNotUpdatedInAVeryLongTime(getContext());
2500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                if (null != dictFiles && dictFiles.size() > 0) {
251f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                    PrivateLog.log("Returned " + dictFiles.size() + " files");
2520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    return new ResourcePathCursor(dictFiles);
2530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                } else {
254f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                    PrivateLog.log("No dictionary files for this URL");
2550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    return new ResourcePathCursor(Collections.<WordListInfo>emptyList());
2560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                }
2570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // V2_METADATA and V2_DATAFILE are not supported for query()
2580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            default:
2590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return null;
2600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
2620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
2640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Helper method to get the wordlist metadata associated with a wordlist ID.
2650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
2660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param clientId the ID of the client
2670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param wordlistId the ID of the wordlist for which to get the metadata.
2680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return the metadata for this wordlist ID, or null if none could be found.
2690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
2700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private ContentValues getWordlistMetadataForWordlistId(final String clientId,
2710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String wordlistId) {
2720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final Context context = getContext();
2730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (TextUtils.isEmpty(wordlistId)) return null;
2740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
2750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        return MetadataDbHelper.getInstalledOrDeletingWordListContentValuesByWordListId(
2760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                db, wordlistId);
2770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
2780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
2800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Opens an asset file for an URI.
2810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
2820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Called by {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} or
2830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * {@link android.content.ContentResolver#openInputStream(Uri)} from a client requesting a
2840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * dictionary.
2850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @see android.content.ContentProvider#openAssetFile(Uri, String)
2860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
2870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param uri the URI the file is for.
2880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param mode the mode to read the file. MUST be "r" for readonly.
2890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return the descriptor, or null if the file is not found or if mode is not equals to "r".
2900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
2910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
2920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public AssetFileDescriptor openAssetFile(final Uri uri, final String mode) {
2930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (null == mode || !"r".equals(mode)) return null;
2940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final int match = matchUri(uri);
2960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (DICTIONARY_V1_DICT_INFO != match && DICTIONARY_V2_DATAFILE != match) {
2970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // Unsupported URI for openAssetFile
2980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            Log.w(TAG, "Unsupported URI for openAssetFile : " + uri);
2990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return null;
3000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String wordlistId = uri.getLastPathSegment();
3020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String clientId = getClientId(uri);
3030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId);
3040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (null == wordList) return null;
3060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        try {
3080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
3090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_DELETING == status) {
3100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // This will return an empty file (R.raw.empty points at an empty dictionary)
3110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // This is how we "delete" the files. It allows Android Keyboard to fake deleting
3120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // a default dictionary - which is actually in its assets and can't be really
3130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // deleted.
3140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(
3150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        R.raw.empty);
3160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return afd;
3170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } else {
3180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final String localFilename =
3190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
3200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final File f = getContext().getFileStreamPath(localFilename);
3210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final ParcelFileDescriptor pfd =
3220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
3230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return new AssetFileDescriptor(pfd, 0, pfd.getStatSize());
3240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
3250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        } catch (FileNotFoundException e) {
3260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // No file : fall through and return null
3270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        return null;
3290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
3300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
3320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Reads the metadata and returns the collection of dictionaries for a given locale.
3330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
3340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Word list IDs are expected to be in the form category:manual_id. This method
3350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * will select only one word list for each category: the one with the most specific
3360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * locale matching the locale specified in the URI. The manual id serves only to
3370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * distinguish a word list from another for the purpose of updating, and is arbitrary
3380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * but may not contain a colon.
3390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
3400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param clientId the ID of the client requesting the list
3410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param locale the locale for which we want the list, as a String
3420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param mayPrompt true if we are allowed to prompt the user for arbitration via notification
3430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return a collection of ids. It is guaranteed to be non-null, but may be empty.
3440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
3450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private Collection<WordListInfo> getDictionaryWordListsForLocale(final String clientId,
3460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String locale, final boolean mayPrompt) {
3470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final Context context = getContext();
3480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final Cursor results =
3490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                MetadataDbHelper.queryInstalledOrDeletingOrAvailableDictionaryMetadata(context,
3500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        clientId);
3510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (null == results) {
3520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return Collections.<WordListInfo>emptyList();
3530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        } else {
3540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final HashMap<String, WordListInfo> dicts = new HashMap<String, WordListInfo>();
3550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
3560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int localeIndex = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
3570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int localFileNameIndex =
3580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
3590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int statusIndex = results.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
3600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (results.moveToFirst()) {
3610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                do {
3620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final String wordListId = results.getString(idIndex);
3630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    if (TextUtils.isEmpty(wordListId)) continue;
3640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final String[] wordListIdArray =
3650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            TextUtils.split(wordListId, ID_CATEGORY_SEPARATOR);
3660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final String wordListCategory;
3670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    if (2 == wordListIdArray.length) {
3680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // This is at the category:manual_id format.
3690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        wordListCategory = wordListIdArray[0];
3700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // We don't need to read wordListIdArray[1] here, because it's irrelevant to
3710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // word list selection - it's just a name we use to identify which data file
3720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // is a newer version of which word list. We do however return the full id
3730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // string for each selected word list, so in this sense we are 'using' it.
3740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    } else {
3750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // This does not contain a colon, like the old format does. Old-format IDs
3760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // always point to main dictionaries, so we force the main category upon it.
3770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        wordListCategory = UpdateHandler.MAIN_DICTIONARY_CATEGORY;
3780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    }
3790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final String wordListLocale = results.getString(localeIndex);
3800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final String wordListLocalFilename = results.getString(localFileNameIndex);
3810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final int wordListStatus = results.getInt(statusIndex);
3820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // Test the requested locale against this wordlist locale. The requested locale
3830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // has to either match exactly or be more specific than the dictionary - a
3840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // dictionary for "en" would match both a request for "en" or for "en_US", but a
3850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // dictionary for "en_GB" would not match a request for "en_US". Thus if all
3860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // three of "en" "en_US" and "en_GB" dictionaries are installed, a request for
3870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // "en_US" would match "en" and "en_US", and a request for "en" only would only
3880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // match the generic "en" dictionary. For more details, see the documentation
3890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    // for LocaleUtils#getMatchLevel.
3900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final int matchLevel = LocaleUtils.getMatchLevel(wordListLocale, locale);
3910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    if (!LocaleUtils.isMatch(matchLevel)) {
3920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // The locale of this wordlist does not match the required locale.
3930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // Skip this wordlist and go to the next.
3940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        continue;
3950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    }
3960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    if (MetadataDbHelper.STATUS_INSTALLED == wordListStatus) {
3970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // If the file does not exist, it has been deleted and the IME should
3980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // already have it. Do not return it. However, this only applies if the
3990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // word list is INSTALLED, for if it is DELETING we should return it always
4000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // so that Android Keyboard can perform the actual deletion.
4010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        final File f = getContext().getFileStreamPath(wordListLocalFilename);
4020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        if (!f.isFile()) {
4030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            continue;
4040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        }
4050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    } else if (MetadataDbHelper.STATUS_AVAILABLE == wordListStatus) {
4060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // The locale is the id for the main dictionary.
4070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        UpdateHandler.installIfNeverRequested(context, clientId, wordListId,
4080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                                mayPrompt);
4090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        continue;
4100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    }
4110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final WordListInfo currentBestMatch = dicts.get(wordListCategory);
4120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    if (null == currentBestMatch
4130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            || currentBestMatch.mMatchLevel < matchLevel) {
4140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        dicts.put(wordListCategory,
4150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                                new WordListInfo(wordListId, wordListLocale, matchLevel));
4160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    }
4170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                } while (results.moveToNext());
4180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            results.close();
4200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return Collections.unmodifiableCollection(dicts.values());
4210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
4230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
4240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
4250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Deletes the file pointed by Uri, as returned by openAssetFile.
4260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
4270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param uri the URI the file is for.
4280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param selection ignored
4290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param selectionArgs ignored
4300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return the number of files deleted (0 or 1 in the current implementation)
4310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @see android.content.ContentProvider#delete(Uri, String, String[])
4320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
4330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
4340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public int delete(final Uri uri, final String selection, final String[] selectionArgs)
4350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            throws UnsupportedOperationException {
4360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final int match = matchUri(uri);
4370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (DICTIONARY_V1_DICT_INFO == match || DICTIONARY_V2_DATAFILE == match) {
4380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return deleteDataFile(uri);
4390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (DICTIONARY_V2_METADATA == match) {
4410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.deleteClient(getContext(), getClientId(uri))) {
4420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return 1;
4430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return 0;
4450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // Unsupported URI for delete
4470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        return 0;
4480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
4490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
4500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private int deleteDataFile(final Uri uri) {
4510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String wordlistId = uri.getLastPathSegment();
4520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String clientId = getClientId(uri);
4530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId);
4540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (null == wordList) return 0;
4550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
4560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final int version = wordList.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
4570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (MetadataDbHelper.STATUS_DELETING == status) {
4580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            UpdateHandler.markAsDeleted(getContext(), clientId, wordlistId, version, status);
4590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return 1;
4600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        } else if (MetadataDbHelper.STATUS_INSTALLED == status) {
4610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT);
4620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (QUERY_PARAMETER_FAILURE.equals(result)) {
4630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                UpdateHandler.markAsBroken(getContext(), clientId, wordlistId, version);
4640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String localFilename =
4660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
4670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final File f = getContext().getFileStreamPath(localFilename);
4680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // f.delete() returns true if the file was successfully deleted, false otherwise
4690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (f.delete()) {
4700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return 1;
4710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } else {
4720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return 0;
4730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        } else {
4750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            Log.e(TAG, "Attempt to delete a file whose status is " + status);
4760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            return 0;
4770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
4790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
4800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
4810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Insert data into the provider. May be either a metadata source URL or some dictionary info.
4820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
4830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param uri the designated content URI. See sUriMatcherV{1,2} for available URIs.
4840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param values the values to insert for this content uri
4850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @return the URI for the newly inserted item. May be null if arguments don't allow for insert
4860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
4870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
4880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public Uri insert(final Uri uri, final ContentValues values)
4890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            throws UnsupportedOperationException {
4900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        if (null == uri || null == values) return null; // Should never happen but let's be safe
491f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard        PrivateLog.log("Insert, uri = " + uri.toString());
4920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final String clientId = getClientId(uri);
4930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        switch (matchUri(uri)) {
4940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_METADATA:
4950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // The values should contain a valid client ID and a valid URI for the metadata.
4960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // The client ID may not be null, nor may it be empty because the empty client ID
4970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // is reserved for internal use.
4980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // The metadata URI may not be null, but it may be empty if the client does not
4990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // want the dictionary pack to update the metadata automatically.
5000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                MetadataDbHelper.updateClientInfo(getContext(), clientId, values);
5010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                break;
5020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V2_DICT_INFO:
5030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                try {
5040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final WordListMetadata newDictionaryMetadata =
5050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            WordListMetadata.createFromContentValues(
5060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                                    MetadataDbHelper.completeWithDefaultValues(values));
5070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    new ActionBatch.MarkPreInstalledAction(clientId, newDictionaryMetadata)
5080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            .execute(getContext());
5090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                } catch (final BadFormatException e) {
5100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    Log.w(TAG, "Not enough information to insert this dictionary " + values, e);
5110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                }
51276d5f512f99700a963aa20a02590833e37221bffJean Chalard                // We just received new information about the list of dictionary for this client.
51376d5f512f99700a963aa20a02590833e37221bffJean Chalard                // For all intents and purposes, this is new metadata, so we should publish it
51476d5f512f99700a963aa20a02590833e37221bffJean Chalard                // so that any listeners (like the Settings interface for example) can update
51576d5f512f99700a963aa20a02590833e37221bffJean Chalard                // themselves.
51676d5f512f99700a963aa20a02590833e37221bffJean Chalard                UpdateHandler.publishUpdateMetadataCompleted(getContext(), true);
5170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                break;
5180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V1_WHOLE_LIST:
5190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            case DICTIONARY_V1_DICT_INFO:
520f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                PrivateLog.log("Attempt to insert : " + uri);
5210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                throw new UnsupportedOperationException(
5220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        "Insertion in the dictionary is not supported in this version");
5230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
5240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        return uri;
5250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
5260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
5270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
5280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Updating data is not supported, and will throw an exception.
5290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @see android.content.ContentProvider#update(Uri, ContentValues, String, String[])
5300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @see android.content.ContentProvider#insert(Uri, ContentValues)
5310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
5320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    @Override
5330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public int update(final Uri uri, final ContentValues values, final String selection,
5340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String[] selectionArgs) throws UnsupportedOperationException {
535f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard        PrivateLog.log("Attempt to update : " + uri);
5360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        throw new UnsupportedOperationException("Updating dictionary words is not supported");
5370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
5380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard}
539