Try decaying user history at hourly intervals.

Bug: 6669677

Change-Id: Ib465fa7e1a7f289a07843535ba89d0dd5259e803
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index b54406f..031d62e 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -110,6 +110,12 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".personalization.DictionaryDecayBroadcastReciever">
+            <intent-filter>
+                <action android:name="com.android.inputmethod.latin.personalization.DICT_DECAY" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name=".DictionaryPackInstallBroadcastReceiver">
             <intent-filter>
                 <action android:name="com.android.inputmethod.dictionarypack.aosp.UNKNOWN_CLIENT" />
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 306c1a2..0985aae 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -273,9 +273,9 @@
                 lastModifiedTime);
     }
 
-    private void runGCIfRequired() {
+    protected void runGCIfRequired(final boolean mindsBlockByGC) {
         if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
-        if (mBinaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+        if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
             if (setIsRegeneratingIfNotRegenerating()) {
                 getExecutor(mFilename).execute(new Runnable() {
                     @Override
@@ -300,7 +300,7 @@
             Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
             return;
         }
-        runGCIfRequired();
+        runGCIfRequired(true /* mindsBlockByGC */);
         getExecutor(mFilename).execute(new Runnable() {
             @Override
             public void run() {
@@ -324,7 +324,7 @@
                     + mFilename);
             return;
         }
-        runGCIfRequired();
+        runGCIfRequired(true /* mindsBlockByGC */);
         getExecutor(mFilename).execute(new Runnable() {
             @Override
             public void run() {
@@ -348,7 +348,7 @@
                     + mFilename);
             return;
         }
-        runGCIfRequired();
+        runGCIfRequired(true /* mindsBlockByGC */);
         getExecutor(mFilename).execute(new Runnable() {
             @Override
             public void run() {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 96e16de..5b7e935 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -77,6 +77,7 @@
 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
@@ -567,6 +568,8 @@
         newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
         registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
 
+        DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
+
         mInputUpdater = new InputUpdater(this);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 7cf4f0c..2662164 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -236,4 +236,8 @@
         // Then flush the cleared state of the dictionary on disk.
         asyncFlashAllBinaryDictionary();
     }
+
+    /* package */ void decayIfNeeded() {
+        runGCIfRequired(false /* mindsBlockByGC */);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
new file mode 100644
index 0000000..e9ca662
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Broadcast receiver for periodically updating decaying dictionaries.
+ */
+public class DictionaryDecayBroadcastReciever extends BroadcastReceiver {
+    /**
+     * The root domain for the personalization.
+     */
+    private static final String PERSONALIZATION_DOMAIN =
+            "com.android.inputmethod.latin.personalization";
+
+    /**
+     * The action of the intent to tell the time to decay dictionaries.
+     */
+    private static final String DICTIONARY_DECAY_INTENT_ACTION =
+            PERSONALIZATION_DOMAIN + ".DICT_DECAY";
+
+    /**
+     * Interval to update for decaying dictionaries.
+     */
+    private static final long DICTIONARY_DECAY_INTERVAL = TimeUnit.MINUTES.toMillis(60);
+
+    public static void setUpIntervalAlarmForDictionaryDecaying(Context context) {
+        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
+        final Intent updateIntent = new Intent(DICTIONARY_DECAY_INTENT_ACTION);
+        updateIntent.setClass(context, DictionaryDecayBroadcastReciever.class);
+        final long alarmTime =  System.currentTimeMillis() + DICTIONARY_DECAY_INTERVAL;
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */,
+                updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+        if (null != alarmManager) alarmManager.setInexactRepeating(AlarmManager.RTC,
+                alarmTime, DICTIONARY_DECAY_INTERVAL, pendingIntent);
+    }
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) {
+            PersonalizationHelper.tryDecayingAllOpeningUserHistoryDictionary();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 8c9484b..221ddee 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -29,7 +29,6 @@
 public class PersonalizationHelper {
     private static final String TAG = PersonalizationHelper.class.getSimpleName();
     private static final boolean DEBUG = false;
-
     private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
             sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
 
@@ -62,6 +61,18 @@
         }
     }
 
+    public static void tryDecayingAllOpeningUserHistoryDictionary() {
+        for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry
+                : sLangUserHistoryDictCache.entrySet()) {
+            if (entry.getValue() != null) {
+                final UserHistoryDictionary dict = entry.getValue().get();
+                if (dict != null) {
+                    dict.decayIfNeeded();
+                }
+            }
+        }
+    }
+
     public static void registerPersonalizationDictionaryUpdateSession(final Context context,
             final PersonalizationDictionaryUpdateSession session, String locale) {
         final PersonalizationPredictionDictionary predictionDictionary =