Pipe IME state into insets (IME transitions 3/n)
Add a IME state changes callback that pipes IME state into the Inset consumer. Bug: 118599175 Bug: 118118435 Test: atest InsetControllerTest Test: atest InsetSourceConsumerTest Test: atest ImeInsetsSourceConsumerTest Change-Id: Id878226418e19cdf0499a0094f1d5c47fea33125
This commit is contained in:
committed by
Jorim Jaggi
parent
5cccc2bd8e
commit
2cbcd7ffbf
@@ -597,6 +597,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
Log.v(TAG, "Making IME window invisible");
|
||||
}
|
||||
setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
|
||||
applyVisibilityInInsetsConsumer(false /* setVisible */);
|
||||
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
|
||||
} else {
|
||||
mShowInputFlags = 0;
|
||||
@@ -625,10 +626,10 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
? mDecorViewVisible && mWindowVisible : isInputViewShown();
|
||||
if (dispatchOnShowInputRequested(flags, false)) {
|
||||
if (mIsPreRendered) {
|
||||
// TODO: notify visibility to insets consumer.
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "Making IME window visible");
|
||||
}
|
||||
applyVisibilityInInsetsConsumer(true /* setVisible */);
|
||||
onPreRenderedWindowVisibilityChanged(true /* setVisible */);
|
||||
} else {
|
||||
showWindow(true);
|
||||
@@ -1887,10 +1888,23 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
|
||||
mWindow.show();
|
||||
}
|
||||
maybeNotifyPreRendered();
|
||||
mDecorViewWasVisible = true;
|
||||
mInShowWindow = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify {@link android.view.ImeInsetsSourceConsumer} if IME has been pre-rendered
|
||||
* for current EditorInfo, when pre-rendering is enabled.
|
||||
*/
|
||||
private void maybeNotifyPreRendered() {
|
||||
if (!mCanPreRender || !mIsPreRendered) {
|
||||
return;
|
||||
}
|
||||
mPrivOps.reportPreRendered(getCurrentInputEditorInfo());
|
||||
}
|
||||
|
||||
|
||||
private boolean prepareWindow(boolean showInput) {
|
||||
boolean doShowInput = false;
|
||||
mDecorViewVisible = true;
|
||||
@@ -1942,6 +1956,18 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when
|
||||
* pre-rendering is enabled.
|
||||
* @param setVisible {@code true} to make it visible, false to hide it.
|
||||
*/
|
||||
private void applyVisibilityInInsetsConsumer(boolean setVisible) {
|
||||
if (!mIsPreRendered) {
|
||||
return;
|
||||
}
|
||||
mPrivOps.applyImeVisibility(setVisible);
|
||||
}
|
||||
|
||||
private void finishViews(boolean finishingInput) {
|
||||
if (mInputViewStarted) {
|
||||
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
|
||||
@@ -2081,6 +2107,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
// When IME is not pre-rendered, this will actually show the IME.
|
||||
if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
|
||||
mWindow.show();
|
||||
maybeNotifyPreRendered();
|
||||
mDecorViewWasVisible = true;
|
||||
mInShowWindow = false;
|
||||
} else {
|
||||
|
||||
157
core/java/android/view/ImeInsetsSourceConsumer.java
Normal file
157
core/java/android/view/ImeInsetsSourceConsumer.java
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.view;
|
||||
|
||||
import static android.view.InsetsState.TYPE_IME;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.text.TextUtils;
|
||||
import android.view.SurfaceControl.Transaction;
|
||||
import android.view.WindowInsets.Type;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Controls the visibility and animations of IME window insets source.
|
||||
* @hide
|
||||
*/
|
||||
public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
||||
private EditorInfo mFocusedEditor;
|
||||
private EditorInfo mPreRenderedEditor;
|
||||
/**
|
||||
* Determines if IME would be shown next time IME is pre-rendered for currently focused
|
||||
* editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}.
|
||||
*/
|
||||
private boolean mShowOnNextImeRender;
|
||||
private boolean mHasWindowFocus;
|
||||
|
||||
public ImeInsetsSourceConsumer(
|
||||
InsetsState state, Supplier<Transaction> transactionSupplier,
|
||||
InsetsController controller) {
|
||||
super(TYPE_IME, state, transactionSupplier, controller);
|
||||
}
|
||||
|
||||
public void onPreRendered(EditorInfo info) {
|
||||
mPreRenderedEditor = info;
|
||||
if (mShowOnNextImeRender) {
|
||||
mShowOnNextImeRender = false;
|
||||
if (isServedEditorRendered()) {
|
||||
applyImeVisibility(true /* setVisible */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onServedEditorChanged(EditorInfo info) {
|
||||
if (isDummyOrEmptyEditor(info)) {
|
||||
mShowOnNextImeRender = false;
|
||||
}
|
||||
mFocusedEditor = info;
|
||||
}
|
||||
|
||||
public void applyImeVisibility(boolean setVisible) {
|
||||
if (!mHasWindowFocus) {
|
||||
// App window doesn't have focus, any visibility changes would be no-op.
|
||||
return;
|
||||
}
|
||||
|
||||
if (setVisible) {
|
||||
mController.show(Type.IME);
|
||||
} else {
|
||||
mController.hide(Type.IME);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusGained() {
|
||||
mHasWindowFocus = true;
|
||||
getImm().registerImeConsumer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusLost() {
|
||||
mHasWindowFocus = false;
|
||||
}
|
||||
|
||||
private boolean isDummyOrEmptyEditor(EditorInfo info) {
|
||||
// TODO(b/123044812): Handle dummy input gracefully in IME Insets API
|
||||
return info == null || (info.fieldId <= 0 && info.inputType <= 0);
|
||||
}
|
||||
|
||||
private boolean isServedEditorRendered() {
|
||||
if (mFocusedEditor == null || mPreRenderedEditor == null
|
||||
|| isDummyOrEmptyEditor(mFocusedEditor)
|
||||
|| isDummyOrEmptyEditor(mPreRenderedEditor)) {
|
||||
// No view is focused or ready.
|
||||
return false;
|
||||
}
|
||||
return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) {
|
||||
// We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change
|
||||
// IME views.
|
||||
boolean areOptionsSimilar =
|
||||
info1.imeOptions == info2.imeOptions
|
||||
&& info1.inputType == info2.inputType
|
||||
&& TextUtils.equals(info1.packageName, info2.packageName);
|
||||
areOptionsSimilar &= info1.privateImeOptions != null
|
||||
? info1.privateImeOptions.equals(info2.privateImeOptions) : true;
|
||||
|
||||
if (!areOptionsSimilar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare bundle extras.
|
||||
if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) {
|
||||
return true;
|
||||
}
|
||||
if ((info1.extras == null && info2.extras != null)
|
||||
|| (info1.extras == null && info2.extras != null)) {
|
||||
return false;
|
||||
}
|
||||
if (info1.extras.hashCode() == info2.extras.hashCode()
|
||||
|| info1.extras.equals(info1)) {
|
||||
return true;
|
||||
}
|
||||
if (info1.extras.size() != info2.extras.size()) {
|
||||
return false;
|
||||
}
|
||||
if (info1.extras.toString().equals(info2.extras.toString())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare bytes
|
||||
Parcel parcel1 = Parcel.obtain();
|
||||
info1.extras.writeToParcel(parcel1, 0);
|
||||
parcel1.setDataPosition(0);
|
||||
Parcel parcel2 = Parcel.obtain();
|
||||
info2.extras.writeToParcel(parcel2, 0);
|
||||
parcel2.setDataPosition(0);
|
||||
|
||||
return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
|
||||
}
|
||||
|
||||
private InputMethodManager getImm() {
|
||||
return mController.getViewRoot().mDisplayContext.getSystemService(InputMethodManager.class);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.view;
|
||||
|
||||
import static android.view.InsetsState.TYPE_IME;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
@@ -262,7 +264,7 @@ public class InsetsController implements WindowInsetsController {
|
||||
if (controller != null) {
|
||||
return controller;
|
||||
}
|
||||
controller = new InsetsSourceConsumer(type, mState, Transaction::new, this);
|
||||
controller = createConsumerOfType(type);
|
||||
mSourceConsumers.put(type, controller);
|
||||
return controller;
|
||||
}
|
||||
@@ -273,6 +275,32 @@ public class InsetsController implements WindowInsetsController {
|
||||
sendStateToWindowManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when current window gains focus.
|
||||
*/
|
||||
public void onWindowFocusGained() {
|
||||
getSourceConsumer(TYPE_IME).onWindowFocusGained();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when current window loses focus.
|
||||
*/
|
||||
public void onWindowFocusLost() {
|
||||
getSourceConsumer(TYPE_IME).onWindowFocusLost();
|
||||
}
|
||||
|
||||
ViewRootImpl getViewRoot() {
|
||||
return mViewRoot;
|
||||
}
|
||||
|
||||
private InsetsSourceConsumer createConsumerOfType(int type) {
|
||||
if (type == TYPE_IME) {
|
||||
return new ImeInsetsSourceConsumer(mState, Transaction::new, this);
|
||||
} else {
|
||||
return new InsetsSourceConsumer(type, mState, Transaction::new, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the local visibility state back to window manager.
|
||||
*/
|
||||
|
||||
@@ -30,12 +30,12 @@ import java.util.function.Supplier;
|
||||
*/
|
||||
public class InsetsSourceConsumer {
|
||||
|
||||
protected final InsetsController mController;
|
||||
protected boolean mVisible;
|
||||
private final Supplier<Transaction> mTransactionSupplier;
|
||||
private final @InternalInsetType int mType;
|
||||
private final InsetsState mState;
|
||||
private final InsetsController mController;
|
||||
private @Nullable InsetsSourceControl mSourceControl;
|
||||
private boolean mVisible;
|
||||
|
||||
public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state,
|
||||
Supplier<Transaction> transactionSupplier, InsetsController controller) {
|
||||
@@ -76,6 +76,16 @@ public class InsetsSourceConsumer {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when current window gains focus
|
||||
*/
|
||||
public void onWindowFocusGained() {}
|
||||
|
||||
/**
|
||||
* Called when current window loses focus.
|
||||
*/
|
||||
public void onWindowFocusLost() {}
|
||||
|
||||
boolean applyLocalVisibilityOverride() {
|
||||
|
||||
// If we don't have control, we are not able to change the visibility.
|
||||
|
||||
@@ -2802,6 +2802,11 @@ public final class ViewRootImpl implements ViewParent,
|
||||
hasWindowFocus = mUpcomingWindowFocus;
|
||||
inTouchMode = mUpcomingInTouchMode;
|
||||
}
|
||||
if (hasWindowFocus) {
|
||||
mInsetsController.onWindowFocusGained();
|
||||
} else {
|
||||
mInsetsController.onWindowFocusLost();
|
||||
}
|
||||
|
||||
if (mAdded) {
|
||||
profileRendering(hasWindowFocus);
|
||||
|
||||
@@ -55,6 +55,7 @@ import android.util.PrintWriterPrinter;
|
||||
import android.util.Printer;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Display;
|
||||
import android.view.ImeInsetsSourceConsumer;
|
||||
import android.view.InputChannel;
|
||||
import android.view.InputEvent;
|
||||
import android.view.InputEventSender;
|
||||
@@ -441,6 +442,13 @@ public final class InputMethodManager {
|
||||
*/
|
||||
private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
|
||||
|
||||
/**
|
||||
* When {@link ViewRootImpl#sNewInsetsMode} is set to
|
||||
* >= {@link ViewRootImpl#NEW_INSETS_MODE_IME}, {@link ImeInsetsSourceConsumer} applies the
|
||||
* IME visibility and listens for other state changes.
|
||||
*/
|
||||
private ImeInsetsSourceConsumer mImeInsetsConsumer;
|
||||
|
||||
final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
|
||||
final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
|
||||
|
||||
@@ -454,6 +462,8 @@ public final class InputMethodManager {
|
||||
static final int MSG_TIMEOUT_INPUT_EVENT = 6;
|
||||
static final int MSG_FLUSH_INPUT_EVENT = 7;
|
||||
static final int MSG_REPORT_FULLSCREEN_MODE = 10;
|
||||
static final int MSG_REPORT_PRE_RENDERED = 15;
|
||||
static final int MSG_APPLY_IME_VISIBILITY = 20;
|
||||
|
||||
private static boolean isAutofillUIShowing(View servedView) {
|
||||
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
|
||||
@@ -650,6 +660,23 @@ public final class InputMethodManager {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case MSG_REPORT_PRE_RENDERED: {
|
||||
synchronized (mH) {
|
||||
if (mImeInsetsConsumer != null) {
|
||||
mImeInsetsConsumer.onPreRendered((EditorInfo) msg.obj);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
case MSG_APPLY_IME_VISIBILITY: {
|
||||
synchronized (mH) {
|
||||
if (mImeInsetsConsumer != null) {
|
||||
mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -729,6 +756,18 @@ public final class InputMethodManager {
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportPreRendered(EditorInfo info) {
|
||||
mH.obtainMessage(MSG_REPORT_PRE_RENDERED, 0, 0, info)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyImeVisibility(boolean setVisible) {
|
||||
mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
|
||||
@@ -1515,6 +1554,7 @@ public final class InputMethodManager {
|
||||
|
||||
// Hook 'em up and let 'er rip.
|
||||
mCurrentTextBoxAttribute = tba;
|
||||
maybeCallServedViewChangedLocked(tba);
|
||||
mServedConnecting = false;
|
||||
if (mServedInputConnectionWrapper != null) {
|
||||
mServedInputConnectionWrapper.deactivate();
|
||||
@@ -1730,6 +1770,10 @@ public final class InputMethodManager {
|
||||
mCurrentTextBoxAttribute = null;
|
||||
mCompletions = null;
|
||||
mServedConnecting = true;
|
||||
// servedView has changed and it's not editable.
|
||||
if (!mServedView.onCheckIsTextEditor()) {
|
||||
maybeCallServedViewChangedLocked(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (ic != null) {
|
||||
@@ -1827,6 +1871,21 @@ public final class InputMethodManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for IME state callbacks and applying visibility in
|
||||
* {@link android.view.ImeInsetsSourceConsumer}.
|
||||
* @hide
|
||||
*/
|
||||
public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) {
|
||||
if (imeInsetsConsumer == null) {
|
||||
throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null.");
|
||||
}
|
||||
|
||||
synchronized (mH) {
|
||||
mImeInsetsConsumer = imeInsetsConsumer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the current selection range.
|
||||
*
|
||||
@@ -2705,6 +2764,12 @@ public final class InputMethodManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeCallServedViewChangedLocked(EditorInfo tba) {
|
||||
if (mImeInsetsConsumer != null) {
|
||||
mImeInsetsConsumer.onServedEditorChanged(tba);
|
||||
}
|
||||
}
|
||||
|
||||
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
|
||||
final Printer p = new PrintWriterPrinter(fout);
|
||||
p.println("Input method client state for " + this + ":");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
* Copyright (C) 2019 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.internal.inputmethod;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import com.android.internal.inputmethod.IInputContentUriToken;
|
||||
@@ -39,4 +40,6 @@ interface IInputMethodPrivilegedOperations {
|
||||
boolean switchToNextInputMethod(boolean onlyCurrentIme);
|
||||
boolean shouldOfferSwitchingToNextInputMethod();
|
||||
void notifyUserAction();
|
||||
void reportPreRendered(in EditorInfo info);
|
||||
void applyImeVisibility(boolean setVisible);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -347,4 +348,40 @@ public final class InputMethodPrivilegedOperations {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link IInputMethodPrivilegedOperations#reportPreRendered(info)}.
|
||||
*
|
||||
* @param info {@link EditorInfo} of the currently rendered {@link TextView}.
|
||||
*/
|
||||
@AnyThread
|
||||
public void reportPreRendered(EditorInfo info) {
|
||||
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
|
||||
if (ops == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ops.reportPreRendered(info);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(boolean)}.
|
||||
*
|
||||
* @param setVisible {@code true} to set IME visible, else hidden.
|
||||
*/
|
||||
@AnyThread
|
||||
public void applyImeVisibility(boolean setVisible) {
|
||||
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
|
||||
if (ops == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ops.applyImeVisibility(setVisible);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.internal.view;
|
||||
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import com.android.internal.view.InputBindResult;
|
||||
|
||||
/**
|
||||
@@ -27,4 +29,6 @@ oneway interface IInputMethodClient {
|
||||
void onUnbindMethod(int sequence, int unbindReason);
|
||||
void setActive(boolean active, boolean fullscreen);
|
||||
void reportFullscreenMode(boolean fullscreen);
|
||||
void reportPreRendered(in EditorInfo info);
|
||||
void applyImeVisibility(boolean setVisible);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user