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);
         }