Loading core/java/android/view/ImeInsetsSourceConsumer.java +3 −4 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING; import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION; import static android.view.InsetsState.ITYPE_IME; import android.annotation.Nullable; import android.os.IBinder; Loading Loading @@ -55,9 +54,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { private boolean mIsShowRequestedDuringHideAnimation; public ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, int id, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { super(ITYPE_IME, state, transactionSupplier, controller); super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller); } @Override Loading Loading @@ -137,7 +136,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching // this code here means that we now got control, so we can start the animation immediately. // If client window is trying to control IME and IME is already visible, it is immediate. if (fromIme || (mState.getSource(getInternalType()).isVisible() && getControl() != null)) { if (fromIme || (mState.getSource(getId()).isVisible() && getControl() != null)) { return ShowResult.SHOW_IMMEDIATELY; } Loading core/java/android/view/InsetsController.java +79 −44 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.view.InsetsControllerProto.CONTROL; import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.ViewRootImpl.CAPTION_ON_SHELL; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; Loading Loading @@ -569,8 +568,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final InsetsState mLastDispatchedState = new InsetsState(); private final Rect mFrame = new Rect(); private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator; private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator; private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); private final InsetsSourceConsumer mImeSourceConsumer; private final Host mHost; private final Handler mHandler; Loading Loading @@ -621,19 +621,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation this::invokeControllableInsetsChangedListeners; public InsetsController(Host host) { this(host, (controller, type) -> { if (type == ITYPE_IME) { return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller); this(host, (controller, source) -> { if (source.getType() == ime()) { return new ImeInsetsSourceConsumer(source.getId(), controller.mState, Transaction::new, controller); } else { return new InsetsSourceConsumer(type, controller.mState, Transaction::new, controller); return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState, Transaction::new, controller); } }, host.getHandler()); } @VisibleForTesting public InsetsController(Host host, BiFunction<InsetsController, Integer, InsetsSourceConsumer> consumerCreator, BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator, Handler handler) { mHost = host; mConsumerCreator = consumerCreator; Loading Loading @@ -683,6 +684,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation dispatchAnimationEnd(finishedAnimations.get(i)); } }; // Make mImeSourceConsumer always non-null. mImeSourceConsumer = getSourceConsumer(new InsetsSource(ITYPE_IME, ime())); } @VisibleForTesting Loading Loading @@ -741,28 +745,37 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.set(newState, 0 /* types */); for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); final InsetsSource source = newState.peekSource(consumer.getId()); if (source == null && consumer != mImeSourceConsumer) { // IME source consumer should always be there since we need to communicate with // InputMethodManager no matter we have the source or not. mSourceConsumers.removeAt(i); } } @InsetsType int existingTypes = 0; @InsetsType int visibleTypes = 0; @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { InsetsSource source = newState.peekSource(type); for (int i = 0; i < InsetsState.SIZE; i++) { InsetsSource source = newState.peekSource(i); if (source == null) continue; @InsetsType int insetsType = toPublicType(type); @AnimationType int animationType = getAnimationType(insetsType); @InsetsType int type = source.getType(); @AnimationType int animationType = getAnimationType(type); if (!source.isUserControllable()) { // The user animation is not allowed when visible frame is empty. disabledUserAnimationTypes |= insetsType; disabledUserAnimationTypes |= type; if (animationType == ANIMATION_TYPE_USER) { // Existing user animation needs to be cancelled. animationType = ANIMATION_TYPE_NONE; cancelledUserAnimationTypes[0] |= insetsType; cancelledUserAnimationTypes[0] |= type; } } getSourceConsumer(type).updateSource(source, animationType); existingTypes |= insetsType; getSourceConsumer(source).updateSource(source, animationType); existingTypes |= type; if (source.isVisible()) { visibleTypes |= insetsType; visibleTypes |= type; } } Loading Loading @@ -899,25 +912,34 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @InsetsType int controllableTypes = 0; int consumedControlCount = 0; final int[] showTypes = new int[1]; final int[] hideTypes = new int[1]; // Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); final InsetsSourceControl control = mTmpControlArray.get(consumer.getInternalType()); final InsetsSourceControl control = mTmpControlArray.get(consumer.getId()); if (control != null) { controllableTypes |= control.getType(); consumedControlCount++; } // control may be null, but we still need to update the control to null if it got // revoked. consumer.setControl(control, showTypes, hideTypes); } // Ensure to create source consumers if not available yet. if (consumedControlCount != mTmpControlArray.size()) { // Whoops! The server sent us some controls without sending corresponding sources. for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); final InsetsSourceConsumer consumer = getSourceConsumer(control.getId()); consumer.setControl(control, showTypes, hideTypes); controllableTypes |= control.getType(); final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId()); if (consumer == null) { control.release(SurfaceControl::release); Log.e(TAG, control + " has no consumer."); } } } if (mTmpControlArray.size() > 0) { Loading Loading @@ -1144,11 +1166,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation types &= ~mDisabledUserAnimationInsetsTypes; if (fromIme && (disabledTypes & ime()) != 0 && !mState.getSource(ITYPE_IME).isVisible()) { && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) { // We've requested IMM to show IME, but the IME is not controllable. We need to // cancel the request. setRequestedVisibleTypes(0 /* visibleTypes */, ime()); if (getSourceConsumer(ITYPE_IME).onAnimationStateChanged(false /* running */)) { if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) { notifyVisibilityChanged(); } } Loading @@ -1163,11 +1185,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (DEBUG) Log.d(TAG, "controlAnimation types: " + types); mLastStartedAnimTypes |= types; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); Pair<Integer, Boolean> typesReadyPair = collectSourceControls( fromIme, internalTypes, controls, animationType); fromIme, types, controls, animationType); int typesReady = typesReadyPair.first; boolean imeReady = typesReadyPair.second; if (DEBUG) Log.d(TAG, String.format( Loading Loading @@ -1257,13 +1278,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** * @return Pair of (types ready to animate, IME ready to animate). */ private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) { private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types, SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) { int typesReady = 0; boolean imeReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); if ((consumer.getType() & types) == 0) { continue; } boolean show = animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_USER; boolean canRun = true; Loading Loading @@ -1291,7 +1314,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!canRun) { if (WARN) Log.w(TAG, String.format( "collectSourceControls can't continue show for type: %s fromIme: %b", InsetsState.typeToString(consumer.getInternalType()), fromIme)); WindowInsets.Type.toString(consumer.getType()), fromIme)); continue; } final InsetsSourceControl control = consumer.getControl(); Loading Loading @@ -1437,16 +1460,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @VisibleForTesting public @NonNull InsetsSourceConsumer getSourceConsumer(int id) { InsetsSourceConsumer consumer = mSourceConsumers.get(id); public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) { final int sourceId = source.getId(); InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId); if (consumer != null) { return consumer; } consumer = mConsumerCreator.apply(this, id); mSourceConsumers.put(id, consumer); if (source.getType() == ime() && mImeSourceConsumer != null) { // WindowInsets.Type.ime() should be only provided by one source. mSourceConsumers.remove(mImeSourceConsumer.getId()); consumer = mImeSourceConsumer; consumer.setId(sourceId); } else { consumer = mConsumerCreator.apply(this, source); } mSourceConsumers.put(sourceId, consumer); return consumer; } @VisibleForTesting public @NonNull InsetsSourceConsumer getImeSourceConsumer() { return mImeSourceConsumer; } @VisibleForTesting public void notifyVisibilityChanged() { mHost.notifyInsetsChanged(); Loading @@ -1467,14 +1503,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * Called when current window gains focus. */ public void onWindowFocusGained(boolean hasViewFocused) { getSourceConsumer(ITYPE_IME).onWindowFocusGained(hasViewFocused); mImeSourceConsumer.onWindowFocusGained(hasViewFocused); } /** * Called when current window loses focus. */ public void onWindowFocusLost() { getSourceConsumer(ITYPE_IME).onWindowFocusLost(); mImeSourceConsumer.onWindowFocusLost(); } @VisibleForTesting Loading Loading @@ -1518,13 +1554,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // TODO(b/166736352): We should only skip the animation of specific types, not all types. boolean skipAnim = false; if ((types & ime()) != 0) { final InsetsSourceConsumer consumer = mSourceConsumers.get(ITYPE_IME); final InsetsSourceControl imeControl = consumer != null ? consumer.getControl() : null; final InsetsSourceControl imeControl = mImeSourceConsumer.getControl(); // Skip showing animation once that made by system for some reason. // (e.g. starting window with IME snapshot) if (imeControl != null) { skipAnim = imeControl.getAndClearSkipAnimationOnce() && show && consumer.hasViewFocusWhenWindowFocusGain(); && mImeSourceConsumer.hasViewFocusWhenWindowFocusGain(); } } applyAnimation(types, show, fromIme, skipAnim, statsToken); Loading Loading @@ -1683,7 +1718,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int result = 0; for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); InsetsSource source = mState.peekSource(consumer.getInternalType()); InsetsSource source = mState.peekSource(consumer.getId()); if (consumer.getControl() != null && source != null && source.isUserControllable()) { result |= consumer.getType(); } Loading core/java/android/view/InsetsSourceConsumer.java +34 −30 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE; import static android.view.InsetsSourceConsumerProto.PENDING_FRAME; import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME; import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL; import static android.view.InsetsState.getDefaultVisibility; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; Loading @@ -34,7 +33,6 @@ import android.annotation.Nullable; import android.graphics.Rect; import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; Loading Loading @@ -72,7 +70,7 @@ public class InsetsSourceConsumer { protected final InsetsController mController; protected final InsetsState mState; private final @InternalInsetsType int mInternalType; private int mId; private final @InsetsType int mType; private static final String TAG = "InsetsSourceConsumer"; Loading @@ -88,16 +86,17 @@ public class InsetsSourceConsumer { private Rect mPendingVisibleFrame; /** * @param type The {@link InternalInsetsType} of the consumed insets. * @param id The ID of the consumed insets. * @param type The {@link InsetsType} of the consumed insets. * @param state The current {@link InsetsState} of the consumed insets. * @param transactionSupplier The source of new {@link Transaction} instances. The supplier * must provide *new* instances, which will be explicitly closed by this class. * @param controller The {@link InsetsController} to use for insets interaction. */ public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { mType = InsetsState.toPublicType(type); mInternalType = type; mId = id; mType = type; mState = state; mTransactionSupplier = transactionSupplier; mController = controller; Loading Loading @@ -134,12 +133,11 @@ public class InsetsSourceConsumer { mController.notifyControlRevoked(this); // Check if we need to restore server visibility. final InsetsSource source = mState.getSource(mInternalType); final boolean serverVisibility = mController.getLastDispatchedState().getSourceOrDefaultVisibility( mInternalType); if (source.isVisible() != serverVisibility) { source.setVisible(serverVisibility); final InsetsSource localSource = mState.peekSource(mId); final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId); if (localSource != null && serverSource != null && localSource.isVisible() != serverSource.isVisible()) { localSource.setVisible(serverSource.isVisible()); mController.notifyVisibilityChanged(); } } else { Loading Loading @@ -196,12 +194,16 @@ public class InsetsSourceConsumer { return (mController.getRequestedVisibleTypes() & mType) != 0; } @InsetsType int getType() { return mType; int getId() { return mId; } void setId(int id) { mId = id; } @InternalInsetsType int getInternalType() { return mInternalType; @InsetsType int getType() { return mType; } /** Loading @@ -211,12 +213,14 @@ public class InsetsSourceConsumer { public boolean onAnimationStateChanged(boolean running) { boolean insetsChanged = false; if (!running && mPendingFrame != null) { InsetsSource source = mState.getSource(mInternalType); final InsetsSource source = mState.peekSource(mId); if (source != null) { source.setFrame(mPendingFrame); source.setVisibleFrame(mPendingVisibleFrame); insetsChanged = true; } mPendingFrame = null; mPendingVisibleFrame = null; insetsChanged = true; } // We apply the visibility override after the animation is started. We don't do this before Loading Loading @@ -248,25 +252,25 @@ public class InsetsSourceConsumer { @VisibleForTesting(visibility = PACKAGE) public boolean applyLocalVisibilityOverride() { final InsetsSource source = mState.peekSource(mInternalType); final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility( mInternalType); final boolean hasControl = mSourceControl != null; final InsetsSource source = mState.peekSource(mId); if (source == null) { return false; } final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; // If we don't have control, we are not able to change the visibility. if (!hasControl) { if (mSourceControl == null) { if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in " + mController.getHost().getRootViewTitle() + " requestedVisible=" + requestedVisible); return false; } if (isVisible == requestedVisible) { if (source.isVisible() == requestedVisible) { return false; } if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", mController.getHost().getRootViewTitle(), requestedVisible)); mState.getSource(mInternalType).setVisible(requestedVisible); source.setVisible(requestedVisible); return true; } Loading Loading @@ -301,7 +305,7 @@ public class InsetsSourceConsumer { @VisibleForTesting(visibility = PACKAGE) public void updateSource(InsetsSource newSource, @AnimationType int animationType) { InsetsSource source = mState.peekSource(mInternalType); InsetsSource source = mState.peekSource(mId); if (source == null || animationType == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mPendingFrame = null; Loading Loading @@ -345,7 +349,7 @@ public class InsetsSourceConsumer { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mInternalType)); proto.write(INTERNAL_INSETS_TYPE, WindowInsets.Type.toString(mType)); proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); proto.write(IS_REQUESTED_VISIBLE, (mController.getRequestedVisibleTypes() & mType) != 0); if (mSourceControl != null) { Loading core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -60,7 +60,7 @@ import org.mockito.Spy; public class ImeInsetsSourceConsumerTest { Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); ImeInsetsSourceConsumer mImeConsumer; InsetsSourceConsumer mImeConsumer; @Spy InsetsController mController; SurfaceControl mLeash; Loading @@ -86,7 +86,7 @@ public class ImeInsetsSourceConsumerTest { false, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, SOFT_INPUT_ADJUST_RESIZE, 0, 0); mImeConsumer = (ImeInsetsSourceConsumer) mController.getSourceConsumer(ITYPE_IME); mImeConsumer = mController.getImeSourceConsumer(); }); } Loading core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +4 −2 Original line number Diff line number Diff line Loading @@ -89,7 +89,8 @@ public class InsetsAnimationControlImplTest { mInsetsState = new InsetsState(); mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100)); mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500)); InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState, InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(), mInsetsState, () -> mMockTransaction, mMockController); topConsumer.setControl( new InsetsSourceControl(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(), Loading @@ -97,7 +98,8 @@ public class InsetsAnimationControlImplTest { new int[1], new int[1]); InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR, mInsetsState, () -> mMockTransaction, mMockController); WindowInsets.Type.navigationBars(), mInsetsState, () -> mMockTransaction, mMockController); navConsumer.setControl( new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)), Loading Loading
core/java/android/view/ImeInsetsSourceConsumer.java +3 −4 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING; import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION; import static android.view.InsetsState.ITYPE_IME; import android.annotation.Nullable; import android.os.IBinder; Loading Loading @@ -55,9 +54,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { private boolean mIsShowRequestedDuringHideAnimation; public ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, int id, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { super(ITYPE_IME, state, transactionSupplier, controller); super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller); } @Override Loading Loading @@ -137,7 +136,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching // this code here means that we now got control, so we can start the animation immediately. // If client window is trying to control IME and IME is already visible, it is immediate. if (fromIme || (mState.getSource(getInternalType()).isVisible() && getControl() != null)) { if (fromIme || (mState.getSource(getId()).isVisible() && getControl() != null)) { return ShowResult.SHOW_IMMEDIATELY; } Loading
core/java/android/view/InsetsController.java +79 −44 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.view.InsetsControllerProto.CONTROL; import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.ViewRootImpl.CAPTION_ON_SHELL; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; Loading Loading @@ -569,8 +568,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final InsetsState mLastDispatchedState = new InsetsState(); private final Rect mFrame = new Rect(); private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator; private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator; private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); private final InsetsSourceConsumer mImeSourceConsumer; private final Host mHost; private final Handler mHandler; Loading Loading @@ -621,19 +621,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation this::invokeControllableInsetsChangedListeners; public InsetsController(Host host) { this(host, (controller, type) -> { if (type == ITYPE_IME) { return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller); this(host, (controller, source) -> { if (source.getType() == ime()) { return new ImeInsetsSourceConsumer(source.getId(), controller.mState, Transaction::new, controller); } else { return new InsetsSourceConsumer(type, controller.mState, Transaction::new, controller); return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState, Transaction::new, controller); } }, host.getHandler()); } @VisibleForTesting public InsetsController(Host host, BiFunction<InsetsController, Integer, InsetsSourceConsumer> consumerCreator, BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator, Handler handler) { mHost = host; mConsumerCreator = consumerCreator; Loading Loading @@ -683,6 +684,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation dispatchAnimationEnd(finishedAnimations.get(i)); } }; // Make mImeSourceConsumer always non-null. mImeSourceConsumer = getSourceConsumer(new InsetsSource(ITYPE_IME, ime())); } @VisibleForTesting Loading Loading @@ -741,28 +745,37 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.set(newState, 0 /* types */); for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); final InsetsSource source = newState.peekSource(consumer.getId()); if (source == null && consumer != mImeSourceConsumer) { // IME source consumer should always be there since we need to communicate with // InputMethodManager no matter we have the source or not. mSourceConsumers.removeAt(i); } } @InsetsType int existingTypes = 0; @InsetsType int visibleTypes = 0; @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { InsetsSource source = newState.peekSource(type); for (int i = 0; i < InsetsState.SIZE; i++) { InsetsSource source = newState.peekSource(i); if (source == null) continue; @InsetsType int insetsType = toPublicType(type); @AnimationType int animationType = getAnimationType(insetsType); @InsetsType int type = source.getType(); @AnimationType int animationType = getAnimationType(type); if (!source.isUserControllable()) { // The user animation is not allowed when visible frame is empty. disabledUserAnimationTypes |= insetsType; disabledUserAnimationTypes |= type; if (animationType == ANIMATION_TYPE_USER) { // Existing user animation needs to be cancelled. animationType = ANIMATION_TYPE_NONE; cancelledUserAnimationTypes[0] |= insetsType; cancelledUserAnimationTypes[0] |= type; } } getSourceConsumer(type).updateSource(source, animationType); existingTypes |= insetsType; getSourceConsumer(source).updateSource(source, animationType); existingTypes |= type; if (source.isVisible()) { visibleTypes |= insetsType; visibleTypes |= type; } } Loading Loading @@ -899,25 +912,34 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @InsetsType int controllableTypes = 0; int consumedControlCount = 0; final int[] showTypes = new int[1]; final int[] hideTypes = new int[1]; // Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); final InsetsSourceControl control = mTmpControlArray.get(consumer.getInternalType()); final InsetsSourceControl control = mTmpControlArray.get(consumer.getId()); if (control != null) { controllableTypes |= control.getType(); consumedControlCount++; } // control may be null, but we still need to update the control to null if it got // revoked. consumer.setControl(control, showTypes, hideTypes); } // Ensure to create source consumers if not available yet. if (consumedControlCount != mTmpControlArray.size()) { // Whoops! The server sent us some controls without sending corresponding sources. for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); final InsetsSourceConsumer consumer = getSourceConsumer(control.getId()); consumer.setControl(control, showTypes, hideTypes); controllableTypes |= control.getType(); final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId()); if (consumer == null) { control.release(SurfaceControl::release); Log.e(TAG, control + " has no consumer."); } } } if (mTmpControlArray.size() > 0) { Loading Loading @@ -1144,11 +1166,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation types &= ~mDisabledUserAnimationInsetsTypes; if (fromIme && (disabledTypes & ime()) != 0 && !mState.getSource(ITYPE_IME).isVisible()) { && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) { // We've requested IMM to show IME, but the IME is not controllable. We need to // cancel the request. setRequestedVisibleTypes(0 /* visibleTypes */, ime()); if (getSourceConsumer(ITYPE_IME).onAnimationStateChanged(false /* running */)) { if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) { notifyVisibilityChanged(); } } Loading @@ -1163,11 +1185,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (DEBUG) Log.d(TAG, "controlAnimation types: " + types); mLastStartedAnimTypes |= types; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); Pair<Integer, Boolean> typesReadyPair = collectSourceControls( fromIme, internalTypes, controls, animationType); fromIme, types, controls, animationType); int typesReady = typesReadyPair.first; boolean imeReady = typesReadyPair.second; if (DEBUG) Log.d(TAG, String.format( Loading Loading @@ -1257,13 +1278,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** * @return Pair of (types ready to animate, IME ready to animate). */ private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) { private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types, SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) { int typesReady = 0; boolean imeReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); if ((consumer.getType() & types) == 0) { continue; } boolean show = animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_USER; boolean canRun = true; Loading Loading @@ -1291,7 +1314,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!canRun) { if (WARN) Log.w(TAG, String.format( "collectSourceControls can't continue show for type: %s fromIme: %b", InsetsState.typeToString(consumer.getInternalType()), fromIme)); WindowInsets.Type.toString(consumer.getType()), fromIme)); continue; } final InsetsSourceControl control = consumer.getControl(); Loading Loading @@ -1437,16 +1460,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @VisibleForTesting public @NonNull InsetsSourceConsumer getSourceConsumer(int id) { InsetsSourceConsumer consumer = mSourceConsumers.get(id); public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) { final int sourceId = source.getId(); InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId); if (consumer != null) { return consumer; } consumer = mConsumerCreator.apply(this, id); mSourceConsumers.put(id, consumer); if (source.getType() == ime() && mImeSourceConsumer != null) { // WindowInsets.Type.ime() should be only provided by one source. mSourceConsumers.remove(mImeSourceConsumer.getId()); consumer = mImeSourceConsumer; consumer.setId(sourceId); } else { consumer = mConsumerCreator.apply(this, source); } mSourceConsumers.put(sourceId, consumer); return consumer; } @VisibleForTesting public @NonNull InsetsSourceConsumer getImeSourceConsumer() { return mImeSourceConsumer; } @VisibleForTesting public void notifyVisibilityChanged() { mHost.notifyInsetsChanged(); Loading @@ -1467,14 +1503,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * Called when current window gains focus. */ public void onWindowFocusGained(boolean hasViewFocused) { getSourceConsumer(ITYPE_IME).onWindowFocusGained(hasViewFocused); mImeSourceConsumer.onWindowFocusGained(hasViewFocused); } /** * Called when current window loses focus. */ public void onWindowFocusLost() { getSourceConsumer(ITYPE_IME).onWindowFocusLost(); mImeSourceConsumer.onWindowFocusLost(); } @VisibleForTesting Loading Loading @@ -1518,13 +1554,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // TODO(b/166736352): We should only skip the animation of specific types, not all types. boolean skipAnim = false; if ((types & ime()) != 0) { final InsetsSourceConsumer consumer = mSourceConsumers.get(ITYPE_IME); final InsetsSourceControl imeControl = consumer != null ? consumer.getControl() : null; final InsetsSourceControl imeControl = mImeSourceConsumer.getControl(); // Skip showing animation once that made by system for some reason. // (e.g. starting window with IME snapshot) if (imeControl != null) { skipAnim = imeControl.getAndClearSkipAnimationOnce() && show && consumer.hasViewFocusWhenWindowFocusGain(); && mImeSourceConsumer.hasViewFocusWhenWindowFocusGain(); } } applyAnimation(types, show, fromIme, skipAnim, statsToken); Loading Loading @@ -1683,7 +1718,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int result = 0; for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); InsetsSource source = mState.peekSource(consumer.getInternalType()); InsetsSource source = mState.peekSource(consumer.getId()); if (consumer.getControl() != null && source != null && source.isUserControllable()) { result |= consumer.getType(); } Loading
core/java/android/view/InsetsSourceConsumer.java +34 −30 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE; import static android.view.InsetsSourceConsumerProto.PENDING_FRAME; import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME; import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL; import static android.view.InsetsState.getDefaultVisibility; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; Loading @@ -34,7 +33,6 @@ import android.annotation.Nullable; import android.graphics.Rect; import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; Loading Loading @@ -72,7 +70,7 @@ public class InsetsSourceConsumer { protected final InsetsController mController; protected final InsetsState mState; private final @InternalInsetsType int mInternalType; private int mId; private final @InsetsType int mType; private static final String TAG = "InsetsSourceConsumer"; Loading @@ -88,16 +86,17 @@ public class InsetsSourceConsumer { private Rect mPendingVisibleFrame; /** * @param type The {@link InternalInsetsType} of the consumed insets. * @param id The ID of the consumed insets. * @param type The {@link InsetsType} of the consumed insets. * @param state The current {@link InsetsState} of the consumed insets. * @param transactionSupplier The source of new {@link Transaction} instances. The supplier * must provide *new* instances, which will be explicitly closed by this class. * @param controller The {@link InsetsController} to use for insets interaction. */ public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { mType = InsetsState.toPublicType(type); mInternalType = type; mId = id; mType = type; mState = state; mTransactionSupplier = transactionSupplier; mController = controller; Loading Loading @@ -134,12 +133,11 @@ public class InsetsSourceConsumer { mController.notifyControlRevoked(this); // Check if we need to restore server visibility. final InsetsSource source = mState.getSource(mInternalType); final boolean serverVisibility = mController.getLastDispatchedState().getSourceOrDefaultVisibility( mInternalType); if (source.isVisible() != serverVisibility) { source.setVisible(serverVisibility); final InsetsSource localSource = mState.peekSource(mId); final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId); if (localSource != null && serverSource != null && localSource.isVisible() != serverSource.isVisible()) { localSource.setVisible(serverSource.isVisible()); mController.notifyVisibilityChanged(); } } else { Loading Loading @@ -196,12 +194,16 @@ public class InsetsSourceConsumer { return (mController.getRequestedVisibleTypes() & mType) != 0; } @InsetsType int getType() { return mType; int getId() { return mId; } void setId(int id) { mId = id; } @InternalInsetsType int getInternalType() { return mInternalType; @InsetsType int getType() { return mType; } /** Loading @@ -211,12 +213,14 @@ public class InsetsSourceConsumer { public boolean onAnimationStateChanged(boolean running) { boolean insetsChanged = false; if (!running && mPendingFrame != null) { InsetsSource source = mState.getSource(mInternalType); final InsetsSource source = mState.peekSource(mId); if (source != null) { source.setFrame(mPendingFrame); source.setVisibleFrame(mPendingVisibleFrame); insetsChanged = true; } mPendingFrame = null; mPendingVisibleFrame = null; insetsChanged = true; } // We apply the visibility override after the animation is started. We don't do this before Loading Loading @@ -248,25 +252,25 @@ public class InsetsSourceConsumer { @VisibleForTesting(visibility = PACKAGE) public boolean applyLocalVisibilityOverride() { final InsetsSource source = mState.peekSource(mInternalType); final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility( mInternalType); final boolean hasControl = mSourceControl != null; final InsetsSource source = mState.peekSource(mId); if (source == null) { return false; } final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; // If we don't have control, we are not able to change the visibility. if (!hasControl) { if (mSourceControl == null) { if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in " + mController.getHost().getRootViewTitle() + " requestedVisible=" + requestedVisible); return false; } if (isVisible == requestedVisible) { if (source.isVisible() == requestedVisible) { return false; } if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", mController.getHost().getRootViewTitle(), requestedVisible)); mState.getSource(mInternalType).setVisible(requestedVisible); source.setVisible(requestedVisible); return true; } Loading Loading @@ -301,7 +305,7 @@ public class InsetsSourceConsumer { @VisibleForTesting(visibility = PACKAGE) public void updateSource(InsetsSource newSource, @AnimationType int animationType) { InsetsSource source = mState.peekSource(mInternalType); InsetsSource source = mState.peekSource(mId); if (source == null || animationType == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mPendingFrame = null; Loading Loading @@ -345,7 +349,7 @@ public class InsetsSourceConsumer { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mInternalType)); proto.write(INTERNAL_INSETS_TYPE, WindowInsets.Type.toString(mType)); proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); proto.write(IS_REQUESTED_VISIBLE, (mController.getRequestedVisibleTypes() & mType) != 0); if (mSourceControl != null) { Loading
core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -60,7 +60,7 @@ import org.mockito.Spy; public class ImeInsetsSourceConsumerTest { Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); ImeInsetsSourceConsumer mImeConsumer; InsetsSourceConsumer mImeConsumer; @Spy InsetsController mController; SurfaceControl mLeash; Loading @@ -86,7 +86,7 @@ public class ImeInsetsSourceConsumerTest { false, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, SOFT_INPUT_ADJUST_RESIZE, 0, 0); mImeConsumer = (ImeInsetsSourceConsumer) mController.getSourceConsumer(ITYPE_IME); mImeConsumer = mController.getImeSourceConsumer(); }); } Loading
core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +4 −2 Original line number Diff line number Diff line Loading @@ -89,7 +89,8 @@ public class InsetsAnimationControlImplTest { mInsetsState = new InsetsState(); mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100)); mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500)); InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState, InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(), mInsetsState, () -> mMockTransaction, mMockController); topConsumer.setControl( new InsetsSourceControl(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(), Loading @@ -97,7 +98,8 @@ public class InsetsAnimationControlImplTest { new int[1], new int[1]); InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR, mInsetsState, () -> mMockTransaction, mMockController); WindowInsets.Type.navigationBars(), mInsetsState, () -> mMockTransaction, mMockController); navConsumer.setControl( new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)), Loading