Merge "A brave new world for window insets (5/n)"
diff --git a/api/current.txt b/api/current.txt
index 17ea06f..328bd02 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14134,8 +14134,11 @@
public final class Insets {
method public static android.graphics.Insets add(android.graphics.Insets, android.graphics.Insets);
+ method public static android.graphics.Insets max(android.graphics.Insets, android.graphics.Insets);
+ method public static android.graphics.Insets min(android.graphics.Insets, android.graphics.Insets);
method public static android.graphics.Insets of(int, int, int, int);
method public static android.graphics.Insets of(android.graphics.Rect);
+ method public static android.graphics.Insets subtract(android.graphics.Insets, android.graphics.Insets);
field public static final android.graphics.Insets NONE;
field public final int bottom;
field public final int left;
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index d100f12..680e85f 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -55,6 +55,13 @@
}
/**
+ * @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 0000000..7b9f78e
--- /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 fb4f9c0..4ab1f26 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -28,6 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* Implements {@link WindowInsetsController} on the client.
@@ -41,6 +42,7 @@
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 @@
/**
* @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 @@
}
}
+ @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 @@
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 0cb8ad7..f8148a9 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -70,7 +70,8 @@
*
* @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 689b14f..63025dc 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 @@
/** 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 @@
* @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 @@
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 @@
}
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/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
similarity index 72%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java
rename to core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 807edf6..0270acb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -11,24 +11,22 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.shared.system;
+package android.view;
-import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.view.Surface;
-import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.View;
-import android.view.ViewRootImpl;
+
+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 {
@@ -54,30 +52,32 @@
if (mTargetViewRootImpl == null) {
return;
}
- mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
- @Override
- public void onFrameDraw(long 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();
+ 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(TransactionCompat t, SurfaceParams params) {
- applyParams(t.mTransaction, params, t.mTmpValues);
+ 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);
}
/**
@@ -109,15 +109,6 @@
}
}
- private 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);
- }
-
public static class SurfaceParams {
/**
@@ -129,9 +120,9 @@
* @param matrix Matrix to apply.
* @param windowCrop Crop to apply.
*/
- public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
+ public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
Rect windowCrop, int layer, float cornerRadius) {
- this.surface = surface.mSurfaceControl;
+ this.surface = surface;
this.alpha = alpha;
this.matrix = new Matrix(matrix);
this.windowCrop = new Rect(windowCrop);
@@ -139,11 +130,22 @@
this.cornerRadius = cornerRadius;
}
- final SurfaceControl surface;
- final float alpha;
- final Matrix matrix;
- final Rect windowCrop;
- final int layer;
+ @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 69cd3e6..57635ef 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10488,6 +10488,7 @@
*
* @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 0000000..b27a23d
--- /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 0000000..9de517d
--- /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 7be5f2e..a35be27 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 @@
* 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);
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
new file mode 100644
index 0000000..d520f15
--- /dev/null
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.TYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.TYPE_TOP_BAR;
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+import android.view.SurfaceControl.Transaction;
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@Presubmit
+@FlakyTest(detail = "Promote once confirmed non-flaky")
+@RunWith(AndroidJUnit4.class)
+public class InsetsAnimationControlImplTest {
+
+ private InsetsAnimationControlImpl mController;
+
+ private SurfaceSession mSession = new SurfaceSession();
+ private SurfaceControl mTopLeash;
+ private SurfaceControl mNavLeash;
+
+ @Mock Transaction mMockTransaction;
+ @Mock InsetsController mMockController;
+ @Mock WindowInsetsAnimationControlListener mMockListener;
+ @Mock SyncRtSurfaceTransactionApplier mMockTransactionApplier;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTopLeash = new SurfaceControl.Builder(mSession)
+ .setName("testSurface")
+ .build();
+ mNavLeash = new SurfaceControl.Builder(mSession)
+ .setName("testSurface")
+ .build();
+ InsetsState state = new InsetsState();
+ state.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 500, 100));
+ state.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
+ InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, state,
+ () -> mMockTransaction, mMockController);
+ topConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mTopLeash));
+
+ InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(TYPE_NAVIGATION_BAR, state,
+ () -> mMockTransaction, mMockController);
+ navConsumer.hide();
+ navConsumer.setControl(new InsetsSourceControl(TYPE_NAVIGATION_BAR, mNavLeash));
+
+ SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
+ consumers.put(TYPE_TOP_BAR, topConsumer);
+ consumers.put(TYPE_NAVIGATION_BAR, navConsumer);
+ mController = new InsetsAnimationControlImpl(consumers,
+ new Rect(0, 0, 500, 500), state, mMockListener, WindowInsets.Type.systemBars(),
+ () -> mMockTransactionApplier);
+ }
+
+ @Test
+ public void testGetters() {
+ assertEquals(Insets.of(0, 100, 100, 0), mController.getShownStateInsets());
+ assertEquals(Insets.of(0, 0, 0, 0), mController.getHiddenStateInsets());
+ assertEquals(Insets.of(0, 100, 0, 0), mController.getCurrentInsets());
+ assertEquals(WindowInsets.Type.systemBars(), mController.getTypes());
+ }
+
+ @Test
+ public void testChangeInsets() {
+ mController.changeInsets(Insets.of(0, 30, 40, 0));
+ assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+
+ ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
+ verify(mMockTransactionApplier).scheduleApply(captor.capture());
+ List<SurfaceParams> params = captor.getAllValues();
+ assertEquals(2, params.size());
+ SurfaceParams first = params.get(0);
+ SurfaceParams second = params.get(1);
+ SurfaceParams topParams = first.surface == mTopLeash ? first : second;
+ SurfaceParams navParams = first.surface == mNavLeash ? first : second;
+ assertPosition(topParams.matrix, new Rect(0, 0, 500, 100), new Rect(0, -70, 500, 30));
+ assertPosition(navParams.matrix, new Rect(400, 0, 500, 500), new Rect(460, 0, 560, 500));
+ }
+
+ private void assertPosition(Matrix m, Rect original, Rect transformed) {
+ RectF rect = new RectF(original);
+ rect.offsetTo(0, 0);
+ m.mapRect(rect);
+ rect.round(original);
+ assertEquals(original, transformed);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 2ad6028..d3d274a 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -28,6 +28,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@Presubmit
@FlakyTest(detail = "Promote once confirmed non-flaky")
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 6bb9539..d41a718 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.InsetsState.INSET_SIDE_BOTTOM;
+import static android.view.InsetsState.INSET_SIDE_TOP;
import static android.view.InsetsState.TYPE_IME;
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
@@ -27,6 +29,7 @@
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.FlakyTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseIntArray;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,9 +48,12 @@
mState.getSource(TYPE_TOP_BAR).setVisible(true);
mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 100, 300));
mState.getSource(TYPE_IME).setVisible(true);
+ SparseIntArray typeSideMap = new SparseIntArray();
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT);
+ DisplayCutout.NO_CUTOUT, typeSideMap);
assertEquals(new Rect(0, 100, 0, 100), insets.getSystemWindowInsets());
+ assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR));
+ assertEquals(INSET_SIDE_BOTTOM, typeSideMap.get(TYPE_IME));
}
@Test
@@ -57,7 +63,7 @@
mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300));
mState.getSource(TYPE_IME).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT);
+ DisplayCutout.NO_CUTOUT, null);
assertEquals(100, insets.getStableInsetBottom());
assertEquals(new Rect(0, 0, 0, 200), insets.getSystemWindowInsets());
}
@@ -69,7 +75,7 @@
mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT);
+ DisplayCutout.NO_CUTOUT, null);
assertEquals(new Rect(0, 100, 20, 0), insets.getSystemWindowInsets());
}
@@ -81,7 +87,7 @@
mState.getSource(TYPE_IME).setVisible(true);
mState.removeSource(TYPE_IME);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT);
+ DisplayCutout.NO_CUTOUT, null);
assertEquals(0, insets.getSystemWindowInsetBottom());
}
diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java
index d9da27c..8258b57 100644
--- a/graphics/java/android/graphics/Insets.java
+++ b/graphics/java/android/graphics/Insets.java
@@ -93,6 +93,41 @@
}
/**
+ * Subtract two Insets.
+ *
+ * @param a The minuend.
+ * @param b The subtrahend.
+ * @return a - b, i. e. all insets on every side are subtracted from each other.
+ */
+ public static @NonNull Insets subtract(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(a.left - b.left, a.top - b.top, a.right - b.right, a.bottom - b.bottom);
+ }
+
+ /**
+ * Retrieves the maximum of two Insets.
+ *
+ * @param a The first Insets.
+ * @param b The second Insets.
+ * @return max(a, b), i. e. the larger of every inset on every side is taken for the result.
+ */
+ public static @NonNull Insets max(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(Math.max(a.left, b.left), Math.max(a.top, b.top),
+ Math.max(a.right, b.right), Math.max(a.bottom, b.bottom));
+ }
+
+ /**
+ * Retrieves the minimum of two Insets.
+ *
+ * @param a The first Insets.
+ * @param b The second Insets.
+ * @return min(a, b), i. e. the smaller of every inset on every side is taken for the result.
+ */
+ public static @NonNull Insets min(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(Math.min(a.left, b.left), Math.min(a.top, b.top),
+ Math.min(a.right, b.right), Math.min(a.bottom, b.bottom));
+ }
+
+ /**
* Two Insets instances are equal iff they belong to the same class and their fields are
* pairwise equal.
*
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
new file mode 100644
index 0000000..c0a1d89
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.android.systemui.shared.system;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.ViewRootImpl;
+
+import java.util.function.Consumer;
+
+/**
+ * Helper class to apply surface transactions in sync with RenderThread.
+ */
+public class SyncRtSurfaceTransactionApplierCompat {
+
+ private final SyncRtSurfaceTransactionApplier mApplier;
+
+ /**
+ * @param targetView The view in the surface that acts as synchronization anchor.
+ */
+ public SyncRtSurfaceTransactionApplierCompat(View targetView) {
+ mApplier = new SyncRtSurfaceTransactionApplier(targetView);
+ }
+
+ private SyncRtSurfaceTransactionApplierCompat(SyncRtSurfaceTransactionApplier applier) {
+ mApplier = applier;
+ }
+
+ /**
+ * 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) {
+ mApplier.scheduleApply(convert(params));
+ }
+
+ private SyncRtSurfaceTransactionApplier.SurfaceParams[] convert(SurfaceParams[] params) {
+ SyncRtSurfaceTransactionApplier.SurfaceParams[] result =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams[params.length];
+ for (int i = 0; i < params.length; i++) {
+ result[i] = params[i].mParams;
+ }
+ return result;
+ }
+
+ public static void applyParams(TransactionCompat t, SurfaceParams params) {
+ SyncRtSurfaceTransactionApplier.applyParams(t.mTransaction, params.mParams, t.mTmpValues);
+ }
+
+ public static void create(final View targetView,
+ final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
+ SyncRtSurfaceTransactionApplier.create(targetView,
+ new Consumer<SyncRtSurfaceTransactionApplier>() {
+ @Override
+ public void accept(SyncRtSurfaceTransactionApplier applier) {
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(applier));
+ }
+ });
+ }
+
+ public static class SurfaceParams {
+
+ private final SyncRtSurfaceTransactionApplier.SurfaceParams mParams;
+
+ /**
+ * 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(SurfaceControlCompat surface, float alpha, Matrix matrix,
+ Rect windowCrop, int layer, float cornerRadius) {
+ mParams = new SyncRtSurfaceTransactionApplier.SurfaceParams(surface.mSurfaceControl,
+ alpha, matrix, windowCrop, layer, cornerRadius);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index f899863..e1b231b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -28,12 +28,12 @@
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.shared.system.SurfaceControlCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -267,8 +267,8 @@
Matrix m = new Matrix();
m.postTranslate(0, (float) (mParams.top - app.position.y));
mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
- SurfaceParams params = new SurfaceParams(new SurfaceControlCompat(app.leash),
- 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex, mCornerRadius);
+ SurfaceParams params = new SurfaceParams(app.leash, 1f /* alpha */, m, mWindowCrop,
+ app.prefixOrderIndex, mCornerRadius);
mSyncRtTransactionApplier.scheduleApply(params);
}