Merge "Restructure TSMS"
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index a357909..951a073 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -75,12 +75,9 @@
     private final Context mContext;
     private final TextServicesMonitor mMonitor;
     private final SparseArray<TextServicesData> mUserData = new SparseArray<>();
-    private final TextServicesSettings mSettings;
     @NonNull
     private final UserManager mUserManager;
     private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private int mSpellCheckerMapUpdateCount = 0;
 
     private static class TextServicesData {
         @UserIdInt
@@ -88,12 +85,180 @@
         private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap;
         private final ArrayList<SpellCheckerInfo> mSpellCheckerList;
         private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
+        private final Context mContext;
+        private final ContentResolver mResolver;
+        public int mUpdateCount = 0;
 
-        public TextServicesData(@UserIdInt int userId) {
+        public TextServicesData(@UserIdInt int userId, @NonNull Context context) {
             mUserId = userId;
             mSpellCheckerMap = new HashMap<>();
             mSpellCheckerList = new ArrayList<>();
             mSpellCheckerBindGroups = new HashMap<>();
+            mContext = context;
+            mResolver = context.getContentResolver();
+        }
+
+        private void putString(final String key, final String str) {
+            Settings.Secure.putStringForUser(mResolver, key, str, mUserId);
+        }
+
+        @Nullable
+        private String getString(@NonNull final String key, @Nullable final String defaultValue) {
+            final String result;
+            result = Settings.Secure.getStringForUser(mResolver, key, mUserId);
+            return result != null ? result : defaultValue;
+        }
+
+        private void putInt(final String key, final int value) {
+            Settings.Secure.putIntForUser(mResolver, key, value, mUserId);
+        }
+
+        private int getInt(final String key, final int defaultValue) {
+            return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mUserId);
+        }
+
+        private boolean getBoolean(final String key, final boolean defaultValue) {
+            return getInt(key, defaultValue ? 1 : 0) == 1;
+        }
+
+        private void putSelectedSpellChecker(@Nullable String sciId) {
+            if (TextUtils.isEmpty(sciId)) {
+                // OK to coalesce to null, since getSelectedSpellChecker() can take care of the
+                // empty data scenario.
+                putString(Settings.Secure.SELECTED_SPELL_CHECKER, null);
+            } else {
+                putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId);
+            }
+        }
+
+        private void putSelectedSpellCheckerSubtype(int hashCode) {
+            putInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, hashCode);
+        }
+
+        @NonNull
+        private String getSelectedSpellChecker() {
+            return getString(Settings.Secure.SELECTED_SPELL_CHECKER, "");
+        }
+
+        public int getSelectedSpellCheckerSubtype(final int defaultValue) {
+            return getInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, defaultValue);
+        }
+
+        public boolean isSpellCheckerEnabled() {
+            return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true);
+        }
+
+        @Nullable
+        public SpellCheckerInfo getCurrentSpellChecker() {
+            final String curSpellCheckerId = getSelectedSpellChecker();
+            if (TextUtils.isEmpty(curSpellCheckerId)) {
+                return null;
+            }
+            return mSpellCheckerMap.get(curSpellCheckerId);
+        }
+
+        public void setCurrentSpellChecker(SpellCheckerInfo sci) {
+            putSelectedSpellChecker(sci.getId());
+            putSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
+        }
+
+        private void initializeTextServicesData() {
+            if (DBG) {
+                Slog.d(TAG, "initializeTextServicesData for user: " + mUserId);
+            }
+            mSpellCheckerList.clear();
+            mSpellCheckerMap.clear();
+            mUpdateCount++;
+            final PackageManager pm = mContext.getPackageManager();
+            // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the
+            // default behavior of PackageManager is exactly what we want.  It by default picks up
+            // appropriate services depending on the unlock state for the specified user.
+            final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                    new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
+                    mUserId);
+            final int N = services.size();
+            for (int i = 0; i < N; ++i) {
+                final ResolveInfo ri = services.get(i);
+                final ServiceInfo si = ri.serviceInfo;
+                final ComponentName compName = new ComponentName(si.packageName, si.name);
+                if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
+                    Slog.w(TAG, "Skipping text service " + compName
+                            + ": it does not require the permission "
+                            + android.Manifest.permission.BIND_TEXT_SERVICE);
+                    continue;
+                }
+                if (DBG) Slog.d(TAG, "Add: " + compName + " for user: " + mUserId);
+                try {
+                    final SpellCheckerInfo sci = new SpellCheckerInfo(mContext, ri);
+                    if (sci.getSubtypeCount() <= 0) {
+                        Slog.w(TAG, "Skipping text service " + compName
+                                + ": it does not contain subtypes.");
+                        continue;
+                    }
+                    mSpellCheckerList.add(sci);
+                    mSpellCheckerMap.put(sci.getId(), sci);
+                } catch (XmlPullParserException e) {
+                    Slog.w(TAG, "Unable to load the spell checker " + compName, e);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Unable to load the spell checker " + compName, e);
+                }
+            }
+            if (DBG) {
+                Slog.d(TAG, "initializeSpellCheckerMap: " + mSpellCheckerList.size() + ","
+                        + mSpellCheckerMap.size());
+            }
+        }
+
+        private void dump(PrintWriter pw) {
+            int spellCheckerIndex = 0;
+            pw.println("  User #" + mUserId);
+            pw.println("  Spell Checkers:");
+            pw.println("  Spell Checkers: " +  "mUpdateCount=" + mUpdateCount);
+            for (final SpellCheckerInfo info : mSpellCheckerMap.values()) {
+                pw.println("  Spell Checker #" + spellCheckerIndex);
+                info.dump(pw, "    ");
+                ++spellCheckerIndex;
+            }
+
+            pw.println("");
+            pw.println("  Spell Checker Bind Groups:");
+            HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = mSpellCheckerBindGroups;
+            for (final Map.Entry<String, SpellCheckerBindGroup> ent
+                    : spellCheckerBindGroups.entrySet()) {
+                final SpellCheckerBindGroup grp = ent.getValue();
+                pw.println("    " + ent.getKey() + " " + grp + ":");
+                pw.println("      " + "mInternalConnection=" + grp.mInternalConnection);
+                pw.println("      " + "mSpellChecker=" + grp.mSpellChecker);
+                pw.println("      " + "mUnbindCalled=" + grp.mUnbindCalled);
+                pw.println("      " + "mConnected=" + grp.mConnected);
+                final int numPendingSessionRequests = grp.mPendingSessionRequests.size();
+                for (int j = 0; j < numPendingSessionRequests; j++) {
+                    final SessionRequest req = grp.mPendingSessionRequests.get(j);
+                    pw.println("      " + "Pending Request #" + j + ":");
+                    pw.println("        " + "mTsListener=" + req.mTsListener);
+                    pw.println("        " + "mScListener=" + req.mScListener);
+                    pw.println(
+                            "        " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
+                }
+                final int numOnGoingSessionRequests = grp.mOnGoingSessionRequests.size();
+                for (int j = 0; j < numOnGoingSessionRequests; j++) {
+                    final SessionRequest req = grp.mOnGoingSessionRequests.get(j);
+                    pw.println("      " + "On going Request #" + j + ":");
+                    ++j;
+                    pw.println("        " + "mTsListener=" + req.mTsListener);
+                    pw.println("        " + "mScListener=" + req.mScListener);
+                    pw.println(
+                            "        " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
+                }
+                final int N = grp.mListeners.getRegisteredCallbackCount();
+                for (int j = 0; j < N; j++) {
+                    final ISpellCheckerSessionListener mScListener =
+                            grp.mListeners.getRegisteredCallbackItem(j);
+                    pw.println("      " + "Listener #" + j + ":");
+                    pw.println("        " + "mScListener=" + mScListener);
+                    pw.println("        " + "mGroup=" + grp);
+                }
+            }
         }
     }
 
@@ -153,18 +318,17 @@
 
         mMonitor = new TextServicesMonitor();
         mMonitor.register(context, null, UserHandle.ALL, true);
-        mSettings = new TextServicesSettings(context.getContentResolver());
     }
 
     private void initializeInternalStateLocked(@UserIdInt int userId) {
         TextServicesData tsd = mUserData.get(userId);
         if (tsd == null) {
-            tsd = new TextServicesData(userId);
+            tsd = new TextServicesData(userId, mContext);
             mUserData.put(userId, tsd);
         }
 
-        updateTextServiceDataLocked(tsd);
-        SpellCheckerInfo sci = getCurrentSpellCheckerInternalLocked(tsd);
+        tsd.initializeTextServicesData();
+        SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
         if (sci == null) {
             sci = findAvailSpellCheckerLocked(null, tsd);
             if (sci != null) {
@@ -176,14 +340,6 @@
         }
     }
 
-    private void updateTextServiceDataLocked(TextServicesData tsd) {
-        if (DBG) {
-            Slog.d(TAG, "updateTextServiceDataLocked for user: " + tsd.mUserId);
-        }
-        buildSpellCheckerMapLocked(mContext, tsd.mSpellCheckerList, tsd.mSpellCheckerMap,
-                tsd.mUserId);
-    }
-
     private final class TextServicesMonitor extends PackageMonitor {
         @Override
         public void onSomePackagesChanged() {
@@ -197,8 +353,8 @@
                 if (tsd == null) return;
 
                 // TODO: Update for each locale
-                SpellCheckerInfo sci = getCurrentSpellCheckerInternalLocked(tsd);
-                updateTextServiceDataLocked(tsd);
+                SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
+                tsd.initializeTextServicesData();
                 // If no spell checker is enabled, just return. The user should explicitly
                 // enable the spell checker.
                 if (sci == null) return;
@@ -219,51 +375,6 @@
         }
     }
 
-    private void buildSpellCheckerMapLocked(Context context,
-            ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map,
-            @UserIdInt int userId) {
-        list.clear();
-        map.clear();
-        mSpellCheckerMapUpdateCount++;
-        final PackageManager pm = context.getPackageManager();
-        // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
-        // behavior of PackageManager is exactly what we want.  It by default picks up appropriate
-        // services depending on the unlock state for the specified user.
-        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
-                new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
-                userId);
-        final int N = services.size();
-        for (int i = 0; i < N; ++i) {
-            final ResolveInfo ri = services.get(i);
-            final ServiceInfo si = ri.serviceInfo;
-            final ComponentName compName = new ComponentName(si.packageName, si.name);
-            if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
-                Slog.w(TAG, "Skipping text service " + compName
-                        + ": it does not require the permission "
-                        + android.Manifest.permission.BIND_TEXT_SERVICE);
-                continue;
-            }
-            if (DBG) Slog.d(TAG, "Add: " + compName + " for user: " + userId);
-            try {
-                final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
-                if (sci.getSubtypeCount() <= 0) {
-                    Slog.w(TAG, "Skipping text service " + compName
-                            + ": it does not contain subtypes.");
-                    continue;
-                }
-                list.add(sci);
-                map.put(sci.getId(), sci);
-            } catch (XmlPullParserException e) {
-                Slog.w(TAG, "Unable to load the spell checker " + compName, e);
-            } catch (IOException e) {
-                Slog.w(TAG, "Unable to load the spell checker " + compName, e);
-            }
-        }
-        if (DBG) {
-            Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size());
-        }
-    }
-
     private boolean bindCurrentSpellCheckerService(
             Intent service, ServiceConnection conn, int flags, @UserIdInt int userId) {
         if (service == null || conn == null) {
@@ -348,21 +459,10 @@
             TextServicesData tsd = mUserData.get(userId);
             if (tsd == null) return null;
 
-            return getCurrentSpellCheckerInternalLocked(tsd);
+            return tsd.getCurrentSpellChecker();
         }
     }
 
