1d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka/* 2d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * Copyright (C) 2013 The Android Open Source Project 3d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * 4d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * Licensed under the Apache License, Version 2.0 (the "License"); 5d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * you may not use this file except in compliance with the License. 6d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * You may obtain a copy of the License at 7d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * 8d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * http://www.apache.org/licenses/LICENSE-2.0 9d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * 10d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * Unless required by applicable law or agreed to in writing, software 11d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * distributed under the License is distributed on an "AS IS" BASIS, 12d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * See the License for the specific language governing permissions and 14d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka * limitations under the License. 15d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka */ 16d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka 17d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataokapackage com.android.inputmethod.latin.personalization; 18d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka 19d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataokaimport android.content.Context; 20d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataokaimport android.util.Log; 21d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka 22a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaokaimport com.android.inputmethod.latin.utils.FileUtils; 23a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka 24f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagiimport java.io.File; 25f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagiimport java.io.FilenameFilter; 26d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataokaimport java.lang.ref.SoftReference; 272fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasaimport java.util.Locale; 28d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataokaimport java.util.concurrent.ConcurrentHashMap; 29d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagiimport java.util.concurrent.TimeUnit; 30d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka 316e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagipublic class PersonalizationHelper { 326e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi private static final String TAG = PersonalizationHelper.class.getSimpleName(); 33d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka private static final boolean DEBUG = false; 345ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> 35a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka sLangUserHistoryDictCache = new ConcurrentHashMap<>(); 36d4528b88e132ce2f25e45455a073b81385fcbd81Satoshi Kataoka private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>> 37a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka sLangPersonalizationDictCache = new ConcurrentHashMap<>(); 38d4528b88e132ce2f25e45455a073b81385fcbd81Satoshi Kataoka 395ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi public static UserHistoryDictionary getUserHistoryDictionary( 402fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final Context context, final Locale locale) { 412fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final String localeStr = locale.toString(); 4260586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka synchronized (sLangUserHistoryDictCache) { 432fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (sLangUserHistoryDictCache.containsKey(localeStr)) { 445ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi final SoftReference<UserHistoryDictionary> ref = 452fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa sLangUserHistoryDictCache.get(localeStr); 465ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi final UserHistoryDictionary dict = ref == null ? null : ref.get(); 47d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka if (dict != null) { 48d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka if (DEBUG) { 495ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi Log.w(TAG, "Use cached UserHistoryDictionary for " + locale); 50d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka } 51ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada dict.reloadDictionaryIfRequired(); 52d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka return dict; 53d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka } 54d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka } 552fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale); 56a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka sLangUserHistoryDictCache.put(localeStr, new SoftReference<>(dict)); 5760586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka return dict; 5860586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka } 5960586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka } 6060586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka 61d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi private static int sCurrentTimestampForTesting = 0; 62d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi public static void currentTimeChangedForTesting(final int currentTimestamp) { 63d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi if (TimeUnit.MILLISECONDS.toSeconds( 64d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi DictionaryDecayBroadcastReciever.DICTIONARY_DECAY_INTERVAL) 65d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi < currentTimestamp - sCurrentTimestampForTesting) { 66d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi runGCOnAllOpenedUserHistoryDictionaries(); 6789eaa6701fd40c909cda492712b9afa7134805e1Keisuke Kuroyanagi runGCOnAllOpenedPersonalizationDictionaries(); 68d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi } 69d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi } 70d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi 71d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi public static void runGCOnAllOpenedUserHistoryDictionaries() { 72d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi runGCOnAllDictionariesIfRequired(sLangUserHistoryDictCache); 73d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi } 74d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi 75d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi public static void runGCOnAllOpenedPersonalizationDictionaries() { 76d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi runGCOnAllDictionariesIfRequired(sLangPersonalizationDictCache); 77d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi } 78d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi 79d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi private static <T extends DecayingExpandableBinaryDictionaryBase> 80d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi void runGCOnAllDictionariesIfRequired( 81d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap) { 82d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry 83d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi : dictionaryMap.entrySet()) { 84d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get(); 85d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi if (dict != null) { 86d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi dict.runGCIfRequired(); 87d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi } else { 88d302b98ce63743bde9d8d8c14755b5cf71c4e7a3Keisuke Kuroyanagi dictionaryMap.remove(entry.getKey()); 89f36a97ab3abf7fb3766ed6ff553a2b6501d0908fKeisuke Kuroyanagi } 90f36a97ab3abf7fb3766ed6ff553a2b6501d0908fKeisuke Kuroyanagi } 91f36a97ab3abf7fb3766ed6ff553a2b6501d0908fKeisuke Kuroyanagi } 92f36a97ab3abf7fb3766ed6ff553a2b6501d0908fKeisuke Kuroyanagi 93366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka public static PersonalizationDictionary getPersonalizationDictionary( 942fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final Context context, final Locale locale) { 952fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final String localeStr = locale.toString(); 9660586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka synchronized (sLangPersonalizationDictCache) { 972fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (sLangPersonalizationDictCache.containsKey(localeStr)) { 98366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka final SoftReference<PersonalizationDictionary> ref = 992fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa sLangPersonalizationDictCache.get(localeStr); 100366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka final PersonalizationDictionary dict = ref == null ? null : ref.get(); 101366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka if (dict != null) { 102366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka if (DEBUG) { 1032fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa Log.w(TAG, "Use cached PersonalizationDictionary for " + locale); 104366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka } 105366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka return dict; 106366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka } 107366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka } 1082fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale); 109a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka sLangPersonalizationDictCache.put(localeStr, new SoftReference<>(dict)); 110d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka return dict; 111d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka } 112d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka } 113f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi 114d102eb80da07de6b9541c3e8d767441d84257b8dKeisuke Kuroyanagi public static void removeAllPersonalizationDictionaries(final Context context) { 115f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi removeAllDictionaries(context, sLangPersonalizationDictCache, 116f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi PersonalizationDictionary.NAME); 117f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 118f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi 119d102eb80da07de6b9541c3e8d767441d84257b8dKeisuke Kuroyanagi public static void removeAllUserHistoryDictionaries(final Context context) { 120d102eb80da07de6b9541c3e8d767441d84257b8dKeisuke Kuroyanagi removeAllDictionaries(context, sLangUserHistoryDictCache, 121d102eb80da07de6b9541c3e8d767441d84257b8dKeisuke Kuroyanagi UserHistoryDictionary.NAME); 122d102eb80da07de6b9541c3e8d767441d84257b8dKeisuke Kuroyanagi } 123d102eb80da07de6b9541c3e8d767441d84257b8dKeisuke Kuroyanagi 124f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi private static <T extends DecayingExpandableBinaryDictionaryBase> void removeAllDictionaries( 125f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi final Context context, final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap, 126f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi final String dictNamePrefix) { 127f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi synchronized (dictionaryMap) { 128f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry 129f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi : dictionaryMap.entrySet()) { 130f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi if (entry.getValue() != null) { 131f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get(); 132f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi if (dict != null) { 1332dcb5c1b4d399501fc7645bf933f08f3a0e7e512Keisuke Kuroyanagi dict.clear(); 134f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 135f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 136f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 137f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi dictionaryMap.clear(); 138a545e8dd23ea270714173f564940896e9acd322eKeisuke Kuroyanagi final File filesDir = context.getFilesDir(); 139a545e8dd23ea270714173f564940896e9acd322eKeisuke Kuroyanagi if (filesDir == null) { 140a545e8dd23ea270714173f564940896e9acd322eKeisuke Kuroyanagi Log.e(TAG, "context.getFilesDir() returned null."); 14179ff803cb0d30a162d05cdd06347d084f98b1284Keisuke Kuroyanagi return; 142a545e8dd23ea270714173f564940896e9acd322eKeisuke Kuroyanagi } 143a545e8dd23ea270714173f564940896e9acd322eKeisuke Kuroyanagi if (!FileUtils.deleteFilteredFiles(filesDir, new DictFilter(dictNamePrefix))) { 144f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi Log.e(TAG, "Cannot remove all existing dictionary files. filesDir: " 145a545e8dd23ea270714173f564940896e9acd322eKeisuke Kuroyanagi + filesDir.getAbsolutePath() + ", dictNamePrefix: " + dictNamePrefix); 146f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 147f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 148f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 149f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi 150f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi private static class DictFilter implements FilenameFilter { 151f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi private final String mName; 152f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi 153f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi DictFilter(final String name) { 154f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi mName = name; 155f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 156f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi 157f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi @Override 158f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi public boolean accept(final File dir, final String name) { 159f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi return name.startsWith(mName); 160f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 161f1457e7a221082688b6399853e84e77948633c7bKeisuke Kuroyanagi } 162d45e4b6e5bf8d9a7edf95e400b2ce6e7b49b41e1Satoshi Kataoka} 163