DictionaryFactory.java revision e150ef98569d61078e0f8c67ded8364a9c3d4a20
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.LinkedList;
26import java.util.List;
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.
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     * @return an initialized instance of Dictionary
46     */
47    public static Dictionary createDictionaryFromManager(Context context, Locale locale,
48            int fallbackResId) {
49        if (null == locale) {
50            Log.e(TAG, "No locale defined for dictionary");
51            return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, locale));
52        }
53
54        final List<Dictionary> dictList = new LinkedList<Dictionary>();
55        final List<AssetFileAddress> assetFileList =
56                BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
57        if (null != assetFileList) {
58            for (final AssetFileAddress f : assetFileList) {
59                final BinaryDictionary binaryDictionary =
60                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null);
61                if (binaryDictionary.isValidDictionary()) {
62                    dictList.add(binaryDictionary);
63                }
64            }
65        }
66
67        // null == dictList is not supposed to be possible, but better safe than sorry and it's
68        // safer for future extension. In this case, rather than returning null, it should be safer
69        // to return an empty DictionaryCollection.
70        if (null == dictList) {
71            return new DictionaryCollection();
72        } else {
73            if (dictList.isEmpty()) {
74                // The list may be empty if no dictionaries have been added. The getter should not
75                // return an empty list, but if it does we end up here. Likewise, if the files
76                // we found could not be opened by the native code for any reason (format mismatch,
77                // file too big to fit in memory, etc) then we could have an empty list. In this
78                // case we want to fall back on the resource.
79                return new DictionaryCollection(createBinaryDictionary(context, fallbackResId,
80                        locale));
81            } else {
82                return new DictionaryCollection(dictList);
83            }
84        }
85    }
86
87    /**
88     * Initializes a dictionary from a raw resource file
89     * @param context application context for reading resources
90     * @param resId the resource containing the raw binary dictionary
91     * @param locale the locale to use for the resource
92     * @return an initialized instance of BinaryDictionary
93     */
94    protected static BinaryDictionary createBinaryDictionary(final Context context,
95            final int resId, final Locale locale) {
96        AssetFileDescriptor afd = null;
97        try {
98            final Resources res = context.getResources();
99            if (null != locale) {
100                final Locale savedLocale = Utils.setSystemLocale(res, locale);
101                afd = res.openRawResourceFd(resId);
102                Utils.setSystemLocale(res, savedLocale);
103            } else {
104                afd = res.openRawResourceFd(resId);
105            }
106            if (afd == null) {
107                Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
108                return null;
109            }
110            if (!isFullDictionary(afd)) return null;
111            final String sourceDir = context.getApplicationInfo().sourceDir;
112            final File packagePath = new File(sourceDir);
113            // TODO: Come up with a way to handle a directory.
114            if (!packagePath.isFile()) {
115                Log.e(TAG, "sourceDir is not a file: " + sourceDir);
116                return null;
117            }
118            return new BinaryDictionary(context,
119                    sourceDir, afd.getStartOffset(), afd.getLength(), null);
120        } catch (android.content.res.Resources.NotFoundException e) {
121            Log.e(TAG, "Could not find the resource. resId=" + resId);
122            return null;
123        } finally {
124            if (null != afd) {
125                try {
126                    afd.close();
127                } catch (java.io.IOException e) {
128                    /* IOException on close ? What am I supposed to do ? */
129                }
130            }
131        }
132    }
133
134    /**
135     * Create a dictionary from passed data. This is intended for unit tests only.
136     * @param context the test context to create this data from.
137     * @param dictionary the file to read
138     * @param startOffset the offset in the file where the data starts
139     * @param length the length of the data
140     * @param flagArray the flags to use with this data for testing
141     * @return the created dictionary, or null.
142     */
143    public static Dictionary createDictionaryForTest(Context context, File dictionary,
144            long startOffset, long length, Flag[] flagArray) {
145        if (dictionary.isFile()) {
146            return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
147                    flagArray);
148        } else {
149            Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
150            return null;
151        }
152    }
153
154    /**
155     * Find out whether a dictionary is available for this locale.
156     * @param context the context on which to check resources.
157     * @param locale the locale to check for.
158     * @return whether a (non-placeholder) dictionary is available or not.
159     */
160    public static boolean isDictionaryAvailable(Context context, Locale locale) {
161        final Resources res = context.getResources();
162        final Locale saveLocale = Utils.setSystemLocale(res, locale);
163
164        final int resourceId = Utils.getMainDictionaryResourceId(res);
165        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
166        final boolean hasDictionary = isFullDictionary(afd);
167        try {
168            if (null != afd) afd.close();
169        } catch (java.io.IOException e) {
170            /* Um, what can we do here exactly? */
171        }
172
173        Utils.setSystemLocale(res, saveLocale);
174        return hasDictionary;
175    }
176
177    // TODO: Do not use the size of the dictionary as an unique dictionary ID.
178    public static Long getDictionaryId(Context context, Locale locale) {
179        final Resources res = context.getResources();
180        final Locale saveLocale = Utils.setSystemLocale(res, locale);
181
182        final int resourceId = Utils.getMainDictionaryResourceId(res);
183        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
184        final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
185                ? afd.getLength()
186                : null;
187        try {
188            if (null != afd) afd.close();
189        } catch (java.io.IOException e) {
190        }
191
192        Utils.setSystemLocale(res, saveLocale);
193        return size;
194    }
195
196    // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
197    // Suggestion : strip the locale, open the placeholder file and store its offset.
198    // Upon opening the file, if it's the same offset, then it's the placeholder.
199    private static final long PLACEHOLDER_LENGTH = 34;
200    /**
201     * Finds out whether the data pointed out by an AssetFileDescriptor is a full
202     * dictionary (as opposed to null, or to a place holder).
203     * @param afd the file descriptor to test, or null
204     * @return true if the dictionary is a real full dictionary, false if it's null or a placeholder
205     */
206    protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
207        return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
208    }
209}
210