-    private SpellCheckerInfo getCurrentSpellCheckerInternalLocked(TextServicesData tsd) {
-        final String curSpellCheckerId = mSettings.getSelectedSpellChecker(tsd.mUserId);
-        if (DBG) {
-            Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId);
-        }
-        if (TextUtils.isEmpty(curSpellCheckerId)) {
-            return null;
-        }
-        return tsd.mSpellCheckerMap.get(curSpellCheckerId);
-    }
-
     // TODO: Respect allowImplicitlySelectedSubtype
     // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
     @Override
@@ -378,12 +478,11 @@
             if (tsd == null) return null;
 
             subtypeHashCode =
-                    mSettings.getSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE,
-                            userId);
+                    tsd.getSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
             if (DBG) {
                 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCode);
             }
-            sci = getCurrentSpellCheckerInternalLocked(tsd);
+            sci = tsd.getCurrentSpellChecker();
             systemLocale = mContext.getResources().getConfiguration().locale;
         }
         if (sci == null || sci.getSubtypeCount() == 0) {
@@ -496,7 +595,7 @@
             TextServicesData tsd = mUserData.get(userId);
             if (tsd == null) return false;
 
-            return isSpellCheckerEnabledLocked(userId);
+            return tsd.isSpellCheckerEnabled();
         }
     }
 
