diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 8b5af29517cb5..ef9d990168d2c 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -21,6 +21,7 @@ import static android.view.InsetsState.ITYPE_IME; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.os.Parcel; import android.text.TextUtils; import android.view.SurfaceControl.Transaction; @@ -153,6 +154,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { return mIsRequestedVisibleAwaitingControl || isRequestedVisible(); } + @Override + public void onPerceptible(boolean perceptible) { + super.onPerceptible(perceptible); + final IBinder window = mController.getHost().getWindowToken(); + if (window != null) { + getImm().reportPerceptible(window, perceptible); + } + } + 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); diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 74c186948b2f2..3431c3ecc310f 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,6 +16,7 @@ package android.view; +import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; /** @@ -64,4 +65,15 @@ public interface InsetsAnimationControlCallbacks { * previous calls to applySurfaceParams. */ void releaseSurfaceControlFromRt(SurfaceControl sc); + + /** + * Reports that the perceptibility of the given types has changed to the given value. + * + * A type is perceptible if it is not (almost) entirely off-screen and not (almost) entirely + * transparent. + * + * @param types the (public) types whose perceptibility has changed + * @param perceptible true, if the types are now perceptible, false if they are not perceptible + */ + void reportPerceptible(@InsetsType int types, boolean perceptible); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index baeae2fd2cdde..31da83ad5137e 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -87,6 +87,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private float mPendingAlpha = 1.0f; @VisibleForTesting(visibility = PACKAGE) public boolean mReadyDispatched; + private Boolean mPerceptible; @VisibleForTesting public InsetsAnimationControlImpl(SparseArray controls, Rect frame, @@ -121,6 +122,14 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll new Bounds(mHiddenInsets, mShownInsets)); } + private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { + return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left) + && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top) + && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right) + && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom) + && currentAlpha >= 0.5f; + } + @Override public boolean hasZeroInsetsIme() { return mHasZeroInsetsIme; @@ -175,6 +184,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mPendingInsets = sanitize(insets); mPendingAlpha = sanitize(alpha); mController.scheduleApplyChangeInsets(this); + boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha); + if (mPerceptible == null || perceptible != mPerceptible) { + mController.reportPerceptible(mTypes, perceptible); + mPerceptible = perceptible; + } } @VisibleForTesting diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 0e71b7643b7d9..123604489da49 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -90,6 +90,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro // Since we don't push the SurfaceParams to the RT we can release directly sc.release(); } + + @Override + public void reportPerceptible(int types, boolean perceptible) { + mMainThreadHandler.post(() -> mOuterCallbacks.reportPerceptible(types, perceptible)); + } }; @UiThread diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5f99bfe432a42..a3c88bacb6277 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -35,6 +35,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; +import android.os.IBinder; import android.os.Trace; import android.util.ArraySet; import android.util.Log; @@ -165,6 +166,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** @see ViewRootImpl#dipToPx */ int dipToPx(int dips); + + /** + * @return token associated with the host, if it has one. + */ + @Nullable + IBinder getWindowToken(); } private static final String TAG = "InsetsController"; @@ -1353,6 +1360,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mHost.releaseSurfaceControlFromRt(sc); } + @Override + public void reportPerceptible(int types, boolean perceptible) { + final ArraySet internalTypes = toInternalType(types); + final int size = mSourceConsumers.size(); + for (int i = 0; i < size; i++) { + final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (internalTypes.contains(consumer.getType())) { + consumer.onPerceptible(perceptible); + } + } + } + Host getHost() { return mHost; } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 565846638acc0..b62e67c8f9e1f 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -260,6 +260,15 @@ public class InsetsSourceConsumer { return ShowResult.SHOW_IMMEDIATELY; } + /** + * Reports that this source's perceptibility has changed + * + * @param perceptible true if the source is perceptible, false otherwise. + * @see InsetsAnimationControlCallbacks#reportPerceptible + */ + public void onPerceptible(boolean perceptible) { + } + /** * Notify listeners that window is now hidden. */ @@ -339,5 +348,6 @@ public class InsetsSourceConsumer { t.hide(mSourceControl.getLeash()); } t.apply(); + onPerceptible(mRequestedVisible); } } diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index f7ca3c2b7ddf3..90a80cefc54d7 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONT import android.annotation.NonNull; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.inputmethod.InputMethodManager; @@ -236,4 +237,16 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { } return 0; } + + @Override + public IBinder getWindowToken() { + if (mViewRoot == null) { + return null; + } + final View view = mViewRoot.getView(); + if (view == null) { + return null; + } + return view.getWindowToken(); + } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 821dd742db9ab..aedb59bfee422 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -541,6 +541,19 @@ public final class InputMethodManager { return servedView.hasWindowFocus() || isAutofillUIShowing(servedView); } + /** + * Reports whether the IME is currently perceptible or not, according to the leash applied by + * {@link android.view.WindowInsetsController}. + * @hide + */ + public void reportPerceptible(IBinder windowToken, boolean perceptible) { + try { + mService.reportPerceptible(windowToken, perceptible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final class DelegateImpl implements ImeFocusController.InputMethodManagerDelegate { /** diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index d22f942133380..8ec51b89d2400 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -72,5 +72,6 @@ interface IInputMethodManager { void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); + oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); void removeImeSurface(); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 8eca650398bf2..a2b1e3d69cd2e 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -234,6 +234,24 @@ public class InsetsAnimationControlImplTest { verify(mMockListener).onFinished(mController); } + @Test + public void testPerceptible_insets() { + mController.setInsetsAndAlpha(mController.getHiddenStateInsets(), 1f, 1f); + verify(mMockController).reportPerceptible(systemBars(), false); + + mController.setInsetsAndAlpha(mController.getShownStateInsets(), 1f, 1f); + verify(mMockController).reportPerceptible(systemBars(), true); + } + + @Test + public void testPerceptible_alpha() { + mController.setInsetsAndAlpha(mController.getShownStateInsets(), 0f, 1f); + verify(mMockController).reportPerceptible(systemBars(), false); + + mController.setInsetsAndAlpha(mController.getShownStateInsets(), 1f, 1f); + verify(mMockController).reportPerceptible(systemBars(), true); + } + private void assertPosition(Matrix m, Rect original, Rect transformed) { RectF rect = new RectF(original); rect.offsetTo(0, 0); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index de13bd86a4155..70f0399d10709 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -17,6 +17,7 @@ package com.android.server.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.os.IBinder; import android.view.inputmethod.InlineSuggestionsRequest; @@ -108,6 +109,16 @@ public abstract class InputMethodManagerInternal { public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, int displayId); + /** + * Reports that IME control has transferred to the given window token, or if null that + * control has been taken away from client windows (and is instead controlled by the policy + * or SystemUI). + * + * @param windowToken the window token that is now in control, or {@code null} if no client + * window is in control of the IME. + */ + public abstract void reportImeControl(@Nullable IBinder windowToken); + /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ @@ -151,6 +162,10 @@ public abstract class InputMethodManagerInternal { int displayId) { return false; } + + @Override + public void reportImeControl(@Nullable IBinder windowToken) { + } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index fea7980c1c246..9acb47538043c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -179,6 +179,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -592,6 +593,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Was the keyguard locked when this client became current? private boolean mCurClientInKeyguard; + /** + * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} + */ + private boolean mCurPerceptible; + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). @@ -2936,6 +2942,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { vis = 0; } + if (!mCurPerceptible) { + vis = 0; + } // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBar != null) { @@ -3148,6 +3157,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @BinderThread + @Override + public void reportPerceptible(IBinder windowToken, boolean perceptible) { + Objects.requireNonNull(windowToken, "windowToken must not be null"); + int uid = Binder.getCallingUid(); + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } + final long ident = Binder.clearCallingIdentity(); + try { + if (mCurFocusedWindow == windowToken + && mCurPerceptible != perceptible) { + mCurPerceptible = perceptible; + updateSystemUiLocked(mImeWindowVis, mBackDisposition); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + @GuardedBy("mMethodMap") boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { @@ -3451,6 +3482,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindow = windowToken; mCurFocusedWindowSoftInputMode = softInputMode; mCurFocusedWindowClient = cs; + mCurPerceptible = true; // Should we auto-show the IME even if the caller has not // specified what should be done with it? @@ -5010,6 +5042,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken); } + private void reportImeControl(@Nullable IBinder windowToken) { + synchronized (mMethodMap) { + if (mCurFocusedWindow != windowToken) { + // mCurPerceptible was set by the focused window, but it is no longer in control, + // so we reset mCurPerceptible. + mCurPerceptible = true; + } + } + } + private static final class LocalServiceImpl extends InputMethodManagerInternal { @NonNull private final InputMethodManagerService mService; @@ -5062,6 +5104,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int displayId) { return mService.transferTouchFocusToImeWindow(sourceInputToken, displayId); } + + @Override + public void reportImeControl(@Nullable IBinder windowToken) { + mService.reportImeControl(windowToken); + } } @BinderThread @@ -5164,6 +5211,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mCurMethodId=" + mCurMethodId); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); + p.println(" mCurPerceptible=" + mCurPerceptible); p.println(" mCurFocusedWindow=" + mCurFocusedWindow + " softInputMode=" + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode) diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 33c78e403ce3c..2e3d3963c9dfe 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -221,6 +221,10 @@ public final class MultiClientInputMethodManagerService { reportNotSupported(); return false; } + + @Override + public void reportImeControl(@Nullable IBinder windowToken) { + } }); } @@ -1751,6 +1755,12 @@ public final class MultiClientInputMethodManagerService { reportNotSupported(); } + @BinderThread + @Override + public void reportPerceptible(IBinder windowClient, boolean perceptible) { + reportNotSupported(); + } + @BinderThread @Override public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ed055eb7eb099..9b5d94ebb1ac9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -210,6 +210,7 @@ import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledFunction; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.protolog.common.ProtoLog; import com.android.server.wm.utils.DisplayRotationUtil; @@ -3566,6 +3567,14 @@ class DisplayContent extends WindowContainer + InputMethodManagerInternal.get().reportImeControl(token) + ); } private void updateImeParent() { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index c7b91067d59fc..254356d673e35 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -482,6 +482,12 @@ class InsetsPolicy { WindowInsetsAnimation animation, Bounds bounds) { } + + @Override + public void reportPerceptible(int types, boolean perceptible) { + // No-op for now - only client windows report perceptibility for now, with policy + // controllers assumed to always be perceptible. + } } } }