Loading services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java +161 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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. */ Loading @@ -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) { Loading @@ -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)) { Loading @@ -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); Loading services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +4 −0 Original line number Diff line number Diff line Loading @@ -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"); } } } Loading Loading
services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java +161 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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. */ Loading @@ -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) { Loading @@ -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)) { Loading @@ -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); Loading
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +4 −0 Original line number Diff line number Diff line Loading @@ -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"); } } } Loading