diff options
| -rw-r--r-- | services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java | 164 | ||||
| -rw-r--r-- | services/core/java/com/android/server/inputmethod/InputMethodManagerService.java | 4 |
2 files changed, 165 insertions, 3 deletions
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java index dd6433d98553..82ecb4acb197 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java @@ -16,12 +16,16 @@ package com.android.server.inputmethod; +import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.annotation.WorkerThread; import android.content.Context; import android.content.pm.UserInfo; import android.os.Handler; +import android.os.Process; +import android.util.IntArray; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -29,6 +33,10 @@ import com.android.internal.inputmethod.DirectBootAwareness; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; +import java.util.ArrayList; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + /** * Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype} * persistent storages. @@ -38,6 +46,152 @@ final class AdditionalSubtypeMapRepository { @NonNull private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>(); + record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap, + @NonNull InputMethodMap inputMethodMap) { + } + + static final class SingleThreadedBackgroundWriter { + /** + * A {@link ReentrantLock} used to guard {@link #mPendingTasks} and {@link #mRemovedUsers}. + */ + @NonNull + private final ReentrantLock mLock = new ReentrantLock(); + /** + * A {@link Condition} associated with {@link #mLock} for producer to unblock consumer. + */ + @NonNull + private final Condition mLockNotifier = mLock.newCondition(); + + @GuardedBy("mLock") + @NonNull + private final SparseArray<WriteTask> mPendingTasks = new SparseArray<>(); + + @GuardedBy("mLock") + private final IntArray mRemovedUsers = new IntArray(); + + @NonNull + private final Thread mWriterThread = new Thread("android.ime.as") { + + /** + * Waits until the next data has come then return the result after filtering out any + * already removed users. + * + * @return A list of {@link WriteTask} to be written into persistent storage + */ + @WorkerThread + private ArrayList<WriteTask> fetchNextTasks() { + final SparseArray<WriteTask> tasks; + final IntArray removedUsers; + mLock.lock(); + try { + while (true) { + if (mPendingTasks.size() != 0) { + tasks = mPendingTasks.clone(); + mPendingTasks.clear(); + if (mRemovedUsers.size() == 0) { + removedUsers = null; + } else { + removedUsers = mRemovedUsers.clone(); + } + break; + } + mLockNotifier.awaitUninterruptibly(); + } + } finally { + mLock.unlock(); + } + final int size = tasks.size(); + final ArrayList<WriteTask> result = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + final int userId = tasks.keyAt(i); + if (removedUsers != null && removedUsers.contains(userId)) { + continue; + } + result.add(tasks.valueAt(i)); + } + return result; + } + + @WorkerThread + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + while (true) { + final ArrayList<WriteTask> tasks = fetchNextTasks(); + tasks.forEach(task -> AdditionalSubtypeUtils.save( + task.subtypeMap, task.inputMethodMap, task.userId)); + } + } + }; + + /** + * Schedules a write operation + * + * @param userId the target user ID of this operation + * @param subtypeMap {@link AdditionalSubtypeMap} to be saved + * @param inputMethodMap {@link InputMethodMap} to be used to filter our {@code subtypeMap} + */ + @AnyThread + void scheduleWriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap, + @NonNull InputMethodMap inputMethodMap) { + final var task = new WriteTask(userId, subtypeMap, inputMethodMap); + mLock.lock(); + try { + if (mRemovedUsers.contains(userId)) { + return; + } + mPendingTasks.put(userId, task); + mLockNotifier.signalAll(); + } finally { + mLock.unlock(); + } + } + + /** + * Called back when a user is being created. + * + * @param userId The user ID to be created + */ + @AnyThread + void onUserCreated(@UserIdInt int userId) { + mLock.lock(); + try { + for (int i = mRemovedUsers.size() - 1; i >= 0; --i) { + if (mRemovedUsers.get(i) == userId) { + mRemovedUsers.remove(i); + } + } + } finally { + mLock.unlock(); + } + } + + /** + * Called back when a user is being removed. Any pending task will be effectively canceled + * if the user is removed before the task is fulfilled. + * + * @param userId The user ID to be removed + */ + @AnyThread + void onUserRemoved(@UserIdInt int userId) { + mLock.lock(); + try { + mRemovedUsers.add(userId); + mPendingTasks.remove(userId); + } finally { + mLock.unlock(); + } + } + + void startThread() { + mWriterThread.start(); + } + } + + private static final SingleThreadedBackgroundWriter sWriter = + new SingleThreadedBackgroundWriter(); + /** * Not intended to be instantiated. */ @@ -64,9 +218,11 @@ final class AdditionalSubtypeMapRepository { return; } sPerUserMap.put(userId, map); - // TODO: Offload this to a background thread. - // TODO: Skip if the previous data is exactly the same as new one. - AdditionalSubtypeUtils.save(map, inputMethodMap, userId); + sWriter.scheduleWriteTask(userId, map, inputMethodMap); + } + + static void startWriterThread() { + sWriter.startThread(); } static void initialize(@NonNull Handler handler, @NonNull Context context) { @@ -78,6 +234,7 @@ final class AdditionalSubtypeMapRepository { @Override public void onUserCreated(UserInfo user, @Nullable Object token) { final int userId = user.id; + sWriter.onUserCreated(userId); handler.post(() -> { synchronized (ImfLock.class) { if (!sPerUserMap.contains(userId)) { @@ -99,6 +256,7 @@ final class AdditionalSubtypeMapRepository { @Override public void onUserRemoved(UserInfo user) { final int userId = user.id; + sWriter.onUserRemoved(userId); handler.post(() -> { synchronized (ImfLock.class) { sPerUserMap.remove(userId); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 5843d72f346a..7513c40a1f90 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1547,6 +1547,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), newSettings.getEnabledInputMethodList()); + + final var unused = SystemServerInitThreadPool.submit( + AdditionalSubtypeMapRepository::startWriterThread, + "Start AdditionalSubtypeMapRepository's writer thread"); } } } |