Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.latin;
     18 
     19 import android.content.ContentProviderClient;
     20 import android.content.Context;
     21 import android.content.res.AssetFileDescriptor;
     22 import android.util.Log;
     23 
     24 import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
     25 
     26 import java.io.File;
     27 import java.util.ArrayList;
     28 import java.util.LinkedList;
     29 import java.util.Locale;
     30 
     31 /**
     32  * Factory for dictionary instances.
     33  */
     34 public final class DictionaryFactory {
     35     private static final String TAG = DictionaryFactory.class.getSimpleName();
     36 
     37     /**
     38      * Initializes a main dictionary collection from a dictionary pack, with explicit flags.
     39      *
     40      * This searches for a content provider providing a dictionary pack for the specified
     41      * locale. If none is found, it falls back to the built-in dictionary - if any.
     42      * @param context application context for reading resources
     43      * @param locale the locale for which to create the dictionary
     44      * @return an initialized instance of DictionaryCollection
     45      */
     46     public static DictionaryCollection createMainDictionaryFromManager(final Context context,
     47             final Locale locale) {
     48         if (null == locale) {
     49             Log.e(TAG, "No locale defined for dictionary");
     50             return new DictionaryCollection(Dictionary.TYPE_MAIN, locale,
     51                     createReadOnlyBinaryDictionary(context, locale));
     52         }
     53 
     54         final LinkedList<Dictionary> dictList = new LinkedList<>();
     55         final ArrayList<AssetFileAddress> assetFileList =
     56                 BinaryDictionaryGetter.getDictionaryFiles(locale, context, true);
     57         if (null != assetFileList) {
     58             for (final AssetFileAddress f : assetFileList) {
     59                 final ReadOnlyBinaryDictionary readOnlyBinaryDictionary =
     60                         new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
     61                                 false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
     62                 if (readOnlyBinaryDictionary.isValidDictionary()) {
     63                     dictList.add(readOnlyBinaryDictionary);
     64                 } else {
     65                     readOnlyBinaryDictionary.close();
     66                     // Prevent this dictionary to do any further harm.
     67                     killDictionary(context, f);
     68                 }
     69             }
     70         }
     71 
     72         // If the list is empty, that means we should not use any dictionary (for example, the user
     73         // explicitly disabled the main dictionary), so the following is okay. dictList is never
     74         // null, but if for some reason it is, DictionaryCollection handles it gracefully.
     75         return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList);
     76     }
     77 
     78     /**
     79      * Kills a dictionary so that it is never used again, if possible.
     80      * @param context The context to contact the dictionary provider, if possible.
     81      * @param f A file address to the dictionary to kill.
     82      */
     83     public static void killDictionary(final Context context, final AssetFileAddress f) {
     84         if (f.pointsToPhysicalFile()) {
     85             f.deleteUnderlyingFile();
     86             // Warn the dictionary provider if the dictionary came from there.
     87             final ContentProviderClient providerClient;
     88             try {
     89                 providerClient = context.getContentResolver().acquireContentProviderClient(
     90                         BinaryDictionaryFileDumper.getProviderUriBuilder("").build());
     91             } catch (final SecurityException e) {
     92                 Log.e(TAG, "No permission to communicate with the dictionary provider", e);
     93                 return;
     94             }
     95             if (null == providerClient) {
     96                 Log.e(TAG, "Can't establish communication with the dictionary provider");
     97                 return;
     98             }
     99             final String wordlistId =
    100                     DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName());
    101             // TODO: this is a reasonable last resort, but it is suboptimal.
    102             // The following will remove the entry for this dictionary with the dictionary
    103             // provider. When the metadata is downloaded again, we will try downloading it
    104             // again.
    105             // However, in the practice that will mean the user will find themselves without
    106             // the new dictionary. That's fine for languages where it's included in the APK,
    107             // but for other languages it will leave the user without a dictionary at all until
    108             // the next update, which may be a few days away.
    109             // Ideally, we would trigger a new download right away, and use increasing retry
    110             // delays for this particular id/version combination.
    111             // Then again, this is expected to only ever happen in case of human mistake. If
    112             // the wrong file is on the server, the following is still doing the right thing.
    113             // If it's a file left over from the last version however, it's not great.
    114             BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
    115                     providerClient,
    116                     context.getString(R.string.dictionary_pack_client_id),
    117                     wordlistId);
    118         }
    119     }
    120 
    121     /**
    122      * Initializes a read-only binary dictionary from a raw resource file
    123      * @param context application context for reading resources
    124      * @param locale the locale to use for the resource
    125      * @return an initialized instance of ReadOnlyBinaryDictionary
    126      */
    127     private static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context,
    128             final Locale locale) {
    129         AssetFileDescriptor afd = null;
    130         try {
    131             final int resId = DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
    132                     context.getResources(), locale);
    133             if (0 == resId) return null;
    134             afd = context.getResources().openRawResourceFd(resId);
    135             if (afd == null) {
    136                 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
    137                 return null;
    138             }
    139             final String sourceDir = context.getApplicationInfo().sourceDir;
    140             final File packagePath = new File(sourceDir);
    141             // TODO: Come up with a way to handle a directory.
    142             if (!packagePath.isFile()) {
    143                 Log.e(TAG, "sourceDir is not a file: " + sourceDir);
    144                 return null;
    145             }
    146             return new ReadOnlyBinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
    147                     false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
    148         } catch (android.content.res.Resources.NotFoundException e) {
    149             Log.e(TAG, "Could not find the resource");
    150             return null;
    151         } finally {
    152             if (null != afd) {
    153                 try {
    154                     afd.close();
    155                 } catch (java.io.IOException e) {
    156                     /* IOException on close ? What am I supposed to do ? */
    157                 }
    158             }
    159         }
    160     }
    161 }
    162