diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/util/SparseSetArray.java | 7 | ||||
| -rw-r--r-- | core/java/android/view/InsetsAnimationControlImpl.java | 197 | ||||
| -rw-r--r-- | core/java/android/view/InsetsController.java | 33 | ||||
| -rw-r--r-- | core/java/android/view/InsetsSource.java | 3 | ||||
| -rw-r--r-- | core/java/android/view/InsetsState.java | 57 | ||||
| -rw-r--r-- | core/java/android/view/SyncRtSurfaceTransactionApplier.java | 151 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 1 | ||||
| -rw-r--r-- | core/java/android/view/WindowInsetsAnimationControlListener.java | 50 | ||||
| -rw-r--r-- | core/java/android/view/WindowInsetsAnimationController.java | 81 | ||||
| -rw-r--r-- | core/java/android/view/WindowInsetsController.java | 12 |
10 files changed, 584 insertions, 8 deletions
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java index d100f12ed026..680e85fa2ba8 100644 --- a/core/java/android/util/SparseSetArray.java +++ b/core/java/android/util/SparseSetArray.java @@ -55,6 +55,13 @@ public class SparseSetArray<T> { } /** + * @return the set of items at index n + */ + public ArraySet<T> get(int n) { + return mData.get(n); + } + + /** * Remove a value from index n. * @return TRUE when the value existed at the given index and removed, FALSE otherwise. */ diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java new file mode 100644 index 000000000000..7b9f78e70050 --- /dev/null +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -0,0 +1,197 @@ +/* + * 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 static android.view.InsetsState.INSET_SIDE_BOTTOM; +import static android.view.InsetsState.INSET_SIDE_LEFT; +import static android.view.InsetsState.INSET_SIDE_RIGHT; +import static android.view.InsetsState.INSET_SIDE_TOP; + +import android.annotation.Nullable; +import android.graphics.Insets; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.UidProto.Sync; +import android.util.ArraySet; +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.util.SparseSetArray; +import android.view.InsetsState.InsetSide; +import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; +import android.view.WindowInsets.Type.InsetType; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Implements {@link WindowInsetsAnimationController} + * @hide + */ +@VisibleForTesting +public class InsetsAnimationControlImpl implements WindowInsetsAnimationController { + + private final WindowInsetsAnimationControlListener mListener; + private final SparseArray<InsetsSourceConsumer> mConsumers; + private final SparseIntArray mTypeSideMap = new SparseIntArray(); + private final SparseSetArray<InsetsSourceConsumer> mSideSourceMap = new SparseSetArray<>(); + + /** @see WindowInsetsAnimationController#getHiddenStateInsets */ + private final Insets mHiddenInsets; + + /** @see WindowInsetsAnimationController#getShownStateInsets */ + private final Insets mShownInsets; + private final Matrix mTmpMatrix = new Matrix(); + private final InsetsState mInitialInsetsState; + private final @InsetType int mTypes; + private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier; + + private Insets mCurrentInsets; + + @VisibleForTesting + public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame, + InsetsState state, WindowInsetsAnimationControlListener listener, + @InsetType int types, + Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier) { + mConsumers = consumers; + mListener = listener; + mTypes = types; + mTransactionApplierSupplier = transactionApplierSupplier; + mInitialInsetsState = new InsetsState(state); + mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); + mHiddenInsets = calculateInsets(mInitialInsetsState, frame, consumers, false /* shown */, + null /* typeSideMap */); + mShownInsets = calculateInsets(mInitialInsetsState, frame, consumers, true /* shown */, + mTypeSideMap); + buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mConsumers); + + // TODO: Check for controllability first and wait for IME if needed. + listener.onReady(this, types); + } + + @Override + public Insets getHiddenStateInsets() { + return mHiddenInsets; + } + + @Override + public Insets getShownStateInsets() { + return mShownInsets; + } + + @Override + public Insets getCurrentInsets() { + return mCurrentInsets; + } + + @Override + @InsetType + public int getTypes() { + return mTypes; + } + + @Override + public void changeInsets(Insets insets) { + insets = sanitize(insets); + final Insets offset = Insets.subtract(mShownInsets, insets); + ArrayList<SurfaceParams> params = new ArrayList<>(); + if (offset.left != 0) { + updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params); + } + if (offset.top != 0) { + updateLeashesForSide(INSET_SIDE_TOP, offset.top, params); + } + if (offset.right != 0) { + updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params); + } + if (offset.bottom != 0) { + updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params); + } + SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get(); + applier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); + mCurrentInsets = insets; + } + + @Override + public void finish(int shownTypes) { + // TODO + } + + private Insets calculateInsets(InsetsState state, Rect frame, + SparseArray<InsetsSourceConsumer> consumers, boolean shown, + @Nullable @InsetSide SparseIntArray typeSideMap) { + for (int i = consumers.size() - 1; i >= 0; i--) { + state.getSource(consumers.valueAt(i).getType()).setVisible(shown); + } + return getInsetsFromState(state, frame, typeSideMap); + } + + private Insets getInsetsFromState(InsetsState state, Rect frame, + @Nullable @InsetSide SparseIntArray typeSideMap) { + return state.calculateInsets(frame, false /* isScreenRound */, + false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap) + .getSystemWindowInsets(); + } + + private Insets sanitize(Insets insets) { + return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets); + } + + private void updateLeashesForSide(@InsetSide int side, int inset, + ArrayList<SurfaceParams> surfaceParams) { + ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side); + // TODO: Implement behavior when inset spans over multiple types + for (int i = items.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer consumer = items.valueAt(i); + final InsetsSource source = mInitialInsetsState.getSource(consumer.getType()); + final SurfaceControl leash = consumer.getControl().getLeash(); + mTmpMatrix.setTranslate(source.getFrame().left, source.getFrame().top); + addTranslationToMatrix(side, inset, mTmpMatrix); + surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f)); + } + } + + private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m) { + switch (side) { + case INSET_SIDE_LEFT: + m.postTranslate(-inset, 0); + break; + case INSET_SIDE_TOP: + m.postTranslate(0, -inset); + break; + case INSET_SIDE_RIGHT: + m.postTranslate(inset, 0); + break; + case INSET_SIDE_BOTTOM: + m.postTranslate(0, inset); + break; + } + } + + private static void buildTypeSourcesMap(SparseIntArray typeSideMap, + SparseSetArray<InsetsSourceConsumer> sideSourcesMap, + SparseArray<InsetsSourceConsumer> consumers) { + for (int i = typeSideMap.size() - 1; i >= 0; i--) { + int type = typeSideMap.keyAt(i); + int side = typeSideMap.valueAt(i); + sideSourcesMap.add(side, consumers.get(type)); + } + } +} + diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index fb4f9c03fa68..4ab1f266cc70 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -28,6 +28,7 @@ import android.view.InsetsState.InternalInsetType; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayList; /** * Implements {@link WindowInsetsController} on the client. @@ -41,6 +42,7 @@ public class InsetsController implements WindowInsetsController { private final ViewRootImpl mViewRoot; private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); + private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; @@ -67,9 +69,11 @@ public class InsetsController implements WindowInsetsController { /** * @see InsetsState#calculateInsets */ - WindowInsets calculateInsets(boolean isScreenRound, + @VisibleForTesting + public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeNavBar, DisplayCutout cutout) { - return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout); + return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout, + null /* typeSideMap */); } /** @@ -116,6 +120,28 @@ public class InsetsController implements WindowInsetsController { } } + @Override + public void controlWindowInsetsAnimation(@InsetType int types, + WindowInsetsAnimationControlListener listener) { + + // TODO: Check whether we already have a controller. + final ArraySet<Integer> internalTypes = mState.toInternalType(types); + final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>(); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (consumer.getControl() != null) { + consumers.put(consumer.getType(), consumer); + } else { + // TODO: Let calling app know it's not possible, or wait + // TODO: Remove it from types + } + } + final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers, + mFrame, mState, listener, types, + () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView)); + mAnimationControls.add(controller); + } + private void applyLocalVisibilityOverride() { for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i); @@ -134,7 +160,8 @@ public class InsetsController implements WindowInsetsController { return controller; } - void notifyVisibilityChanged() { + @VisibleForTesting + public void notifyVisibilityChanged() { mViewRoot.notifyInsetsChanged(); } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 0cb8ad72f102..f8148a906bb3 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -70,7 +70,8 @@ public class InsetsSource implements Parcelable { * * @param relativeFrame The frame to calculate the insets relative to. * @param ignoreVisibility If true, always reports back insets even if source isn't visible. - * @return The resulting insets. + * @return The resulting insets. The contract is that only one side will be occupied by a + * source. */ public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { if (!ignoreVisibility && !mVisible) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 689b14fe29c6..63025dc16f17 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -17,12 +17,15 @@ package android.view; import android.annotation.IntDef; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetType; @@ -77,11 +80,30 @@ public class InsetsState implements Parcelable { /** A shelf is the same as the navigation bar. */ public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR; + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "INSET_SIDE", value = { + INSET_SIDE_LEFT, + INSET_SIDE_TOP, + INSET_SIDE_RIGHT, + INSET_SIDE_BOTTOM, + INSET_SIDE_UNKNWON + }) + public @interface InsetSide {} + static final int INSET_SIDE_LEFT = 0; + static final int INSET_SIDE_TOP = 1; + static final int INSET_SIDE_RIGHT = 2; + static final int INSET_SIDE_BOTTOM = 3; + static final int INSET_SIDE_UNKNWON = 4; + private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>(); public InsetsState() { } + public InsetsState(InsetsState copy) { + set(copy); + } + /** * Calculates {@link WindowInsets} based on the current source configuration. * @@ -89,7 +111,8 @@ public class InsetsState implements Parcelable { * @return The calculated insets. */ public WindowInsets calculateInsets(Rect frame, boolean isScreenRound, - boolean alwaysConsumeNavBar, DisplayCutout cutout) { + boolean alwaysConsumeNavBar, DisplayCutout cutout, + @Nullable @InsetSide SparseIntArray typeSideMap) { Insets systemInsets = Insets.NONE; Insets maxInsets = Insets.NONE; final Rect relativeFrame = new Rect(frame); @@ -100,13 +123,13 @@ public class InsetsState implements Parcelable { continue; } systemInsets = processSource(source, systemInsets, relativeFrame, - false /* ignoreVisibility */); + false /* ignoreVisibility */, typeSideMap); // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. if (source.getType() != TYPE_IME) { maxInsets = processSource(source, maxInsets, relativeFrameMax, - true /* ignoreVisibility */); + true /* ignoreVisibility */, null /* typeSideMap */); } } return new WindowInsets(new Rect(systemInsets), null, new Rect(maxInsets), isScreenRound, @@ -114,13 +137,39 @@ public class InsetsState implements Parcelable { } private Insets processSource(InsetsSource source, Insets insets, Rect relativeFrame, - boolean ignoreVisibility) { + boolean ignoreVisibility, @Nullable @InsetSide SparseIntArray typeSideMap) { Insets currentInsets = source.calculateInsets(relativeFrame, ignoreVisibility); insets = Insets.add(currentInsets, insets); relativeFrame.inset(insets); + if (typeSideMap != null && !Insets.NONE.equals(currentInsets)) { + @InsetSide int insetSide = getInsetSide(currentInsets); + if (insetSide != INSET_SIDE_UNKNWON) { + typeSideMap.put(source.getType(), getInsetSide(currentInsets)); + } + } return insets; } + /** + * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b + * is set in order that this method returns a meaningful result. + */ + private @InsetSide int getInsetSide(Insets insets) { + if (insets.left != 0) { + return INSET_SIDE_LEFT; + } + if (insets.top != 0) { + return INSET_SIDE_TOP; + } + if (insets.right != 0) { + return INSET_SIDE_RIGHT; + } + if (insets.bottom != 0) { + return INSET_SIDE_BOTTOM; + } + return INSET_SIDE_UNKNWON; + } + public InsetsSource getSource(@InternalInsetType int type) { return mSources.computeIfAbsent(type, InsetsSource::new); } diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java new file mode 100644 index 000000000000..0270acb3aea7 --- /dev/null +++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java @@ -0,0 +1,151 @@ +/* + * 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.Matrix; +import android.graphics.Rect; +import android.view.SurfaceControl.Transaction; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.function.Consumer; + +/** + * Helper class to apply surface transactions in sync with RenderThread. + * @hide + */ +public class SyncRtSurfaceTransactionApplier { + + private final Surface mTargetSurface; + private final ViewRootImpl mTargetViewRootImpl; + private final float[] mTmpFloat9 = new float[9]; + + /** + * @param targetView The view in the surface that acts as synchronization anchor. + */ + public SyncRtSurfaceTransactionApplier(View targetView) { + mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; + mTargetSurface = mTargetViewRootImpl != null ? mTargetViewRootImpl.mSurface : null; + } + + /** + * Schedules applying surface parameters on the next frame. + * + * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into + * this method to avoid synchronization issues. + */ + public void scheduleApply(final SurfaceParams... params) { + if (mTargetViewRootImpl == null) { + return; + } + mTargetViewRootImpl.registerRtFrameCallback(frame -> { + if (mTargetSurface == null || !mTargetSurface.isValid()) { + return; + } + Transaction t = new Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SurfaceParams surfaceParams = params[i]; + SurfaceControl surface = surfaceParams.surface; + t.deferTransactionUntilSurface(surface, mTargetSurface, frame); + applyParams(t, surfaceParams, mTmpFloat9); + } + t.setEarlyWakeup(); + t.apply(); + }); + + // Make sure a frame gets scheduled. + mTargetViewRootImpl.getView().invalidate(); + } + + public static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) { + t.setMatrix(params.surface, params.matrix, tmpFloat9); + t.setWindowCrop(params.surface, params.windowCrop); + t.setAlpha(params.surface, params.alpha); + t.setLayer(params.surface, params.layer); + t.setCornerRadius(params.surface, params.cornerRadius); + t.show(params.surface); + } + + /** + * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is + * attached if necessary. + */ + public static void create(final View targetView, + final Consumer<SyncRtSurfaceTransactionApplier> callback) { + if (targetView == null) { + // No target view, no applier + callback.accept(null); + } else if (targetView.getViewRootImpl() != null) { + // Already attached, we're good to go + callback.accept(new SyncRtSurfaceTransactionApplier(targetView)); + } else { + // Haven't been attached before we can get the view root + targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + targetView.removeOnAttachStateChangeListener(this); + callback.accept(new SyncRtSurfaceTransactionApplier(targetView)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Do nothing + } + }); + } + } + + public static class SurfaceParams { + + /** + * Constructs surface parameters to be applied when the current view state gets pushed to + * RenderThread. + * + * @param surface The surface to modify. + * @param alpha Alpha to apply. + * @param matrix Matrix to apply. + * @param windowCrop Crop to apply. + */ + public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix, + Rect windowCrop, int layer, float cornerRadius) { + this.surface = surface; + this.alpha = alpha; + this.matrix = new Matrix(matrix); + this.windowCrop = new Rect(windowCrop); + this.layer = layer; + this.cornerRadius = cornerRadius; + } + + @VisibleForTesting + public final SurfaceControl surface; + + @VisibleForTesting + public final float alpha; + + @VisibleForTesting + final float cornerRadius; + + @VisibleForTesting + public final Matrix matrix; + + @VisibleForTesting + public final Rect windowCrop; + + @VisibleForTesting + public final int layer; + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 69cd3e6487c9..57635efff72e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -10488,6 +10488,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @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() { diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java new file mode 100644 index 000000000000..b27a23da61b7 --- /dev/null +++ b/core/java/android/view/WindowInsetsAnimationControlListener.java @@ -0,0 +1,50 @@ +/* + * 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.annotation.NonNull; +import android.view.WindowInsets.Type.InsetType; +import android.view.inputmethod.EditorInfo; + +/** + * 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. + * + * @param controller The controller to control the inset animation. + * @param types The {@link InsetType}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 + * 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. + */ + void onReady(@NonNull WindowInsetsAnimationController controller, @InsetType int types); + + /** + * Called when the window no longer has control over the requested types. If it loses control + * over one type, the whole control will be cancelled. If none of the requested types were + * available when requesting the control, the animation control will be cancelled immediately + * without {@link #onReady} being called. + */ + void onCancelled(); +} diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java new file mode 100644 index 000000000000..9de517dac5de --- /dev/null +++ b/core/java/android/view/WindowInsetsAnimationController.java @@ -0,0 +1,81 @@ +/* + * 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.annotation.NonNull; +import android.graphics.Insets; +import android.view.WindowInsets.Type.InsetType; + +/** + * Interface to control a window inset animation frame-by-frame. + * @hide pending unhide + */ +public interface WindowInsetsAnimationController { + + /** + * Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden. + * + * @return Insets when the windows this animation is controlling are fully hidden. + */ + @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. + * + * @return Insets when the windows this animation is controlling are fully shown. + */ + @NonNull Insets getShownStateInsets(); + + /** + * @return The current insets on the window. These will follow any animation changes. + */ + @NonNull Insets getCurrentInsets(); + + /** + * @return The {@link InsetType}s this object is currently controlling. + */ + @InsetType int getTypes(); + + /** + * Modifies the insets 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 + * {@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, use + * TODO add link to animation listeners. + * <p> + * {@link View#dispatchApplyWindowInsets} will instead be called once the animation has + * finished, i.e. once {@link #finish} has been called. + * + * @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} + */ + void changeInsets(@NonNull Insets insets); + + /** + * @param shownTypes The list of windows causing insets that should remain shown after finishing + * the animation. + */ + void finish(@InsetType int shownTypes); +} diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 7be5f2e7a0b0..a35be273f3bf 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.NonNull; import android.view.WindowInsets.Type.InsetType; /** @@ -51,4 +52,15 @@ public interface WindowInsetsController { * would like to make disappear. */ void hide(@InsetType int types); + + /** + * Lets the application control window inset animations in a frame-by-frame manner by modifying + * the position of the windows in the system causing insets directly. + * + * @param types The {@link InsetType}s the application has requested to control. + * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the + * windows are ready to be controlled, among other callbacks. + */ + void controlWindowInsetsAnimation(@InsetType int types, + @NonNull WindowInsetsAnimationControlListener listener); } |
