From 49ed14017697f8f19b8d49d55462713396a0588e Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 7 Mar 2016 19:12:54 -0800 Subject: [PATCH 1/2] Add copy-on-write mode to TextServicesSettings. This ports my previous CL for InputMethodManagerService [1] to TextServicesSettings (TSMS). This is a preparation for File-Based Encryption (FBE) support in TSMS. With this CL, we start switching TextServicesSettings from copy-on-write mode to real mode and start rebuilding the list of available spell checkers when the system becomes ready. Anything else should be the same. See the previous commit message [1] for details. [1]: I9c6f9bb3d51174198e5f73588637f87ea0d90e11 68645a638ad1bfb734b2b0f56b17fe206bb891c5 Bug: 27456430 Change-Id: Ie3d61458648df469abe149b7aaad8087c531a675 --- .../server/TextServicesManagerService.java | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java index 8bcb7adc1511a..fa0f25aed564c 100644 --- a/services/core/java/com/android/server/TextServicesManagerService.java +++ b/services/core/java/com/android/server/TextServicesManagerService.java @@ -115,6 +115,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { synchronized (mSpellCheckerMap) { if (!mSystemReady) { mSystemReady = true; + switchUserLocked(mSettings.getCurrentUserId()); } } } @@ -142,14 +143,18 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } mMonitor = new TextServicesMonitor(); mMonitor.register(context, null, true); - mSettings = new TextServicesSettings(context.getContentResolver(), userId); + // If the system is not ready, then we use copy-on-write settings. + final boolean useCopyOnWriteSettings = !mSystemReady; + mSettings = new TextServicesSettings(context.getContentResolver(), userId, + useCopyOnWriteSettings); // "switchUserLocked" initializes the states for the foreground user switchUserLocked(userId); } private void switchUserLocked(@UserIdInt int userId) { - mSettings.setCurrentUserId(userId); + final boolean useCopyOnWriteSettings = !mSystemReady; + mSettings.switchCurrentUser(userId, useCopyOnWriteSettings); updateCurrentProfileIds(); unbindServiceLocked(); buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings); @@ -1023,33 +1028,70 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private int[] mCurrentProfileIds = new int[0]; private Object mLock = new Object(); - public TextServicesSettings(ContentResolver resolver, @UserIdInt int userId) { + /** + * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. + */ + private final HashMap mCopyOnWriteDataStore = new HashMap<>(); + private boolean mCopyOnWrite = false; + + public TextServicesSettings(ContentResolver resolver, @UserIdInt int userId, + boolean copyOnWrite) { mResolver = resolver; - mCurrentUserId = userId; + switchCurrentUser(userId, copyOnWrite); } - public void setCurrentUserId(@UserIdInt int userId) { + /** + * Must be called when the current user is changed. + * + * @param userId The user ID. + * @param copyOnWrite If {@code true}, for each settings key + * (e.g. {@link Settings.Secure#SELECTED_SPELL_CHECKER}) we use the actual settings on the + * {@link Settings.Secure} until we do the first write operation. + */ + public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { if (DBG) { Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId + ", new ime = " + getSelectedSpellChecker()); } + if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) { + mCopyOnWriteDataStore.clear(); + // TODO: mCurrentProfileIds should be cleared here. + } // TSMS settings are kept per user, so keep track of current user mCurrentUserId = userId; + mCopyOnWrite = copyOnWrite; + // TODO: mCurrentProfileIds should be updated here. } private void putString(final String key, final String str) { - Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + if (mCopyOnWrite) { + mCopyOnWriteDataStore.put(key, str); + } else { + Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + } } private String getString(final String key) { + if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { + final String result = mCopyOnWriteDataStore.get(key); + return result != null ? result : ""; + } return Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId); } private void putInt(final String key, final int value) { - Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + if (mCopyOnWrite) { + mCopyOnWriteDataStore.put(key, String.valueOf(value)); + } else { + Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + } } private int getInt(final String key, final int defaultValue) { + if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { + final String result = mCopyOnWriteDataStore.get(key); + return result != null ? Integer.valueOf(result) : 0; + } return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); } @@ -1109,6 +1151,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { public void dumpLocked(final PrintWriter pw, final String prefix) { pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds)); + pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite); } } From f0f168066335ac1ec103e575d693a957da714c4e Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 8 Mar 2016 16:04:58 -0800 Subject: [PATCH 2/2] Reset TSMS when the device is unlocked. This ports my previous CL for InputMethodManagerService [1] to TextServicesSettings (TSMS). With File-Based Encryption (FBE), now we have yet another runteime event to reset TSMS state in order to keep the list of available spell checkers updated. See my previous commit message [1] for details about the idea. Rebuild spell checker list when the system becomes ready. [1]: Ifa2225070bf8223f8964cf063c86889e312c5e9a 0e367d08aab2c1cb2d0fb9cbf1e8a6d09d1ac523 Bug: 27456430 Change-Id: I2b5a6989a99fd18b3bf109f5cda6270ccadaaf6d --- .../server/TextServicesManagerService.java | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java index fa0f25aed564c..306e9331671ec 100644 --- a/services/core/java/com/android/server/TextServicesManagerService.java +++ b/services/core/java/com/android/server/TextServicesManagerService.java @@ -27,6 +27,7 @@ import com.android.internal.textservice.ITextServicesSessionListener; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManagerNative; import android.app.AppGlobals; @@ -80,6 +81,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private final ArrayList mSpellCheckerList = new ArrayList<>(); private final HashMap mSpellCheckerBindGroups = new HashMap<>(); private final TextServicesSettings mSettings; + @NonNull + private final UserManager mUserManager; public static final class Lifecycle extends SystemService { private TextServicesManagerService mService; @@ -109,20 +112,37 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mService.systemRunning(); } } + + @Override + public void onUnlockUser(@UserIdInt int userHandle) { + // Called on the system server's main looper thread. + // TODO: Dispatch this to a worker thread as needed. + mService.onUnlockUser(userHandle); + } } void systemRunning() { synchronized (mSpellCheckerMap) { if (!mSystemReady) { mSystemReady = true; - switchUserLocked(mSettings.getCurrentUserId()); + resetInternalState(mSettings.getCurrentUserId()); } } } void onSwitchUser(@UserIdInt int userId) { synchronized (mSpellCheckerMap) { - switchUserLocked(userId); + resetInternalState(userId); + } + } + + void onUnlockUser(@UserIdInt int userId) { + synchronized(mSpellCheckerMap) { + final int currentUserId = mSettings.getCurrentUserId(); + if (userId != currentUserId) { + return; + } + resetInternalState(currentUserId); } } @@ -130,6 +150,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mSystemReady = false; mContext = context; + mUserManager = mContext.getSystemService(UserManager.class); + final IntentFilter broadcastFilter = new IntentFilter(); broadcastFilter.addAction(Intent.ACTION_USER_ADDED); broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); @@ -143,17 +165,18 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } mMonitor = new TextServicesMonitor(); mMonitor.register(context, null, true); - // If the system is not ready, then we use copy-on-write settings. - final boolean useCopyOnWriteSettings = !mSystemReady; + final boolean useCopyOnWriteSettings = + !mSystemReady || !mUserManager.isUserUnlocked(userId); mSettings = new TextServicesSettings(context.getContentResolver(), userId, useCopyOnWriteSettings); - // "switchUserLocked" initializes the states for the foreground user - switchUserLocked(userId); + // "resetInternalState" initializes the states for the foreground user + resetInternalState(userId); } - private void switchUserLocked(@UserIdInt int userId) { - final boolean useCopyOnWriteSettings = !mSystemReady; + private void resetInternalState(@UserIdInt int userId) { + final boolean useCopyOnWriteSettings = + !mSystemReady || !mUserManager.isUserUnlocked(userId); mSettings.switchCurrentUser(userId, useCopyOnWriteSettings); updateCurrentProfileIds(); unbindServiceLocked(); @@ -171,8 +194,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } void updateCurrentProfileIds() { - List profiles = - UserManager.get(mContext).getProfiles(mSettings.getCurrentUserId()); + final List profiles = mUserManager.getProfiles(mSettings.getCurrentUserId()); int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null for (int i = 0; i < currentProfileIds.length; i++) { currentProfileIds[i] = profiles.get(i).id; @@ -237,6 +259,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { list.clear(); map.clear(); 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 services = pm.queryIntentServicesAsUser( new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA, settings.getCurrentUserId());