@@ -578,42 +677,7 @@
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            mSettings.putSelectedSpellChecker(sciId, tsd.mUserId);
-            setCurrentSpellCheckerSubtypeLocked(0, tsd);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private void setCurrentSpellCheckerSubtypeLocked(int hashCode, TextServicesData tsd) {
-        if (DBG) {
-            Slog.w(TAG, "setCurrentSpellCheckerSubtype: " + hashCode);
-        }
-
-        final SpellCheckerInfo sci = getCurrentSpellCheckerInternalLocked(tsd);
-        int tempHashCode = 0;
-        for (int i = 0; sci != null && i < sci.getSubtypeCount(); ++i) {
-            if(sci.getSubtypeAt(i).hashCode() == hashCode) {
-                tempHashCode = hashCode;
-                break;
-            }
-        }
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mSettings.putSelectedSpellCheckerSubtype(tempHashCode, tsd.mUserId);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private boolean isSpellCheckerEnabledLocked(@UserIdInt int userId) {
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            final boolean retval = mSettings.isSpellCheckerEnabled(userId);
-            if (DBG) {
-                Slog.w(TAG, "getSpellCheckerEnabled: " + retval);
-            }
-            return retval;
+            tsd.setCurrentSpellChecker(sci);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -626,14 +690,11 @@
         if (args.length == 0) {  // Dump all users' data
             synchronized (mLock) {
                 pw.println("Current Text Services Manager state:");
-                pw.println("  Text Services: mSpellCheckerMapUpdateCount="
-                        + mSpellCheckerMapUpdateCount);
                 pw.println("  Users:");
                 final int numOfUsers = mUserData.size();
                 for (int i = 0; i < numOfUsers; i++) {
-                    int userId = mUserData.keyAt(i);
-                    pw.println("  User #" + userId);
-                    dumpUserDataLocked(pw, userId);
+                    TextServicesData tsd = mUserData.valueAt(i);
+                    tsd.dump(pw);
                 }
             }
         } else {  // Dump a given user's data
@@ -654,68 +715,13 @@
                 }
                 synchronized (mLock) {
                     pw.println("Current Text Services Manager state:");
-                    pw.println("  Text Services: mSpellCheckerMapUpdateCount="
-                            + mSpellCheckerMapUpdateCount);
                     pw.println("  User " + userId + ":");
-                    dumpUserDataLocked(pw, userId);
+                    tsd.dump(pw);
                 }
             }
         }
     }
 
