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

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

Introduce SystemLocaleWrapper

As part of preparations to support concurrent multi-user in IMMS, this
commit introduces

  SystemLocaleWrapper,

which takes care of system locale access in general around IMMS.

With this CL, you can get LocaleList for a given user with

  SystemLocaleWrapper.get(userId)

rather than

  mRes.getConfiguration().getLocales().

Bug: 309837937
Test: presubmit
Change-Id: Iafbba77f4069121f2efd5ac6a2fb23499dd4503d
parent ca2a2785
Loading
Loading
Loading
Loading
+10 −15
Original line number Diff line number Diff line
@@ -741,7 +741,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
     */
    int mImeWindowVis;

    private LocaleList mLastSystemLocales;
    private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
    private final String mSlotIme;

@@ -1199,9 +1198,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            if (Intent.ACTION_USER_ADDED.equals(action)
                    || Intent.ACTION_USER_REMOVED.equals(action)) {
                updateCurrentProfileIds();
                return;
            } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
                onActionLocaleChanged();
            } else {
                Slog.w(TAG, "Unexpected intent " + intent);
            }
@@ -1240,20 +1236,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
     *
     * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
     * the users. We should ignore this event if this is about any background user's locale.</p>
     *
     * <p>Caution: This method must not be called when system is not ready.</p>
     */
    void onActionLocaleChanged() {
    void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) {
        if (DEBUG) {
            Slog.d(TAG, "onActionLocaleChanged prev=" + prevLocales + " new=" + newLocales);
        }
        synchronized (ImfLock.class) {
            final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
            if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
            if (!mSystemReady) {
                return;
            }
            buildInputMethodListLocked(true);
            // If the locale is changed, needs to reset the default ime
            resetDefaultImeLocked(mContext);
            updateFromSettingsLocked(true);
            mLastSystemLocales = possibleNewLocale;
        }
    }

@@ -1681,6 +1676,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                                true /* allowIo */);
        thread.start();
        mHandler = Handler.createAsync(thread.getLooper(), this);
        SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
        mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
                ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
        // Note: SettingsObserver doesn't register observers in its constructor.
@@ -1838,7 +1834,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        // Even in such cases, IMMS works fine because it will find the most applicable
        // IME for that user.
        final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
        mLastSystemLocales = mRes.getConfiguration().getLocales();

        // The mSystemReady flag is set during boot phase,
        // and user switch would not happen at that time.
@@ -1890,7 +1885,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            }
            if (!mSystemReady) {
                mSystemReady = true;
                mLastSystemLocales = mRes.getConfiguration().getLocales();
                final int currentUserId = mSettings.getCurrentUserId();
                mSettings.switchCurrentUser(currentUserId,
                        !mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId));
@@ -1930,7 +1924,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                final IntentFilter broadcastFilterForSystemUser = new IntentFilter();
                broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED);
                broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED);
                broadcastFilterForSystemUser.addAction(Intent.ACTION_LOCALE_CHANGED);
                mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(),
                        broadcastFilterForSystemUser);

@@ -4070,7 +4063,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                            && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
                        locale = mCurrentSubtype.getLocale();
                    } else {
                        locale = mRes.getConfiguration().locale.toString();
                        locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()).get(0)
                                .toString();
                    }
                    for (int i = 0; i < enabledCount; ++i) {
                        final InputMethodInfo imi = enabled.get(i);
@@ -5425,7 +5419,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
                    final String locale = mRes.getConfiguration().locale.toString();
                    final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId())
                            .get(0).toString();
                    mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
                            explicitlyOrImplicitlyEnabledSubtypes,
                            SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
+3 −5
Original line number Diff line number Diff line
@@ -219,7 +219,6 @@ final class InputMethodUtils {

        @NonNull
        private Context mUserAwareContext;
        private Resources mRes;
        private ContentResolver mResolver;
        private final ArrayMap<String, InputMethodInfo> mMethodMap;

@@ -282,7 +281,6 @@ final class InputMethodUtils {
            mUserAwareContext = context.getUserId() == userId
                    ? context
                    : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
            mRes = mUserAwareContext.getResources();
            mResolver = mUserAwareContext.getContentResolver();
        }

@@ -399,7 +397,7 @@ final class InputMethodUtils {
                    getEnabledInputMethodSubtypeListLocked(imi);
            if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
                enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
                        mRes.getConfiguration().getLocales(), imi);
                        SystemLocaleWrapper.get(mCurrentUserId), imi);
            }
            return InputMethodSubtype.sort(imi, enabledSubtypes);
        }
@@ -648,7 +646,7 @@ final class InputMethodUtils {

        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
            final LocaleList localeList = mRes.getConfiguration().getLocales();
            final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
                if (enabledIme.first.equals(imeId)) {
                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
@@ -851,7 +849,7 @@ final class InputMethodUtils {
            if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                return explicitlyOrImplicitlyEnabledSubtypes.get(0);
            }
            final String locale = mRes.getConfiguration().locale.toString();
            final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
            final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
                    explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
                    locale, true);
+108 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.LocaleList;

import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A set of thread-safe utility methods for the system locals.
 */
final class SystemLocaleWrapper {
    /**
     * Not intended to be instantiated.
     */
    private SystemLocaleWrapper() {
    }

    private static final AtomicReference<LocaleList> sSystemLocale =
            new AtomicReference<>(new LocaleList(Locale.getDefault()));

    /**
     * Returns {@link LocaleList} for the specified user.
     *
     * <p>Note: If you call this method twice, it is possible that the second value is different
     * from the first value. The caller is responsible for taking care of such cases.</p>
     *
     * @param userId the ID of the user to query about.
     * @return {@link LocaleList} associated with the user.
     */
    @AnyThread
    @NonNull
    static LocaleList get(@UserIdInt int userId) {
        // Currently system locale is not per-user.
        // TODO(b/30119489): Make this per-user.
        return sSystemLocale.get();
    }

    /**
     * Callback for the locale change event. When this gets filed, {@link #get(int)} is already
     * updated to return the new value.
     */
    interface Callback {
        void onLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales);
    }

    /**
     * Called when {@link InputMethodManagerService} is about to start.
     *
     * @param context {@link Context} to be used.
     * @param callback {@link Callback} for the locale change events.
     */
    @AnyThread
    static void onStart(@NonNull Context context, @NonNull Callback callback,
            @NonNull Handler handler) {
        sSystemLocale.set(context.getResources().getConfiguration().getLocales());

        context.registerReceiver(new LocaleChangeListener(context, callback),
                new IntentFilter(Intent.ACTION_LOCALE_CHANGED), null, handler);
    }

    private static final class LocaleChangeListener extends BroadcastReceiver {
        @NonNull
        private final Context mContext;
        @NonNull
        private final Callback mCallback;
        LocaleChangeListener(@NonNull Context context, @NonNull Callback callback) {
            mContext = context;
            mCallback = callback;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (!Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
                return;
            }
            final LocaleList newLocales = mContext.getResources().getConfiguration().getLocales();
            final LocaleList prevLocales = sSystemLocale.getAndSet(newLocales);
            if (!Objects.equals(newLocales, prevLocales)) {
                mCallback.onLocaleChanged(prevLocales, newLocales);
            }
        }
    }
}