summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/util/SparseSetArray.java7
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java197
-rw-r--r--core/java/android/view/InsetsController.java33
-rw-r--r--core/java/android/view/InsetsSource.java3
-rw-r--r--core/java/android/view/InsetsState.java57
-rw-r--r--core/java/android/view/SyncRtSurfaceTransactionApplier.java151
-rw-r--r--core/java/android/view/View.java1
-rw-r--r--core/java/android/view/WindowInsetsAnimationControlListener.java50
-rw-r--r--core/java/android/view/WindowInsetsAnimationController.java81
-rw-r--r--core/java/android/view/WindowInsetsController.java12
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);
}