-    private void dumpUserDataLocked(PrintWriter pw, @UserIdInt int userId) {
-        TextServicesData tsd = mUserData.get(userId);
-        HashMap<String, SpellCheckerInfo> spellCheckerMap = tsd.mSpellCheckerMap;
-        int spellCheckerIndex = 0;
-        pw.println("  Spell Checkers:");
-        for (final SpellCheckerInfo info : spellCheckerMap.values()) {
-            pw.println("  Spell Checker #" + spellCheckerIndex);
-            info.dump(pw, "    ");
-            ++spellCheckerIndex;
-        }
-
-        pw.println("");
-        pw.println("  Spell Checker Bind Groups:");
-        HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
-                tsd.mSpellCheckerBindGroups;
-        for (final Map.Entry<String, SpellCheckerBindGroup> ent
-                : spellCheckerBindGroups.entrySet()) {
-            final SpellCheckerBindGroup grp = ent.getValue();
-            pw.println("    " + ent.getKey() + " " + grp + ":");
-            pw.println("      " + "mInternalConnection=" + grp.mInternalConnection);
-            pw.println("      " + "mSpellChecker=" + grp.mSpellChecker);
-            pw.println("      " + "mUnbindCalled=" + grp.mUnbindCalled);
-            pw.println("      " + "mConnected=" + grp.mConnected);
-            final int numPendingSessionRequests = grp.mPendingSessionRequests.size();
-            for (int j = 0; j < numPendingSessionRequests; j++) {
-                final SessionRequest req = grp.mPendingSessionRequests.get(j);
-                pw.println("      " + "Pending Request #" + j + ":");
-                pw.println("        " + "mTsListener=" + req.mTsListener);
-                pw.println("        " + "mScListener=" + req.mScListener);
-                pw.println(
-                        "        " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
-            }
-            final int numOnGoingSessionRequests = grp.mOnGoingSessionRequests.size();
-            for (int j = 0; j < numOnGoingSessionRequests; j++) {
-                final SessionRequest req = grp.mOnGoingSessionRequests.get(j);
-                pw.println("      " + "On going Request #" + j + ":");
-                ++j;
-                pw.println("        " + "mTsListener=" + req.mTsListener);
-                pw.println("        " + "mScListener=" + req.mScListener);
-                pw.println(
-                        "        " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
-            }
-            final int N = grp.mListeners.getRegisteredCallbackCount();
-            for (int j = 0; j < N; j++) {
-                final ISpellCheckerSessionListener mScListener =
-                        grp.mListeners.getRegisteredCallbackItem(j);
-                pw.println("      " + "Listener #" + j + ":");
-                pw.println("        " + "mScListener=" + mScListener);
-                pw.println("        " + "mGroup=" + grp);
-            }
-        }
-    }
-
     private static final class SessionRequest {
         @UserIdInt
         public final int mUserId;
@@ -971,64 +977,4 @@
             mBindGroup.onSessionCreated(newSession, mRequest);
         }
     }
-
-    private static final class TextServicesSettings {
-        private final ContentResolver mResolver;
-
-        public TextServicesSettings(ContentResolver resolver) {
-            mResolver = resolver;
-        }
-
-        private void putString(final String key, final String str, @UserIdInt int userId) {
-            Settings.Secure.putStringForUser(mResolver, key, str, userId);
-        }
-
-        @Nullable
-        private String getString(@NonNull final String key, @Nullable final String defaultValue,
-                @UserIdInt int userId) {
-            final String result;
-            result = Settings.Secure.getStringForUser(mResolver, key, userId);
-            return result != null ? result : defaultValue;
-        }
-
-        private void putInt(final String key, final int value, @UserIdInt int userId) {
-            Settings.Secure.putIntForUser(mResolver, key, value, userId);
-        }
-
-        private int getInt(final String key, final int defaultValue, @UserIdInt int userId) {
-            return Settings.Secure.getIntForUser(mResolver, key, defaultValue, userId);
-        }
-
-        private boolean getBoolean(final String key, final boolean defaultValue,
-                @UserIdInt int userId) {
-            return getInt(key, defaultValue ? 1 : 0, userId) == 1;
-        }
-
-        public void putSelectedSpellChecker(@Nullable String sciId, @UserIdInt int userId) {
-            if (TextUtils.isEmpty(sciId)) {
-                // OK to coalesce to null, since getSelectedSpellChecker() can take care of the
-                // empty data scenario.
-                putString(Settings.Secure.SELECTED_SPELL_CHECKER, null, userId);
-            } else {
-                putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId, userId);
-            }
-        }
-
-        public void putSelectedSpellCheckerSubtype(int hashCode, @UserIdInt int userId) {
-            putInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, hashCode, userId);
-        }
-
-        @NonNull
-        public String getSelectedSpellChecker(@UserIdInt int userId) {
-            return getString(Settings.Secure.SELECTED_SPELL_CHECKER, "", userId);
-        }
-
-        public int getSelectedSpellCheckerSubtype(final int defaultValue, @UserIdInt int userId) {
-            return getInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, defaultValue, userId);
-        }
-
-        public boolean isSpellCheckerEnabled(@UserIdInt int userId) {
-            return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true, userId);
-        }
-    }
 }