Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit db19711e authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Avoid resource I/O when holding ImfLock

Loading InputMethodInfo from APK resources is full of disk I/O, which
can result in ANR as seen in Bug 340221861. This CL aims to avoid such
disk I/O when holding ImfLock.

With this CL

  MyPackageMonitor#onFinishPackageChangesInternal()

updates IME list as follows.

 1. Create List<InputMethodInfo> without additional subtypes
 2. Acquire ImfLock.class
 3. Finalize additional subtypes
 4. Finalize List<InputMethodInfo>
 5. Release ImfLock.class

The key idea is the fact that List<InputMethodInfo> can be
instantiated without ImfLock as long as we can ignore additional
subtypes at the step 1. Creating the final List<InputMethodInfo> at
the step 4 is a pure computatoin, which does not require any disk I/O.

There must be no user observable behavior change.

Fix: 340221861
Test: presubmit
Change-Id: I263cd49dd4d64b64136acc3dad469f83a862ce97
parent 4e7848cc
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
@@ -431,6 +432,14 @@ public final class InputMethodInfo implements Parcelable {
     * @hide
     */
    public InputMethodInfo(InputMethodInfo source) {
        this(source, Collections.emptyList());
    }

    /**
     * @hide
     */
    public InputMethodInfo(@NonNull InputMethodInfo source,
            @NonNull List<InputMethodSubtype> additionalSubtypes) {
        mId = source.mId;
        mSettingsActivityName = source.mSettingsActivityName;
        mLanguageSettingsActivityName = source.mLanguageSettingsActivityName;
@@ -445,7 +454,19 @@ public final class InputMethodInfo implements Parcelable {
        mIsVrOnly = source.mIsVrOnly;
        mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly;
        mService = source.mService;
        if (additionalSubtypes.isEmpty()) {
            mSubtypes = source.mSubtypes;
        } else {
            final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList();
            final int additionalSubtypeCount = additionalSubtypes.size();
            for (int i = 0; i < additionalSubtypeCount; ++i) {
                final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i);
                if (!subtypes.contains(additionalSubtype)) {
                    subtypes.add(additionalSubtype);
                }
            }
            mSubtypes = new InputMethodSubtypeArray(subtypes);
        }
        mHandledConfigChanges = source.mHandledConfigChanges;
        mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
        mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting;
+13 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.util.Slog;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -162,6 +163,18 @@ public class InputMethodSubtypeArray {
        return instance[index];
    }

    /**
     * @return A list of {@link InputMethodInfo} copied from this array.
     */
    @NonNull
    public ArrayList<InputMethodSubtype> toList() {
        final ArrayList<InputMethodSubtype> list = new ArrayList<>(mCount);
        for (int i = 0; i < mCount; ++i) {
            list.add(get(i));
        }
        return list;
    }

    /**
     * Return the number of {@link InputMethodSubtype} objects.
     */
+13 −4
Original line number Diff line number Diff line
@@ -1024,8 +1024,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        }

        private void onFinishPackageChangesInternal() {
            synchronized (ImfLock.class) {
            final int userId = getChangingUserId();

            // Instantiating InputMethodInfo requires disk I/O.
            // Do them before acquiring the lock to minimize the chances of ANR (b/340221861).
            final var newMethodMapWithoutAdditionalSubtypes =
                    queryInputMethodServicesInternal(mContext, userId,
                            AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO)
                            .getMethodMap();

            synchronized (ImfLock.class) {
                final boolean isCurrentUser = (userId == mCurrentUserId);
                final AdditionalSubtypeMap additionalSubtypeMap =
                        AdditionalSubtypeMapRepository.get(userId);
@@ -1077,9 +1085,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
                        && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
                    return;
                }

                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
                        userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
                final var newMethodMap = newMethodMapWithoutAdditionalSubtypes
                        .applyAdditionalSubtypes(newAdditionalSubtypeMap);
                final InputMethodSettings newSettings =
                        InputMethodSettings.create(newMethodMap, userId);
                InputMethodSettingsRepository.put(userId, newSettings);
                if (!isCurrentUser) {
                    return;
+24 −0
Original line number Diff line number Diff line
@@ -75,4 +75,28 @@ final class InputMethodMap {
    int size() {
        return mMap.size();
    }

    @AnyThread
    @NonNull
    public InputMethodMap applyAdditionalSubtypes(
            @NonNull AdditionalSubtypeMap additionalSubtypeMap) {
        if (additionalSubtypeMap.isEmpty()) {
            return this;
        }
        final int size = size();
        final ArrayMap<String, InputMethodInfo> newMethodMap = new ArrayMap<>(size);
        boolean updated = false;
        for (int i = 0; i < size; ++i) {
            final var imi = valueAt(i);
            final var imeId = imi.getId();
            final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId);
            if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) {
                newMethodMap.put(imi.getId(), imi);
            } else {
                newMethodMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes));
                updated = true;
            }
        }
        return updated ? InputMethodMap.of(newMethodMap) : this;
    }
}