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