Merge "API update for insets"
diff --git a/api/current.txt b/api/current.txt
index d8de383..7e7cd9d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -51082,6 +51082,9 @@
method public boolean dispatchUnhandledMove(android.view.View, int);
method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
method public void dispatchWindowFocusChanged(boolean);
+ method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
+ method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
method public void dispatchWindowSystemUiVisiblityChanged(int);
method public void dispatchWindowVisibilityChanged(int);
method @CallSuper public void draw(android.graphics.Canvas);
@@ -51262,6 +51265,7 @@
method @android.view.ViewDebug.ExportedProperty(category="layout") public final int getWidth();
method protected int getWindowAttachCount();
method public android.view.WindowId getWindowId();
+ method @Nullable public android.view.WindowInsetsController getWindowInsetsController();
method public int getWindowSystemUiVisibility();
method public android.os.IBinder getWindowToken();
method public int getWindowVisibility();
@@ -51600,6 +51604,7 @@
method public void setVisibility(int);
method @Deprecated public void setWillNotCacheDrawing(boolean);
method public void setWillNotDraw(boolean);
+ method public void setWindowInsetsAnimationCallback(@Nullable android.view.WindowInsetsAnimationCallback);
method public void setX(float);
method public void setY(float);
method public void setZ(float);
@@ -52486,6 +52491,7 @@
method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
method protected final int getForcedWindowFlags();
+ method @Nullable public android.view.WindowInsetsController getInsetsController();
method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
method public android.media.session.MediaController getMediaController();
@@ -52701,7 +52707,9 @@
method @NonNull public android.view.WindowInsets consumeStableInsets();
method @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
method @Nullable public android.view.DisplayCutout getDisplayCutout();
+ method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+ method @NonNull public android.graphics.Insets getMaxInsets(int) throws java.lang.IllegalArgumentException;
method public int getStableInsetBottom();
method public int getStableInsetLeft();
method public int getStableInsetRight();
@@ -52720,6 +52728,7 @@
method @NonNull public android.view.WindowInsets inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
method public boolean isConsumed();
method public boolean isRound();
+ method public boolean isVisible(int);
method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int);
method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect);
}
@@ -52729,11 +52738,76 @@
ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
method @NonNull public android.view.WindowInsets build();
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+ method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setMaxInsets(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setTappableElementInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setVisible(int, boolean);
+ }
+
+ public static final class WindowInsets.Type {
+ method public static int all();
+ method public static int captionBar();
+ method public static int ime();
+ method public static int mandatorySystemGestures();
+ method public static int navigationBars();
+ method public static int statusBars();
+ method public static int systemBars();
+ method public static int systemGestures();
+ method public static int tappableElement();
+ method public static int windowDecor();
+ }
+
+ public interface WindowInsetsAnimationCallback {
+ method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
+ method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+ }
+
+ public static final class WindowInsetsAnimationCallback.AnimationBounds {
+ ctor public WindowInsetsAnimationCallback.AnimationBounds(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
+ method @NonNull public android.graphics.Insets getLowerBound();
+ method @NonNull public android.graphics.Insets getUpperBound();
+ method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds inset(@NonNull android.graphics.Insets);
+ }
+
+ public static final class WindowInsetsAnimationCallback.InsetsAnimation {
+ ctor public WindowInsetsAnimationCallback.InsetsAnimation(int, @Nullable android.view.animation.Interpolator, long);
+ method public long getDurationMillis();
+ method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
+ method public float getInterpolatedFraction();
+ method @Nullable public android.view.animation.Interpolator getInterpolator();
+ method public int getTypeMask();
+ method public void setDuration(long);
+ method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public interface WindowInsetsAnimationControlListener {
+ method public void onCancelled();
+ method public void onReady(@NonNull android.view.WindowInsetsAnimationController, int);
+ }
+
+ public interface WindowInsetsAnimationController {
+ method public void finish(boolean);
+ method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
+ method @NonNull public android.graphics.Insets getCurrentInsets();
+ method @NonNull public android.graphics.Insets getHiddenStateInsets();
+ method @NonNull public android.graphics.Insets getShownStateInsets();
+ method public int getTypes();
+ method public void setInsetsAndAlpha(@Nullable android.graphics.Insets, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public interface WindowInsetsController {
+ method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener);
+ method public default void hideInputMethod();
+ method public void setSystemBarsAppearance(int);
+ method public void setSystemBarsBehavior(int);
+ method public default void showInputMethod();
+ field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10
+ field public static final int APPEARANCE_LIGHT_STATUS_BARS = 8; // 0x8
}
public interface WindowManager extends android.view.ViewManager {
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 28eb79a..71ac578 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,13 +16,13 @@
package android.view;
-import static android.view.DisplayEventReceiver.CONFIG_CHANGED_EVENT_SUPPRESS;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.graphics.FrameInfo;
+import android.graphics.Insets;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
@@ -219,9 +219,10 @@
/**
* Callback type: Animation callback to handle inset updates. This is separate from
* {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
- * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
- * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
- * that contains all the combined updated insets.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} for multiple
+ * ongoing animations but then update the whole view system with a single callback to
+ * {@link View#dispatchWindowInsetsAnimationProgress} that contains all the combined updated
+ * insets.
* <p>
* Both input and animation may change insets, so we need to run this after these callbacks, but
* before traversals.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 3d139cd..7ea4f30 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -21,7 +21,6 @@
import static android.view.InsetsState.ISIDE_LEFT;
import static android.view.InsetsState.ISIDE_RIGHT;
import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.toPublicType;
import android.annotation.Nullable;
import android.graphics.Insets;
@@ -34,7 +33,8 @@
import android.view.InsetsState.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.WindowManager.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,20 +66,21 @@
private final @InsetsType int mTypes;
private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
private final InsetsController mController;
- private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
+ private final WindowInsetsAnimationCallback.InsetsAnimation mAnimation;
private final Rect mFrame;
private Insets mCurrentInsets;
private Insets mPendingInsets;
+ private float mPendingFraction;
private boolean mFinished;
private boolean mCancelled;
- private int mFinishedShownTypes;
+ private boolean mShownOnFinish;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
- InsetsController controller) {
+ InsetsController controller, long durationMs) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
@@ -97,9 +98,10 @@
// TODO: Check for controllability first and wait for IME if needed.
listener.onReady(this, types);
- mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
- mShownInsets);
- mController.dispatchAnimationStarted(mAnimation);
+ mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
+ InsetsController.INTERPOLATOR, durationMs);
+ mController.dispatchAnimationStarted(mAnimation,
+ new AnimationBounds(mHiddenInsets, mShownInsets));
}
@Override
@@ -123,7 +125,7 @@
}
@Override
- public void changeInsets(Insets insets) {
+ public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
if (mFinished) {
throw new IllegalStateException(
"Can't change insets on an animation that is finished.");
@@ -132,6 +134,7 @@
throw new IllegalStateException(
"Can't change insets on an animation that is cancelled.");
}
+ mPendingFraction = sanitize(fraction);
mPendingInsets = sanitize(insets);
mController.scheduleApplyChangeInsets();
}
@@ -155,30 +158,35 @@
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
+ mAnimation.setFraction(mPendingFraction);
if (mFinished) {
- mController.notifyFinished(this, mFinishedShownTypes);
+ mController.notifyFinished(this, mShownOnFinish);
}
return mFinished;
}
@Override
- public void finish(int shownTypes) {
+ public void finish(boolean shown) {
if (mCancelled) {
return;
}
InsetsState state = new InsetsState(mController.getState());
for (int i = mConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mConsumers.valueAt(i);
- boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0;
- state.getSource(consumer.getType()).setVisible(visible);
+ state.getSource(consumer.getType()).setVisible(shown);
}
Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */);
- changeInsets(insets);
+ setInsetsAndAlpha(insets, 1f /* alpha */, shown ? 1f : 0f /* fraction */);
mFinished = true;
- mFinishedShownTypes = shownTypes;
+ mShownOnFinish = shown;
}
+ @Override
@VisibleForTesting
+ public float getCurrentFraction() {
+ return mAnimation.getFraction();
+ }
+
public void onCancelled() {
if (mFinished) {
return;
@@ -191,6 +199,10 @@
return mAnimation;
}
+ WindowInsetsAnimationControlListener getListener() {
+ return mListener;
+ }
+
private Insets calculateInsets(InsetsState state, Rect frame,
SparseArray<InsetsSourceConsumer> consumers, boolean shown,
@Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
@@ -210,9 +222,16 @@
}
private Insets sanitize(Insets insets) {
+ if (insets == null) {
+ insets = getCurrentInsets();
+ }
return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
}
+ private static float sanitize(float alpha) {
+ return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
+ }
+
private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 43fec82..8870311 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -39,6 +39,8 @@
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -55,11 +57,12 @@
private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340;
- private static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
private static final int DIRECTION_NONE = 0;
private static final int DIRECTION_SHOW = 1;
private static final int DIRECTION_HIDE = 2;
+ static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
@IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
private @interface AnimationDirection{}
@@ -85,8 +88,75 @@
return object.getCurrentInsets();
}
@Override
- public void set(WindowInsetsAnimationController object, Insets value) {
- object.changeInsets(value);
+ public void set(WindowInsetsAnimationController controller, Insets value) {
+ controller.setInsetsAndAlpha(
+ value, 1f /* alpha */, (((DefaultAnimationControlListener)
+ ((InsetsAnimationControlImpl) controller).getListener())
+ .getRawProgress()));
+ }
+ }
+
+ private class DefaultAnimationControlListener implements WindowInsetsAnimationControlListener {
+
+ private WindowInsetsAnimationController mController;
+ private ObjectAnimator mAnimator;
+ private boolean mShow;
+
+ DefaultAnimationControlListener(boolean show) {
+ mShow = show;
+ }
+
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ mController = controller;
+ if (mShow) {
+ showDirectly(types);
+ } else {
+ hideDirectly(types);
+ }
+ mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
+ mAnimator = ObjectAnimator.ofObject(
+ controller,
+ new InsetsProperty(),
+ sEvaluator,
+ mShow ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+ mShow ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+ );
+ mAnimator.setDuration(getDurationMs());
+ mAnimator.setInterpolator(INTERPOLATOR);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationFinish();
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ public void onCancelled() {
+ // Animator can be null when it is cancelled before onReady() completes.
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+
+ private void onAnimationFinish() {
+ mAnimationDirection = DIRECTION_NONE;
+ mController.finish(mShow);
+ }
+
+ private float getRawProgress() {
+ float fraction = (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
+ return mShow ? fraction : 1 - fraction;
+ }
+
+ private long getDurationMs() {
+ if (mAnimator != null) {
+ return mAnimator.getDuration();
+ }
+ return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
}
}
@@ -278,24 +348,25 @@
}
@Override
- public void controlWindowInsetsAnimation(@InsetsType int types,
+ public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(types, listener, false /* fromIme */);
+ controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
}
private void controlWindowInsetsAnimation(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, boolean fromIme) {
+ WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
// If the frame of our window doesn't span the entire display, the control API makes very
// little sense, as we don't deal with negative insets. So just cancel immediately.
if (!mState.getDisplayFrame().equals(mFrame)) {
listener.onCancelled();
return;
}
- controlAnimationUnchecked(types, listener, mFrame, fromIme);
+ controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs);
}
private void controlAnimationUnchecked(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) {
+ WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
+ long durationMs) {
if (types == 0) {
// nothing to animate.
return;
@@ -326,7 +397,7 @@
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
frame, mState, listener, typesReady,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs);
mAnimationControls.add(controller);
}
@@ -397,10 +468,13 @@
}
@VisibleForTesting
- public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) {
+ public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
mAnimationControls.remove(controller);
- hideDirectly(controller.getTypes() & ~shownTypes);
- showDirectly(controller.getTypes() & shownTypes);
+ if (shown) {
+ showDirectly(controller.getTypes());
+ } else {
+ hideDirectly(controller.getTypes());
+ }
}
void notifyControlRevoked(InsetsSourceConsumer consumer) {
@@ -510,58 +584,11 @@
return;
}
- WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
-
- private WindowInsetsAnimationController mController;
- private ObjectAnimator mAnimator;
-
- @Override
- public void onReady(WindowInsetsAnimationController controller, int types) {
- mController = controller;
- if (show) {
- showDirectly(types);
- } else {
- hideDirectly(types);
- }
- mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
- mAnimator = ObjectAnimator.ofObject(
- controller,
- new InsetsProperty(),
- sEvaluator,
- show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
- show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
- );
- mAnimator.setDuration(show
- ? ANIMATION_DURATION_SHOW_MS
- : ANIMATION_DURATION_HIDE_MS);
- mAnimator.setInterpolator(INTERPOLATOR);
- mAnimator.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onAnimationFinish();
- }
- });
- mAnimator.start();
- }
-
- @Override
- public void onCancelled() {
- // Animator can be null when it is cancelled before onReady() completes.
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- }
-
- private void onAnimationFinish() {
- mAnimationDirection = DIRECTION_NONE;
- mController.finish(show ? types : 0);
- }
- };
-
+ final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show);
// Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct.
- controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme);
+ controlAnimationUnchecked(
+ types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs());
}
private void hideDirectly(@InsetsType int types) {
@@ -592,12 +619,12 @@
}
@VisibleForTesting
- public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
- mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
+ public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
+ mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
}
@VisibleForTesting
- public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ public void dispatchAnimationFinished(InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d4f3878..2d0b954 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,7 +109,8 @@
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -4626,7 +4627,7 @@
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
- private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
+ private WindowInsetsAnimationCallback mWindowInsetsAnimationCallback;
/**
* This lives here since it's only valid for interactive views.
@@ -11091,33 +11092,55 @@
}
/**
- * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
+ * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that
* cause insets.
*
* @param listener The listener to set.
- * @hide pending unhide
*/
- public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
- getListenerInfo().mWindowInsetsAnimationListener = listener;
+ public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) {
+ getListenerInfo().mWindowInsetsAnimationCallback = listener;
}
- void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+ * when Window Insets animation is started.
+ * @param animation current animation
+ * @param bounds the upper and lower {@link AnimationBounds} that provides range of
+ * {@link InsetsAnimation}.
+ * @return the upper and lower {@link AnimationBounds}.
+ */
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
}
+ return bounds;
}
- WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onProgress(WindowInsets)}
+ * when Window Insets animation makes progress.
+ * @param insets The current {@link WindowInsets}.
+ * @return current {@link WindowInsets}.
+ */
+ @NonNull
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ return mListenerInfo.mWindowInsetsAnimationCallback.onProgress(insets);
} else {
return insets;
}
}
- void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+ * when Window Insets animation finishes.
+ * @param animation The current ongoing {@link InsetsAnimation}.
+ */
+ public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
}
}
@@ -11253,7 +11276,6 @@
* @return The {@link WindowInsetsController} or {@code null} if the view isn't attached to a
* a window.
* @see Window#getInsetsController()
- * @hide pending unhide
*/
public @Nullable WindowInsetsController getWindowInsetsController() {
if (mAttachInfo != null) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 853a302..4334bb5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -51,7 +51,8 @@
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -7198,16 +7199,20 @@
}
@Override
- void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
- super.dispatchWindowInsetsAnimationStarted(animation);
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ super.dispatchWindowInsetsAnimationStarted(animation, bounds);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
- getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
+ getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
}
+ return bounds;
}
@Override
- WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+ @NonNull
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
insets = super.dispatchWindowInsetsAnimationProgress(insets);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
@@ -7217,7 +7222,7 @@
}
@Override
- void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+ public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
super.dispatchWindowInsetsAnimationFinished(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index af1882b..ff31115 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2573,7 +2573,8 @@
/**
* @return The {@link WindowInsetsController} associated with this window
* @see View#getWindowInsetsController()
- * @hide pending unhide
*/
- public abstract @NonNull WindowInsetsController getInsetsController();
+ public @Nullable WindowInsetsController getInsetsController() {
+ return null;
+ }
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index b16a4ca..2404c84 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -177,7 +177,7 @@
* @return The insets that include system bars indicated by {@code typeMask}, taken from
* {@code typeInsetsMap}.
*/
- private static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
+ static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
Insets result = null;
for (int i = FIRST; i <= LAST; i = i << 1) {
if ((typeMask & i) == 0) {
@@ -289,9 +289,8 @@
*
* @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
* @return The insets.
- *
- * @hide pending unhide
*/
+ @NonNull
public Insets getInsets(@InsetsType int typeMask) {
return getInsets(mTypeInsetsMap, typeMask);
}
@@ -313,8 +312,8 @@
* insets are not available for this type as the height of the
* IME is dynamic depending on the {@link EditorInfo} of the
* currently focused view, as well as the UI state of the IME.
- * @hide pending unhide
*/
+ @NonNull
public Insets getMaxInsets(@InsetsType int typeMask) throws IllegalArgumentException {
if ((typeMask & IME) != 0) {
throw new IllegalArgumentException("Unable to query the maximum insets for IME");
@@ -329,7 +328,6 @@
* @param typeMask Bit mask of {@link Type.InsetsType}s to query visibility status.
* @return {@code true} if and only if all windows included in {@code typeMask} are currently
* visible on screen.
- * @hide pending unhide
*/
public boolean isVisible(@InsetsType int typeMask) {
for (int i = FIRST; i <= LAST; i = i << 1) {
@@ -874,7 +872,7 @@
return typeInsetsMap;
}
- private static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
+ static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
int newLeft = Math.max(0, insets.left - left);
int newTop = Math.max(0, insets.top - top);
int newRight = Math.max(0, insets.right - right);
@@ -1015,7 +1013,6 @@
* @param insets The insets to set.
*
* @return itself
- * @hide pending unhide
*/
@NonNull
public Builder setInsets(@InsetsType int typeMask, @NonNull Insets insets) {
@@ -1046,7 +1043,6 @@
* the IME is dynamic depending on the {@link EditorInfo}
* of the currently focused view, as well as the UI
* state of the IME.
- * @hide pending unhide
*/
@NonNull
public Builder setMaxInsets(@InsetsType int typeMask, @NonNull Insets insets)
@@ -1070,7 +1066,6 @@
* @param visible Whether to mark the windows as visible or not.
*
* @return itself
- * @hide pending unhide
*/
@NonNull
public Builder setVisible(@InsetsType int typeMask, boolean visible) {
@@ -1145,7 +1140,6 @@
/**
* Class that defines different types of sources causing window insets.
- * @hide pending unhide
*/
public static final class Type {
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
new file mode 100644
index 0000000..8ae8520
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -0,0 +1,263 @@
+/*
+ * 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 android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.animation.Interpolator;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ */
+public interface WindowInsetsAnimationCallback {
+
+ /**
+ * Called when an inset animation gets started.
+ * <p>
+ * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
+ * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
+ * of the specific {@link View} that is being traversed. The method my return a modified
+ * instance of the bounds by calling {@link AnimationBounds#inset} to indicate that a part of
+ * the insets have been used to offset or clip its children, and the children shouldn't worry
+ * about that part anymore.
+ *
+ * @param animation The animation that is about to start.
+ * @param bounds The bounds in which animation happens.
+ * @return The animation representing the part of the insets that should be dispatched to the
+ * subtree of the hierarchy.
+ */
+ @NonNull
+ default AnimationBounds onStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ return bounds;
+ }
+
+ /**
+ * Called when the insets change as part of running an animation. Note that even if multiple
+ * animations for different types are running, there will only be one progress callback per
+ * frame. The {@code insets} passed as an argument represents the overall state and will include
+ * all types, regardless of whether they are animating or not.
+ * <p>
+ * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+ * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+ * The method may return a modified instance by calling
+ * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
+ * been used to offset or clip its children, and the children shouldn't worry about that part
+ * anymore.
+ * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation
+ * for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type).
+ * Or on the controller directly?
+ * @param insets The current insets.
+ * @return The insets to dispatch to the subtree of the hierarchy.
+ */
+ @NonNull
+ WindowInsets onProgress(@NonNull WindowInsets insets);
+
+ /**
+ * Called when an inset animation has finished.
+ *
+ * @param animation The animation that has finished running. This will be the same instance as
+ * passed into {@link #onStarted}
+ */
+ default void onFinished(@NonNull InsetsAnimation animation) {
+ }
+
+ /**
+ * Class representing an animation of a set of windows that cause insets.
+ */
+ final class InsetsAnimation {
+
+ private final @InsetsType int mTypeMask;
+ private float mFraction;
+ @Nullable private final Interpolator mInterpolator;
+ private long mDurationMs;
+
+ public InsetsAnimation(
+ @InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
+ mTypeMask = typeMask;
+ mInterpolator = interpolator;
+ mDurationMs = durationMs;
+ }
+
+ /**
+ * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
+ */
+ public @InsetsType int getTypeMask() {
+ return mTypeMask;
+ }
+
+ /**
+ * Returns the raw fractional progress of this animation between
+ * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+ * that this progress is the global progress of the animation, whereas
+ * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+ * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+ * Progress per insets animation is global for the entire animation. One animation animates
+ * all things together (in, out, ...). If they don't animate together, we'd have
+ * multiple animations.
+ *
+ * @return The current progress of this animation.
+ */
+ @FloatRange(from = 0f, to = 1f)
+ public float getFraction() {
+ return mFraction;
+ }
+
+ /**
+ * Returns the interpolated fractional progress of this animation between
+ * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+ * that this progress is the global progress of the animation, whereas
+ * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+ * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+ * Progress per insets animation is global for the entire animation. One animation animates
+ * all things together (in, out, ...). If they don't animate together, we'd have
+ * multiple animations.
+ * @see #getFraction() for raw fraction.
+ * @return The current interpolated progress of this animation. -1 if interpolator isn't
+ * specified.
+ */
+ public float getInterpolatedFraction() {
+ if (mInterpolator != null) {
+ return mInterpolator.getInterpolation(mFraction);
+ }
+ return -1;
+ }
+
+ @Nullable
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+ */
+ public long getDurationMillis() {
+ return mDurationMs;
+ }
+
+ /**
+ * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is
+ * controlled by the app {@see #getCurrentFraction}.
+ * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set progress either.
+ * Progress would be set by system with the system-default animation.
+ * </p>
+ * @param fraction fractional progress between 0 and 1 where 0 represents hidden and
+ * zero progress and 1 represent fully shown final state.
+ */
+ public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * Set duration of the animation if {@link WindowInsets.Type.InsetsType} animation is
+ * controlled by the app.
+ * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set duration either.
+ * Duration would be set by system with the system-default animation.
+ * </p>
+ * @param durationMs in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ */
+ public void setDuration(long durationMs) {
+ mDurationMs = durationMs;
+ }
+ }
+
+ /**
+ * Class representing the range of an {@link InsetsAnimation}
+ */
+ final class AnimationBounds {
+ private final Insets mLowerBound;
+ private final Insets mUpperBound;
+
+ public AnimationBounds(@NonNull Insets lowerBound, @NonNull Insets upperBound) {
+ mLowerBound = lowerBound;
+ mUpperBound = upperBound;
+ }
+
+ /**
+ * Queries the lower inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+ * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+ * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * </p>
+ * There are no overlapping animations for a specific type, but there may be multiple
+ * animations running at the same time for different inset types.
+ *
+ * @see #getUpperBound()
+ * @see WindowInsetsAnimationController#getHiddenStateInsets
+ */
+ @NonNull
+ public Insets getLowerBound() {
+ return mLowerBound;
+ }
+
+ /**
+ * Queries the upper inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE}
+ * nd the upper bound is the same as {@link WindowInsets#getInsets(int)} for the fully
+ * shown state. This is the same as
+ * {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * <p>
+ * There are no overlapping animations for a specific type, but there may be multiple
+ * animations running at the same time for different inset types.
+ *
+ * @see #getLowerBound()
+ * @see WindowInsetsAnimationController#getShownStateInsets
+ */
+ @NonNull
+ public Insets getUpperBound() {
+ return mUpperBound;
+ }
+
+ /**
+ * Insets both the lower and upper bound by the specified insets. This is to be used in
+ * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+ * been used to offset or clip its children, and the children shouldn't worry about that
+ * part anymore.
+ *
+ * @param insets The amount to inset.
+ * @return A copy of this instance inset in the given directions.
+ * @see WindowInsets#inset
+ * @see WindowInsetsAnimationCallback#onStarted
+ */
+ @NonNull
+ public AnimationBounds inset(@NonNull Insets insets) {
+ return new AnimationBounds(
+ // TODO: refactor so that WindowInsets.insetInsets() is in a more appropriate
+ // place eventually.
+ WindowInsets.insetInsets(
+ mLowerBound, insets.left, insets.top, insets.right, insets.bottom),
+ WindowInsets.insetInsets(
+ mUpperBound, insets.left, insets.top, insets.right, insets.bottom));
+ }
+ }
+}
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 33fb327..8a226c1 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -22,19 +22,17 @@
/**
* Interface that informs the client about {@link WindowInsetsAnimationController} state changes.
- * @hide pending unhide
*/
public interface WindowInsetsAnimationControlListener {
/**
- * Gets called as soon as the animation is ready to be controlled. This may be
- * delayed when the IME needs to redraw because of an {@link EditorInfo} change, or when the
- * window is starting up.
+ * Called when the animation is ready to be controlled. This may be delayed when the IME needs
+ * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
*
* @param controller The controller to control the inset animation.
* @param types The {@link InsetsType}s it was able to gain control over. Note that this may be
* different than the types passed into
- * {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window
+ * {@link WindowInsetsController#controlInputMethodAnimation} in case the window
* wasn't able to gain the controls because it wasn't the IME target or not
* currently the window that's controlling the system bars.
*/
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 5cbf3b8..5149103 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -16,83 +16,127 @@
package android.view;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
/**
- * Interface to control a window inset animation frame-by-frame.
- * @hide pending unhide
+ * Controller for app-driven animation of system windows.
+ * <p>
+ * {@code WindowInsetsAnimationController} lets apps animate system windows such as
+ * the {@link android.inputmethodservice.InputMethodService IME}. The animation is
+ * synchronized, such that changes the system windows and the app's current frame
+ * are rendered at the same time.
+ * <p>
+ * Control is obtained through {@link WindowInsetsController#controlInputMethodAnimation}.
*/
+@SuppressLint("NotClosable")
public interface WindowInsetsAnimationController {
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
* <p>
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
+ * <p>
* If there are any animation listeners registered, this value is the same as
- * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
+ * {@link AnimationBounds#getLowerBound()} that is being be passed into the root view of the
+ * hierarchy.
*
* @return Insets when the windows this animation is controlling are fully hidden.
*
- * @see InsetsAnimation#getLowerBound()
+ * @see AnimationBounds#getLowerBound()
*/
@NonNull Insets getHiddenStateInsets();
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully shown.
* <p>
- * In case the size of a window causing insets is changing in the middle of the animation, we
- * execute that height change after this animation has finished.
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
* <p>
* If there are any animation listeners registered, this value is the same as
- * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
+ * {@link AnimationBounds#getUpperBound()} that is being passed into the root view of hierarchy.
*
* @return Insets when the windows this animation is controlling are fully shown.
*
- * @see InsetsAnimation#getUpperBound()
+ * @see AnimationBounds#getUpperBound()
*/
@NonNull Insets getShownStateInsets();
/**
- * @return The current insets on the window. These will follow any animation changes.
+ * Retrieves the current insets.
+ * <p>
+ * Note that these insets are always relative to the window, which is the same as
+ * being relative
+ * to {@link View#getRootView}
+ * @return The current insets on the currently showing frame. These insets will change as the
+ * animation progresses to reflect the current insets provided by the controlled window.
*/
@NonNull Insets getCurrentInsets();
/**
+ * Returns the progress as previously set by {@code fraction} in {@link #setInsetsAndAlpha}
+ *
+ * @return the progress of the animation, where {@code 0} is fully hidden and {@code 1} is
+ * fully shown.
+ * <p>
+ * Note: this value represents raw overall progress of the animation
+ * i.e. the combined progress of insets and alpha.
+ * <p>
+ */
+ @FloatRange(from = 0f, to = 1f)
+ float getCurrentFraction();
+
+ /**
* @return The {@link InsetsType}s this object is currently controlling.
*/
@InsetsType int getTypes();
/**
- * Modifies the insets by indirectly moving the windows around in the system that are causing
- * window insets.
+ * Modifies the insets for the frame being drawn by indirectly moving the windows around in the
+ * system that are causing window insets.
* <p>
- * Note that this will <b>not</b> inform the view system of a full inset change via
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
+ * <p>
+ * Also note that this will <b>not</b> inform the view system of a full inset change via
* {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
* animation. If you'd like to animate views during a window inset animation, register a
- * {@link WindowInsetsAnimationListener} by calling
- * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
- * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+ * {@link WindowInsetsAnimationCallback} by calling
+ * {@link View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)} that will be
+ * notified about any insets change via {@link WindowInsetsAnimationCallback#onProgress} during
* the animation.
* <p>
* {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
* finished, i.e. once {@link #finish} has been called.
+ * Note: If there are no insets, alpha animation is still applied.
*
* @param insets The new insets to apply. Based on the requested insets, the system will
* calculate the positions of the windows in the system causing insets such that
* the resulting insets of that configuration will match the passed in parameter.
* Note that these insets are being clamped to the range from
- * {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+ * {@link #getHiddenStateInsets} to {@link #getShownStateInsets}.
+ * If you intend on changing alpha only, pass null or {@link #getCurrentInsets()}.
+ * @param alpha The new alpha to apply to the inset side.
+ * @param fraction instantaneous animation progress. This value is dispatched to
+ * {@link WindowInsetsAnimationCallback}.
*
- * @see WindowInsetsAnimationListener
- * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
+ * @see WindowInsetsAnimationCallback
+ * @see View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)
*/
- void changeInsets(@NonNull Insets insets);
+ void setInsetsAndAlpha(@Nullable Insets insets, @FloatRange(from = 0f, to = 1f) float alpha,
+ @FloatRange(from = 0f, to = 1f) float fraction);
/**
- * @param shownTypes The list of windows causing insets that should remain shown after finishing
- * the animation.
+ * Finishes the animation, and leaves the windows shown or hidden. After invoking
+ * {@link #finish(boolean)}, this instance is no longer valid.
+ * @param shown if {@code true}, the windows will be shown after finishing the
+ * animation. Otherwise they will be hidden.
*/
- void finish(@InsetsType int shownTypes);
+ void finish(boolean shown);
}
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
deleted file mode 100644
index f734b4b..0000000
--- a/core/java/android/view/WindowInsetsAnimationListener.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.graphics.Insets;
-
-/**
- * Interface that allows the application to listen to animation events for windows that cause
- * insets.
- * @hide pending unhide
- */
-public interface WindowInsetsAnimationListener {
-
- /**
- * Called when an inset animation gets started.
- *
- * @param animation The animation that is about to start.
- */
- void onStarted(InsetsAnimation animation);
-
- /**
- * Called when the insets change as part of running an animation. Note that even if multiple
- * animations for different types are running, there will only be one progress callback per
- * frame. The {@code insets} passed as an argument represents the overall state and will include
- * all types, regardless of whether they are animating or not.
- * <p>
- * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
- * and then traverse it and invoke the callback of the specific {@link View} being traversed.
- * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
- * to indicate that a part of the insets have been used to offset or clip its children, and the
- * children shouldn't worry about that part anymore.
- *
- * @param insets The current insets.
- * @return The insets to dispatch to the subtree of the hierarchy.
- */
- WindowInsets onProgress(WindowInsets insets);
-
- /**
- * Called when an inset animation has finished.
- *
- * @param animation The animation that has finished running.
- */
- void onFinished(InsetsAnimation animation);
-
- /**
- * Class representing an animation of a set of windows that cause insets.
- */
- class InsetsAnimation {
-
- private final @WindowInsets.Type.InsetsType int mTypeMask;
- private final Insets mLowerBound;
- private final Insets mUpperBound;
-
- /**
- * @hide
- */
- InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
- mTypeMask = typeMask;
- mLowerBound = lowerBound;
- mUpperBound = upperBound;
- }
-
- /**
- * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
- */
- public @WindowInsets.Type.InsetsType int getTypeMask() {
- return mTypeMask;
- }
-
- /**
- * Queries the lower inset bound of the animation. If the animation is about showing or
- * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
- * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
- * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
- * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
- * invoked because of an animation that originates from
- * {@link WindowInsetsAnimationController}.
- * <p>
- * However, if the size of a window that causes insets is changing, these are the
- * lower/upper bounds of that size animation.
- * <p>
- * There are no overlapping animations for a specific type, but there may be two animations
- * running at the same time for different inset types.
- *
- * @see #getUpperBound()
- * @see WindowInsetsAnimationController#getHiddenStateInsets
- * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
- * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
- * we:
- * 1. Offer bounds by type here?
- * 2. Restrict one animation to one single type only?
- * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
- * fill in the insets for the types from the other animation into the WindowInsets object
- * as it's changing as well.
- */
- public Insets getLowerBound() {
- return mLowerBound;
- }
-
- /**
- * @see #getLowerBound()
- * @see WindowInsetsAnimationController#getShownStateInsets
- */
- public Insets getUpperBound() {
- return mUpperBound;
- }
- }
-}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index a045a6a..6de56be 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -30,7 +30,6 @@
* Interface to control windows that generate insets.
*
* TODO Needs more information and examples once the API is more baked.
- * @hide pending unhide
*/
public interface WindowInsetsController {
@@ -64,7 +63,10 @@
*/
int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
- /** Determines the appearance of system bars. */
+ /**
+ * Determines the appearance of system bars.
+ * @hide
+ */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
@@ -75,33 +77,40 @@
/**
* The default option for {@link #setSystemBarsBehavior(int)}. System bars will be forcibly
* shown on any user interaction on the corresponding display if navigation bars are hidden by
- * {@link #hide(int)} or {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link #hide(int)} or
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+ * @hide
*/
int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0;
/**
* Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
* hiding navigation bars by calling {@link #hide(int)} or
- * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
*
* <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
* as swiping from the edge of the screen where the bar is hidden from.</p>
+ * @hide
*/
int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
/**
* Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
* hiding navigation bars by calling {@link #hide(int)} or
- * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
*
* <p>When system bars are hidden in this mode, they can be revealed temporarily with system
* gestures, such as swiping from the edge of the screen where the bar is hidden from. These
* transient system bars will overlay app’s content, may have some degree of transparency, and
* will automatically hide after a short timeout.</p>
+ * @hide
*/
int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
- /** Determines the behavior of system bars when hiding them by calling {@link #hide}. */
+ /**
+ * Determines the behavior of system bars when hiding them by calling {@link #hide}.
+ * @hide
+ */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {BEHAVIOR_SHOW_BARS_BY_TOUCH, BEHAVIOR_SHOW_BARS_BY_SWIPE,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
@@ -139,23 +148,27 @@
* the position of the windows in the system causing insets directly.
*
* @param types The {@link InsetsType}s the application has requested to control.
+ * @param durationMillis duration of animation in
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
* @hide
*/
- void controlWindowInsetsAnimation(@InsetsType int types,
+ void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@NonNull WindowInsetsAnimationControlListener listener);
/**
* Lets the application control the animation for showing the IME in a frame-by-frame manner by
* modifying the position of the IME when it's causing insets.
*
+ * @param durationMillis duration of the animation in
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* IME are ready to be controlled, among other callbacks.
*/
- default void controlInputMethodAnimation(
+ default void controlInputMethodAnimation(long durationMillis,
@NonNull WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(ime(), listener);
+ controlWindowInsetsAnimation(ime(), durationMillis, listener);
}
/**
@@ -166,7 +179,7 @@
* the event by observing {@link View#onApplyWindowInsets} and checking visibility with
* {@link WindowInsets#isVisible}.
*
- * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
* @see #hideInputMethod()
*/
default void showInputMethod() {
@@ -181,7 +194,7 @@
* the event by observing {@link View#onApplyWindowInsets} and checking visibility with
* {@link WindowInsets#isVisible}.
*
- * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
* @see #showInputMethod()
*/
default void hideInputMethod() {
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 1a48260..cce38f6 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,11 +19,9 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -118,7 +116,7 @@
consumers.put(ITYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- () -> mMockTransactionApplier, mMockController);
+ () -> mMockTransactionApplier, mMockController, 10 /* durationMs */);
}
@Test
@@ -131,7 +129,8 @@
@Test
public void testChangeInsets() {
- mController.changeInsets(Insets.of(0, 30, 40, 0));
+ mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 1f /* alpha */,
+ 0f /* fraction */);
mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
@@ -150,24 +149,24 @@
@Test
public void testFinishing() {
when(mMockController.getState()).thenReturn(mInsetsState);
- mController.finish(navigationBars());
+ mController.finish(true /* shown */);
mController.applyChangeInsets(mInsetsState);
- assertFalse(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertTrue(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
assertTrue(mInsetsState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets());
- verify(mMockController).notifyFinished(eq(mController), eq(navigationBars()));
+ assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets());
+ verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */));
}
@Test
public void testCancelled() {
mController.onCancelled();
try {
- mController.changeInsets(Insets.NONE);
+ mController.setInsetsAndAlpha(Insets.NONE, 1f /*alpha */, 0f /* fraction */);
fail("Expected exception to be thrown");
} catch (IllegalStateException ignored) {
}
verify(mMockListener).onCancelled();
- mController.finish(navigationBars());
+ mController.finish(true /* shown */);
}
private void assertPosition(Matrix m, Rect original, Rect transformed) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index e4d8279..a89fc1e 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -123,7 +123,7 @@
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+ mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
verify(mockListener).onReady(any(), anyInt());
mController.onControlsChanged(new InsetsSourceControl[0]);
verify(mockListener).onCancelled();
@@ -135,7 +135,7 @@
mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
WindowInsetsAnimationControlListener controlListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(0, controlListener);
+ mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, controlListener);
verify(controlListener).onCancelled();
verify(controlListener, never()).onReady(any(), anyInt());
}
@@ -331,12 +331,13 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+ mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
+ mockListener);
ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
- controllerCaptor.getValue().finish(0 /* shownTypes */);
+ controllerCaptor.getValue().finish(false /* shown */);
});
waitUntilNextFrame();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {