Add account change receiver to handle account removal

Also cleanup the manifest to group similar components together.
Moving LoginAccountUtils to its own package

Bug: 17464068
Change-Id: Iad7928855531d58334b1e4039bb45b97dde75462
diff --git a/java-overridable/src/com/android/inputmethod/latin/utils/LoginAccountUtils.java b/java-overridable/src/com/android/inputmethod/latin/accounts/LoginAccountUtils.java
similarity index 95%
rename from java-overridable/src/com/android/inputmethod/latin/utils/LoginAccountUtils.java
rename to java-overridable/src/com/android/inputmethod/latin/accounts/LoginAccountUtils.java
index faada29..2dc001c 100644
--- a/java-overridable/src/com/android/inputmethod/latin/utils/LoginAccountUtils.java
+++ b/java-overridable/src/com/android/inputmethod/latin/accounts/LoginAccountUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.utils;
+package com.android.inputmethod.latin.accounts;
 
 import android.content.Context;
 
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index b29a6e2..2249be7 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -37,6 +37,7 @@
             android:supportsRtl="true"
             android:allowBackup="true">
 
+        <!-- Services -->
         <service android:name="LatinIME"
                 android:label="@string/english_ime_name"
                 android:permission="android.permission.BIND_INPUT_METHOD">
@@ -56,6 +57,11 @@
                     android:resource="@xml/spellchecker" />
         </service>
 
+        <service android:name="com.android.inputmethod.dictionarypack.DictionaryService"
+                android:label="@string/dictionary_service_name">
+        </service>
+
+        <!-- Activities -->
         <activity android:name=".setup.SetupActivity"
                 android:theme="@style/platformActivityTheme"
                 android:label="@string/english_ime_name"
@@ -77,15 +83,6 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name="SystemBroadcastReceiver">
-            <intent-filter>
-                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-                <action android:name="android.intent.action.USER_INITIALIZE" />
-                <action android:name="android.intent.action.LOCALE_CHANGED" />
-            </intent-filter>
-        </receiver>
-
         <activity android:name=".settings.SettingsActivity"
                 android:theme="@style/platformSettingsTheme"
                 android:label="@string/english_ime_settings"
@@ -103,45 +100,6 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name="SuggestionSpanPickedNotificationReceiver" android:enabled="true">
-            <intent-filter>
-                <action android:name="android.text.style.SUGGESTION_PICKED" />
-            </intent-filter>
-        </receiver>
-
-        <receiver android:name=".personalization.DictionaryDecayBroadcastReciever"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="com.android.inputmethod.latin.personalization.DICT_DECAY" />
-            </intent-filter>
-        </receiver>
-
-        <receiver android:name=".DictionaryPackInstallBroadcastReceiver" android:exported="false">
-            <intent-filter>
-                <action android:name="com.android.inputmethod.dictionarypack.aosp.UNKNOWN_CLIENT" />
-            </intent-filter>
-        </receiver>
-
-        <provider android:name="com.android.inputmethod.dictionarypack.DictionaryProvider"
-                android:grantUriPermissions="true"
-                android:exported="false"
-                android:authorities="@string/authority"
-                android:multiprocess="false"
-                android:label="@string/dictionary_provider_name">
-        </provider>
-
-        <service android:name="com.android.inputmethod.dictionarypack.DictionaryService"
-                android:label="@string/dictionary_service_name">
-        </service>
-
-        <receiver android:name="com.android.inputmethod.dictionarypack.EventHandler">
-            <intent-filter>
-                <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
-                <action android:name="android.intent.action.DATE_CHANGED" />
-                <action android:name="com.android.inputmethod.dictionarypack.aosp.UPDATE_NOW" />
-            </intent-filter>
-        </receiver>
-
         <activity android:name="com.android.inputmethod.dictionarypack.DictionarySettingsActivity"
                 android:theme="@style/platformSettingsTheme"
                 android:label="@string/dictionary_settings_title"
@@ -162,5 +120,59 @@
         <!-- Unexported activity used for tests. -->
         <activity android:name=".settings.TestFragmentActivity"
                 android:exported="false" />
+
+        <!-- Broadcast receivers -->
+        <receiver android:name="SystemBroadcastReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.USER_INITIALIZE" />
+                <action android:name="android.intent.action.LOCALE_CHANGED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".personalization.DictionaryDecayBroadcastReciever"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.inputmethod.latin.personalization.DICT_DECAY" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".DictionaryPackInstallBroadcastReceiver" android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.inputmethod.dictionarypack.aosp.UNKNOWN_CLIENT" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="SuggestionSpanPickedNotificationReceiver" android:enabled="true">
+            <intent-filter>
+                <action android:name="android.text.style.SUGGESTION_PICKED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="com.android.inputmethod.dictionarypack.EventHandler">
+            <intent-filter>
+                <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
+                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="com.android.inputmethod.dictionarypack.aosp.UPDATE_NOW" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Broadcast receiver for AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION. -->
+        <receiver
+            android:name=".accounts.AccountsChangedReceiver">
+            <intent-filter>
+                <action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Content providers -->
+        <provider android:name="com.android.inputmethod.dictionarypack.DictionaryProvider"
+                android:grantUriPermissions="true"
+                android:exported="false"
+                android:authorities="@string/authority"
+                android:multiprocess="false"
+                android:label="@string/dictionary_provider_name">
+        </provider>
     </application>
 </manifest>
diff --git a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java
new file mode 100644
index 0000000..9445ce4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 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.accounts;
+
+import android.accounts.AccountManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.settings.Settings;
+
+/**
+ * {@link BroadcastReceiver} for {@link AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION}.
+ */
+public class AccountsChangedReceiver extends BroadcastReceiver {
+    static final String TAG = "AccountsChangedReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) {
+            Log.w(TAG, "Received unknown broadcast: " + intent);
+            return;
+        }
+
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final String currentAccount = prefs.getString(Settings.PREF_ACCOUNT_NAME, null);
+        if (currentAccount != null) {
+            final String[] accounts = getAccountsForLogin(context);
+            boolean accountFound = false;
+            for (String account : accounts) {
+                if (TextUtils.equals(currentAccount, account)) {
+                    accountFound = true;
+                    break;
+                }
+            }
+            // The current account was not found in the list of accounts, remove it.
+            if (!accountFound) {
+                Log.i(TAG, "The current account was removed from the system: " + currentAccount);
+                prefs.edit()
+                        .remove(Settings.PREF_ACCOUNT_NAME)
+                        .apply();
+            }
+        }
+    }
+
+    /**
+     * Helper method to help test this receiver.
+     */
+    @UsedForTesting
+    protected String[] getAccountsForLogin(Context context) {
+        return LoginAccountUtils.getAccountsForLogin(context);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
index 06ab1e2..ffb0ad7 100644
--- a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
@@ -30,8 +30,8 @@
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.accounts.LoginAccountUtils;
 import com.android.inputmethod.latin.define.ProductionFlags;
-import com.android.inputmethod.latin.utils.LoginAccountUtils;
 
 import javax.annotation.Nullable;
 
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 3339ab5..964d5ce 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -64,6 +64,7 @@
     public final boolean mSoundOn;
     public final boolean mKeyPreviewPopupOn;
     public final boolean mShowsVoiceInputKey;
+    public final String mAccountName;
     public final boolean mIncludesOtherImesInLanguageSwitchList;
     public final boolean mShowsLanguageSwitchKey;
     public final boolean mUseContactsDict;
@@ -140,6 +141,7 @@
         mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
                 && mInputAttributes.mShouldShowVoiceInputKey
                 && SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+        mAccountName = prefs.getString(Settings.PREF_ACCOUNT_NAME, null);
         final String autoCorrectionThresholdRawValue = prefs.getString(
                 Settings.PREF_AUTO_CORRECTION_THRESHOLD,
                 res.getString(R.string.auto_correction_threshold_mode_index_modest));
@@ -383,6 +385,8 @@
         sb.append("" + mKeyPreviewPopupOn);
         sb.append("\n   mShowsVoiceInputKey = ");
         sb.append("" + mShowsVoiceInputKey);
+        sb.append("\n   mAccountName = ");
+        sb.append("" + mAccountName);
         sb.append("\n   mIncludesOtherImesInLanguageSwitchList = ");
         sb.append("" + mIncludesOtherImesInLanguageSwitchList);
         sb.append("\n   mShowsLanguageSwitchKey = ");
diff --git a/tests/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiverTests.java b/tests/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiverTests.java
new file mode 100644
index 0000000..3319eec
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiverTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 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.accounts;
+
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.latin.settings.Settings;
+
+/**
+ * Tests for {@link AccountsChangedReceiver}.
+ */
+public class AccountsChangedReceiverTests extends AndroidTestCase {
+    private static final String ACCOUNT_1 = "account1@example.com";
+    private static final String ACCOUNT_2 = "account2@example.com";
+
+    private SharedPreferences mPrefs;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        // Remove all preferences before the next test.
+        mPrefs.edit().clear();
+    }
+
+    public void testUnknownIntent() {
+        updateAccountName(ACCOUNT_1);
+        AccountsChangedReceiver reciever = new AccountsChangedReceiver();
+        reciever.onReceive(getContext(), new Intent("some-random-action"));
+        // Account should *not* be removed from preferences.
+        assertAccountName(ACCOUNT_1);
+    }
+
+    public void testAccountRemoved() {
+        updateAccountName(ACCOUNT_1);
+        AccountsChangedReceiver reciever = new AccountsChangedReceiver() {
+            @Override
+            protected String[] getAccountsForLogin(Context context) {
+                return new String[] {ACCOUNT_2};
+            }
+        };
+        reciever.onReceive(getContext(), new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION));
+        // Account should be removed from preferences.
+        assertAccountName(null);
+    }
+
+    public void testAccountRemoved_noAccounts() {
+        updateAccountName(ACCOUNT_2);
+        AccountsChangedReceiver reciever = new AccountsChangedReceiver() {
+            @Override
+            protected String[] getAccountsForLogin(Context context) {
+                return new String[0];
+            }
+        };
+        reciever.onReceive(getContext(), new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION));
+        // Account should be removed from preferences.
+        assertAccountName(null);
+    }
+
+    public void testAccountNotRemoved() {
+        updateAccountName(ACCOUNT_2);
+        AccountsChangedReceiver reciever = new AccountsChangedReceiver() {
+            @Override
+            protected String[] getAccountsForLogin(Context context) {
+                return new String[] {ACCOUNT_1, ACCOUNT_2};
+            }
+        };
+        reciever.onReceive(getContext(), new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION));
+        // Account should *not* be removed from preferences.
+        assertAccountName(ACCOUNT_2);
+    }
+
+    private void updateAccountName(String accountName) {
+        mPrefs.edit()
+                .putString(Settings.PREF_ACCOUNT_NAME, accountName)
+                .commit();
+    }
+
+    private void assertAccountName(String expectedAccountName) {
+        assertEquals(expectedAccountName, mPrefs.getString(Settings.PREF_ACCOUNT_NAME, null));
+    }
+}