1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.inputmethodservice; 18 19 import android.annotation.MainThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.res.Configuration; 23 import android.content.res.Resources; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.util.Preconditions; 27 28 /** 29 * Helper class that takes care of Configuration change behavior of {@link InputMethodService}. 30 * Note: this class is public for testing only. Never call any of it's methods for development 31 * of IMEs. 32 * @hide 33 */ 34 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 35 public final class ImsConfigurationTracker { 36 37 /** 38 * A constant value that represents {@link Configuration} has changed from the last time 39 * {@link InputMethodService#onConfigurationChanged(Configuration)} was called. 40 */ 41 private static final int CONFIG_CHANGED = -1; 42 43 @Nullable 44 private Configuration mLastKnownConfig = null; 45 private int mHandledConfigChanges = 0; 46 private boolean mInitialized = false; 47 48 /** 49 * Called from {@link InputMethodService.InputMethodImpl 50 * #initializeInternal(IBinder, int, IInputMethodPrivilegedOperations, int)} ()} 51 * @param handledConfigChanges Configuration changes declared handled by IME 52 * {@link android.R.styleable#InputMethod_configChanges}. 53 */ 54 @MainThread onInitialize(int handledConfigChanges)55 public void onInitialize(int handledConfigChanges) { 56 Preconditions.checkState(!mInitialized, "onInitialize can be called only once."); 57 mInitialized = true; 58 mHandledConfigChanges = handledConfigChanges; 59 } 60 61 /** 62 * Called from {@link InputMethodService.InputMethodImpl#onBindInput()} 63 */ 64 @MainThread onBindInput(@ullable Resources resources)65 public void onBindInput(@Nullable Resources resources) { 66 Preconditions.checkState(mInitialized, 67 "onBindInput can be called only after onInitialize()."); 68 if (mLastKnownConfig == null && resources != null) { 69 mLastKnownConfig = new Configuration(resources.getConfiguration()); 70 } 71 } 72 73 /** 74 * Dynamically set handled configChanges. 75 * Note: this method is public for testing only. 76 */ setHandledConfigChanges(int configChanges)77 public void setHandledConfigChanges(int configChanges) { 78 mHandledConfigChanges = configChanges; 79 } 80 81 /** 82 * Called from {@link InputMethodService.InputMethodImpl#onConfigurationChanged(Configuration)}} 83 */ 84 @MainThread onConfigurationChanged(@onNull Configuration newConfig, @NonNull Runnable resetStateForNewConfigurationRunner)85 public void onConfigurationChanged(@NonNull Configuration newConfig, 86 @NonNull Runnable resetStateForNewConfigurationRunner) { 87 if (!mInitialized) { 88 return; 89 } 90 final int diff = mLastKnownConfig != null 91 ? mLastKnownConfig.diffPublicOnly(newConfig) : CONFIG_CHANGED; 92 // If the new config is the same as the config this Service is already running with, 93 // then don't bother calling resetStateForNewConfiguration. 94 final int unhandledDiff = (diff & ~mHandledConfigChanges); 95 if (unhandledDiff != 0) { 96 resetStateForNewConfigurationRunner.run(); 97 } 98 if (diff != 0) { 99 mLastKnownConfig = new Configuration(newConfig); 100 } 101 } 102 } 103