Loading core/java/android/inputmethodservice/InputMethodService.java +28 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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"); Loading Loading @@ -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 { Loading core/java/android/view/ImeInsetsSourceConsumer.java 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * 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); } } core/java/android/view/InsetsController.java +29 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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. */ Loading core/java/android/view/InsetsSourceConsumer.java +12 −2 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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. Loading core/java/android/view/ViewRootImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
core/java/android/inputmethodservice/InputMethodService.java +28 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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"); Loading Loading @@ -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 { Loading
core/java/android/view/ImeInsetsSourceConsumer.java 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * 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); } }
core/java/android/view/InsetsController.java +29 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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. */ Loading
core/java/android/view/InsetsSourceConsumer.java +12 −2 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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. Loading
core/java/android/view/ViewRootImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -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); Loading