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:
Tarandeep Singh
2019-01-25 11:47:57 -08:00
committed by Jorim Jaggi
parent 5cccc2bd8e
commit 2cbcd7ffbf
11 changed files with 552 additions and 5 deletions

View File

@@ -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 {

View 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);
}
}

View File

@@ -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.
*/

View File

@@ -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.

View File

@@ -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);

View File

@@ -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 + ":");

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}