Loading core/java/android/view/InsetsController.java +25 −3 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.view; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toInternalType; import static android.view.InsetsState.toPublicType; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.ime; Loading Loading @@ -471,7 +472,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!localStateChanged && mLastDispachedState.equals(state)) { if (!localStateChanged && mLastDispachedState.equals(state)) { return false; return false; } } mState.set(state); updateState(state); mLastDispachedState.set(state, true /* copySources */); mLastDispachedState.set(state, true /* copySources */); applyLocalVisibilityOverride(); applyLocalVisibilityOverride(); if (localStateChanged) { if (localStateChanged) { Loading @@ -480,11 +481,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) { if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) { sendStateToWindowManager(); sendStateToWindowManager(); } } return true; } private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); for (int i = newState.getSourcesCount() - 1; i >= 0; i--) { InsetsSource source = newState.sourceAt(i); getSourceConsumer(source.getType()).updateSource(source); } for (int i = mState.getSourcesCount() - 1; i >= 0; i--) { InsetsSource source = mState.sourceAt(i); if (newState.peekSource(source.getType()) == null) { mState.removeSource(source.getType()); } } if (mCaptionInsetsHeight != 0) { if (mCaptionInsetsHeight != 0) { mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); mFrame.right, mFrame.top + mCaptionInsetsHeight)); } } return true; } } private boolean captionInsetsUnchanged() { private boolean captionInsetsUnchanged() { Loading Loading @@ -879,8 +894,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation control.cancel(); control.cancel(); } } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { if (mRunningAnimations.get(i).runner == control) { RunningAnimation runningAnimation = mRunningAnimations.get(i); if (runningAnimation.runner == control) { mRunningAnimations.remove(i); mRunningAnimations.remove(i); ArraySet<Integer> types = toInternalType(control.getTypes()); for (int j = types.size() - 1; j >= 0; j--) { if (getSourceConsumer(types.valueAt(j)).notifyAnimationFinished()) { mViewRoot.notifyInsetsChanged(); } } break; break; } } } } Loading core/java/android/view/InsetsSourceConsumer.java +36 −0 Original line number Original line Diff line number Diff line Loading @@ -16,11 +16,13 @@ package android.view; package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.toPublicType; import static android.view.InsetsState.toPublicType; import android.annotation.IntDef; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.Nullable; import android.graphics.Rect; import android.view.InsetsState.InternalInsetsType; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsets.Type.InsetsType; Loading Loading @@ -64,6 +66,8 @@ public class InsetsSourceConsumer { private final Supplier<Transaction> mTransactionSupplier; private final Supplier<Transaction> mTransactionSupplier; private @Nullable InsetsSourceControl mSourceControl; private @Nullable InsetsSourceControl mSourceControl; private boolean mHasWindowFocus; private boolean mHasWindowFocus; private Rect mPendingFrame; private Rect mPendingVisibleFrame; public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { Supplier<Transaction> transactionSupplier, InsetsController controller) { Loading Loading @@ -215,6 +219,38 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } } void updateSource(InsetsSource newSource) { InsetsSource source = mState.peekSource(mType); if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mState.addSource(newSource); return; } // Frame is changing while animating. Keep note of the new frame but keep existing frame // until animaition is finished. newSource = new InsetsSource(newSource); mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null ? new Rect(newSource.getVisibleFrame()) : null; newSource.setFrame(source.getFrame()); newSource.setVisibleFrame(source.getVisibleFrame()); mState.addSource(newSource); } boolean notifyAnimationFinished() { if (mPendingFrame != null) { InsetsSource source = mState.getSource(mType); source.setFrame(mPendingFrame); source.setVisibleFrame(mPendingVisibleFrame); mPendingFrame = null; mPendingVisibleFrame = null; return true; } return false; } /** /** * Sets requested visibility from the client, regardless of whether we are able to control it at * Sets requested visibility from the client, regardless of whether we are able to control it at * the moment. * the moment. Loading core/tests/coretests/src/android/view/InsetsControllerTest.java +25 −0 Original line number Original line Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; Loading Loading @@ -638,7 +639,31 @@ public class InsetsControllerTest { }); }); } } @Test public void testFrameUpdateDuringAnimation() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.onControlsChanged(createSingletonControl(ITYPE_IME)); // Pretend IME is calling mController.show(ime(), true /* fromIme */); InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3); copy.getSource(ITYPE_IME).setVisibleFrame(new Rect(4, 5, 6, 7)); mController.onStateChanged(copy); assertNotEquals(new Rect(0, 1, 2, 3), mController.getState().getSource(ITYPE_IME).getFrame()); assertNotEquals(new Rect(4, 5, 6, 7), mController.getState().getSource(ITYPE_IME).getVisibleFrame()); mController.cancelExistingAnimation(); assertEquals(new Rect(0, 1, 2, 3), mController.getState().getSource(ITYPE_IME).getFrame()); assertEquals(new Rect(4, 5, 6, 7), mController.getState().getSource(ITYPE_IME).getVisibleFrame()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @Test @Test public void testCaptionInsetsStateAssemble() { public void testCaptionInsetsStateAssemble() { Loading tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +37 −54 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.google.android.test.windowinsetstests; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static java.lang.Math.max; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.min; Loading @@ -31,6 +30,7 @@ import android.content.Context; import android.graphics.Insets; import android.graphics.Insets; import android.os.Bundle; import android.os.Bundle; import android.util.AttributeSet; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewConfiguration; Loading @@ -44,11 +44,11 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.animation.LinearInterpolator; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.LinearLayout; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; import androidx.appcompat.app.AppCompatActivity; public class WindowInsetsActivity extends AppCompatActivity { public class WindowInsetsActivity extends AppCompatActivity { private View mRoot; private View mRoot; Loading Loading @@ -191,28 +191,9 @@ public class WindowInsetsActivity extends AppCompatActivity { mTransitions.forEach(it -> it.onFinish(animation)); mTransitions.forEach(it -> it.onFinish(animation)); } } }); }); } @Override public void onResume() { super.onResume(); // TODO: move this to onCreate once setDecorFitsSystemWindows can be safely called there. getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); getWindow().getInsetsController().addOnControllableInsetsChangedListener( new OnControllableInsetsChangedListener() { boolean hasControl = false; findViewById(R.id.floating_action_button).setOnClickListener( @Override v -> v.getWindowInsetsController().controlWindowInsetsAnimation(ime(), -1, public void onControllableInsetsChanged(WindowInsetsController controller, int types) { if ((types & ime()) != 0 && !hasControl) { hasControl = true; controller.controlWindowInsetsAnimation(ime(), -1, new LinearInterpolator(), null /* cancellationSignal */, new LinearInterpolator(), null /* cancellationSignal */, new WindowInsetsAnimationControlListener() { new WindowInsetsAnimationControlListener() { @Override @Override Loading @@ -237,18 +218,20 @@ public class WindowInsetsActivity extends AppCompatActivity { } } @Override @Override public void onFinished( public void onCancelled(WindowInsetsAnimationController controller) { WindowInsetsAnimationController controller) { } } @Override @Override public void onCancelled( public void onFinished(WindowInsetsAnimationController controller) { WindowInsetsAnimationController controller) { } }); } } })); } } }); @Override public void onResume() { super.onResume(); // TODO: move this to onCreate once setDecorFitsSystemWindows can be safely called there. getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); } } static class Transition { static class Transition { Loading Loading
core/java/android/view/InsetsController.java +25 −3 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.view; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toInternalType; import static android.view.InsetsState.toPublicType; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.ime; Loading Loading @@ -471,7 +472,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!localStateChanged && mLastDispachedState.equals(state)) { if (!localStateChanged && mLastDispachedState.equals(state)) { return false; return false; } } mState.set(state); updateState(state); mLastDispachedState.set(state, true /* copySources */); mLastDispachedState.set(state, true /* copySources */); applyLocalVisibilityOverride(); applyLocalVisibilityOverride(); if (localStateChanged) { if (localStateChanged) { Loading @@ -480,11 +481,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) { if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) { sendStateToWindowManager(); sendStateToWindowManager(); } } return true; } private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); for (int i = newState.getSourcesCount() - 1; i >= 0; i--) { InsetsSource source = newState.sourceAt(i); getSourceConsumer(source.getType()).updateSource(source); } for (int i = mState.getSourcesCount() - 1; i >= 0; i--) { InsetsSource source = mState.sourceAt(i); if (newState.peekSource(source.getType()) == null) { mState.removeSource(source.getType()); } } if (mCaptionInsetsHeight != 0) { if (mCaptionInsetsHeight != 0) { mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); mFrame.right, mFrame.top + mCaptionInsetsHeight)); } } return true; } } private boolean captionInsetsUnchanged() { private boolean captionInsetsUnchanged() { Loading Loading @@ -879,8 +894,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation control.cancel(); control.cancel(); } } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { if (mRunningAnimations.get(i).runner == control) { RunningAnimation runningAnimation = mRunningAnimations.get(i); if (runningAnimation.runner == control) { mRunningAnimations.remove(i); mRunningAnimations.remove(i); ArraySet<Integer> types = toInternalType(control.getTypes()); for (int j = types.size() - 1; j >= 0; j--) { if (getSourceConsumer(types.valueAt(j)).notifyAnimationFinished()) { mViewRoot.notifyInsetsChanged(); } } break; break; } } } } Loading
core/java/android/view/InsetsSourceConsumer.java +36 −0 Original line number Original line Diff line number Diff line Loading @@ -16,11 +16,13 @@ package android.view; package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.toPublicType; import static android.view.InsetsState.toPublicType; import android.annotation.IntDef; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.Nullable; import android.graphics.Rect; import android.view.InsetsState.InternalInsetsType; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsets.Type.InsetsType; Loading Loading @@ -64,6 +66,8 @@ public class InsetsSourceConsumer { private final Supplier<Transaction> mTransactionSupplier; private final Supplier<Transaction> mTransactionSupplier; private @Nullable InsetsSourceControl mSourceControl; private @Nullable InsetsSourceControl mSourceControl; private boolean mHasWindowFocus; private boolean mHasWindowFocus; private Rect mPendingFrame; private Rect mPendingVisibleFrame; public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { Supplier<Transaction> transactionSupplier, InsetsController controller) { Loading Loading @@ -215,6 +219,38 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } } void updateSource(InsetsSource newSource) { InsetsSource source = mState.peekSource(mType); if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mState.addSource(newSource); return; } // Frame is changing while animating. Keep note of the new frame but keep existing frame // until animaition is finished. newSource = new InsetsSource(newSource); mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null ? new Rect(newSource.getVisibleFrame()) : null; newSource.setFrame(source.getFrame()); newSource.setVisibleFrame(source.getVisibleFrame()); mState.addSource(newSource); } boolean notifyAnimationFinished() { if (mPendingFrame != null) { InsetsSource source = mState.getSource(mType); source.setFrame(mPendingFrame); source.setVisibleFrame(mPendingVisibleFrame); mPendingFrame = null; mPendingVisibleFrame = null; return true; } return false; } /** /** * Sets requested visibility from the client, regardless of whether we are able to control it at * Sets requested visibility from the client, regardless of whether we are able to control it at * the moment. * the moment. Loading
core/tests/coretests/src/android/view/InsetsControllerTest.java +25 −0 Original line number Original line Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; Loading Loading @@ -638,7 +639,31 @@ public class InsetsControllerTest { }); }); } } @Test public void testFrameUpdateDuringAnimation() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.onControlsChanged(createSingletonControl(ITYPE_IME)); // Pretend IME is calling mController.show(ime(), true /* fromIme */); InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3); copy.getSource(ITYPE_IME).setVisibleFrame(new Rect(4, 5, 6, 7)); mController.onStateChanged(copy); assertNotEquals(new Rect(0, 1, 2, 3), mController.getState().getSource(ITYPE_IME).getFrame()); assertNotEquals(new Rect(4, 5, 6, 7), mController.getState().getSource(ITYPE_IME).getVisibleFrame()); mController.cancelExistingAnimation(); assertEquals(new Rect(0, 1, 2, 3), mController.getState().getSource(ITYPE_IME).getFrame()); assertEquals(new Rect(4, 5, 6, 7), mController.getState().getSource(ITYPE_IME).getVisibleFrame()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @Test @Test public void testCaptionInsetsStateAssemble() { public void testCaptionInsetsStateAssemble() { Loading
tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +37 −54 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.google.android.test.windowinsetstests; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static java.lang.Math.max; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.min; Loading @@ -31,6 +30,7 @@ import android.content.Context; import android.graphics.Insets; import android.graphics.Insets; import android.os.Bundle; import android.os.Bundle; import android.util.AttributeSet; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewConfiguration; Loading @@ -44,11 +44,11 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.animation.LinearInterpolator; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.LinearLayout; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; import androidx.appcompat.app.AppCompatActivity; public class WindowInsetsActivity extends AppCompatActivity { public class WindowInsetsActivity extends AppCompatActivity { private View mRoot; private View mRoot; Loading Loading @@ -191,28 +191,9 @@ public class WindowInsetsActivity extends AppCompatActivity { mTransitions.forEach(it -> it.onFinish(animation)); mTransitions.forEach(it -> it.onFinish(animation)); } } }); }); } @Override public void onResume() { super.onResume(); // TODO: move this to onCreate once setDecorFitsSystemWindows can be safely called there. getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); getWindow().getInsetsController().addOnControllableInsetsChangedListener( new OnControllableInsetsChangedListener() { boolean hasControl = false; findViewById(R.id.floating_action_button).setOnClickListener( @Override v -> v.getWindowInsetsController().controlWindowInsetsAnimation(ime(), -1, public void onControllableInsetsChanged(WindowInsetsController controller, int types) { if ((types & ime()) != 0 && !hasControl) { hasControl = true; controller.controlWindowInsetsAnimation(ime(), -1, new LinearInterpolator(), null /* cancellationSignal */, new LinearInterpolator(), null /* cancellationSignal */, new WindowInsetsAnimationControlListener() { new WindowInsetsAnimationControlListener() { @Override @Override Loading @@ -237,18 +218,20 @@ public class WindowInsetsActivity extends AppCompatActivity { } } @Override @Override public void onFinished( public void onCancelled(WindowInsetsAnimationController controller) { WindowInsetsAnimationController controller) { } } @Override @Override public void onCancelled( public void onFinished(WindowInsetsAnimationController controller) { WindowInsetsAnimationController controller) { } }); } } })); } } }); @Override public void onResume() { super.onResume(); // TODO: move this to onCreate once setDecorFitsSystemWindows can be safely called there. getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); } } static class Transition { static class Transition { Loading