DictionaryFactory.java revision 660776e09b9a3b321074a94721d901a035ca1b9f
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import android.content.Context;
20import android.content.res.AssetFileDescriptor;
21import android.content.res.Resources;
22import android.util.Log;
23
24import java.io.File;
25import java.util.ArrayList;
26import java.util.LinkedList;
27import java.util.Locale;
28
29/**
30 * Factory for dictionary instances.
31 */
32public class DictionaryFactory {
33
34    private static String TAG = DictionaryFactory.class.getSimpleName();
35
36    /**
37     * Initializes a dictionary from a dictionary pack, with explicit flags.
38     *
39     * This searches for a content provider providing a dictionary pack for the specified
40     * locale. If none is found, it falls back to using the resource passed as fallBackResId
41     * as a dictionary.
42     * @param context application context for reading resources
43     * @param locale the locale for which to create the dictionary
44     * @param fallbackResId the id of the resource to use as a fallback if no pack is found
45     * @param flagArray an array of flags to use
46     * @return an initialized instance of DictionaryCollection
47     */
48    public static DictionaryCollection createDictionaryFromManager(final Context context,
49            final Locale locale, final int fallbackResId, final Flag[] flagArray) {
50        if (null == locale) {
51            Log.e(TAG, "No locale defined for dictionary");
52            return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, locale));
53        }
54
55        final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
56        final ArrayList<AssetFileAddress> assetFileList =
57                BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
58        if (null != assetFileList) {
59            for (final AssetFileAddress f : assetFileList) {
60                final BinaryDictionary binaryDictionary =
61                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, flagArray);
62                if (binaryDictionary.isValidDictionary()) {
63                    dictList.add(binaryDictionary);
64                }
65            }
66        }
67
68        // If the list is empty, that means we should not use any dictionary (for example, the user
69        // explicitly disabled the main dictionary), so the following is okay. dictList is never
70        // null, but if for some reason it is, DictionaryCollection handles it gracefully.
71        return new DictionaryCollection(dictList);
72    }
73
74    /**
75     * Initializes a dictionary from a dictionary pack, with default flags.
76     *
77     * This searches for a content provider providing a dictionary pack for the specified
78     * locale. If none is found, it falls back to using the resource passed as fallBackResId
79     * as a dictionary.
80     * @param context application context for reading resources
81     * @param locale the locale for which to create the dictionary
82     * @param fallbackResId the id of the resource to use as a fallback if no pack is found
83     * @return an initialized instance of DictionaryCollection
84     */
85    public static DictionaryCollection createDictionaryFromManager(final Context context,
86            final Locale locale, final int fallbackResId) {
87        return createDictionaryFromManager(context, locale, fallbackResId, null);
88    }
89
90    /**
91     * Initializes a dictionary from a raw resource file
92     * @param context application context for reading resources
93     * @param resId the resource containing the raw binary dictionary
94     * @param locale the locale to use for the resource
95     * @return an initialized instance of BinaryDictionary
96     */
97    protected static BinaryDictionary createBinaryDictionary(final Context context,
98            final int resId, final Locale locale) {
99        AssetFileDescriptor afd = null;
100        try {
101            final Resources res = context.getResources();
102            if (null != locale) {
103                final Locale savedLocale = LocaleUtils.setSystemLocale(res, locale);
104                afd = res.openRawResourceFd(resId);
105                LocaleUtils.setSystemLocale(res, savedLocale);
106            } else {
107                afd = res.openRawResourceFd(resId);
108            }
109            if (afd == null) {
110                Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
111                return null;
112            }
113            if (!isFullDictionary(afd)) return null;
114            final String sourceDir = context.getApplicationInfo().sourceDir;
115            final File packagePath = new File(sourceDir);
116            // TODO: Come up with a way to handle a directory.
117            if (!packagePath.isFile()) {
118                Log.e(TAG, "sourceDir is not a file: " + sourceDir);
119                return null;
120            }
121            return new BinaryDictionary(context,
122                    sourceDir, afd.getStartOffset(), afd.getLength(), null);
123        } catch (android.content.res.Resources.NotFoundException e) {
124            Log.e(TAG, "Could not find the resource. resId=" + resId);
125            return null;
126        } finally {
127            if (null != afd) {
128                try {
129                    afd.close();
130                } catch (java.io.IOException e) {
131                    /* IOException on close ? What am I supposed to do ? */
132                }
133            }
134        }
135    }
136
137    /**
138     * Create a dictionary from passed data. This is intended for unit tests only.
139     * @param context the test context to create this data from.
140     * @param dictionary the file to read
141     * @param startOffset the offset in the file where the data starts
142     * @param length the length of the data
143     * @param flagArray the flags to use with this data for testing
144     * @return the created dictionary, or null.
145     */
146    public static Dictionary createDictionaryForTest(Context context, File dictionary,
147            long startOffset, long length, Flag[] flagArray) {
148        if (dictionary.isFile()) {
149            return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
150                    flagArray);
151        } else {
152            Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
153            return null;
154        }
155    }
156
157    /**
158     * Find out whether a dictionary is available for this locale.
159     * @param context the context on which to check resources.
160     * @param locale the locale to check for.
161     * @return whether a (non-placeholder) dictionary is available or not.
162     */
163    public static boolean isDictionaryAvailable(Context context, Locale locale) {
164        final Resources res = context.getResources();
165        final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
166
167        final int resourceId = getMainDictionaryResourceId(res);
168        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
169        final boolean hasDictionary = isFullDictionary(afd);
170        try {
171            if (null != afd) afd.close();
172        } catch (java.io.IOException e) {
173            /* Um, what can we do here exactly? */
174        }
175
176        LocaleUtils.setSystemLocale(res, saveLocale);
177        return hasDictionary;
178    }
179
180    // TODO: Do not use the size of the dictionary as an unique dictionary ID.
181    public static Long getDictionaryId(final Context context, final Locale locale) {
182        final Resources res = context.getResources();
183        final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
184
185        final int resourceId = getMainDictionaryResourceId(res);
186        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
187        final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
188                ? afd.getLength()
189                : null;
190        try {
191            if (null != afd) afd.close();
192        } catch (java.io.IOException e) {
193        }
194
195        LocaleUtils.setSystemLocale(res, saveLocale);
196        return size;
197    }
198
199    // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
200    // Suggestion : strip the locale, open the placeholder file and store its offset.
201    // Upon opening the file, if it's the same offset, then it's the placeholder.
202    private static final long PLACEHOLDER_LENGTH = 34;
203    /**
204     * Finds out whether the data pointed out by an AssetFileDescriptor is a full
205     * dictionary (as opposed to null, or to a place holder).
206     * @param afd the file descriptor to test, or null
207     * @return true if the dictionary is a real full dictionary, false if it's null or a placeholder
208     */
209    protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
210        return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
211    }
212
213    /**
214     * Returns a main dictionary resource id
215     * @return main dictionary resource id
216     */
217    public static int getMainDictionaryResourceId(Resources res) {
218        final String MAIN_DIC_NAME = "main";
219        String packageName = LatinIME.class.getPackage().getName();
220        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
221    }
222}
223