API for overlaying app content over decor caption in freeform windows.

Bug: 25486369

Change-Id: I8fba30dd690d9f0ccc48149f57ce254286b0c2ae
diff --git a/api/current.txt b/api/current.txt
index e17bf28..17d349a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3499,6 +3499,7 @@
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
+    method public void overlayWithDecorCaption(boolean);
     method public void overridePendingTransition(int, int);
     method public void postponeEnterTransition();
     method public void recreate();
diff --git a/api/system-current.txt b/api/system-current.txt
index d8b9b6f..4d41062 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3602,6 +3602,7 @@
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
+    method public void overlayWithDecorCaption(boolean);
     method public void overridePendingTransition(int, int);
     method public void postponeEnterTransition();
     method public void recreate();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 472d97f..8bb0ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6618,6 +6618,17 @@
     }
 
     /**
+     * Set whether the caption should displayed directly on the content rather than push it down.
+     *
+     * This affects only freeform windows since they display the caption and only the main
+     * window of the activity. The caption is used to drag the window around and also shows
+     * maximize and close action buttons.
+     */
+    public void overlayWithDecorCaption(boolean overlay) {
+        mWindow.setOverlayDecorCaption(overlay);
+    }
+
+    /**
      * Interface for informing a translucent {@link Activity} once all visible activities below it
      * have completed drawing. This is necessary only after an {@link Activity} has been made
      * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8259372..3c4d45a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -276,6 +276,8 @@
 
     private boolean mDestroyed;
 
+    private boolean mOverlayWithDecorCaption = false;
+
     // The current window attributes.
     private final WindowManager.LayoutParams mWindowAttributes =
         new WindowManager.LayoutParams();
@@ -2044,4 +2046,18 @@
     /** @hide */
     public void setTheme(int resId) {
     }
+
+    /**
+     * Whether the caption should be displayed directly on the content rather than push the content
+     * down. This affects only freeform windows since they display the caption.
+     * @hide
+     */
+    public void setOverlayDecorCaption(boolean overlayCaption) {
+        mOverlayWithDecorCaption = overlayCaption;
+    }
+
+    /** @hide */
+    public boolean getOverlayDecorCaption() {
+        return mOverlayWithDecorCaption;
+    }
 }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 4c221fc5..27fe03c 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1570,7 +1570,7 @@
                         new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             }
             mDecorCaptionView.addView(root,
-                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
         } else {
             addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         }
@@ -1672,9 +1672,7 @@
 
     void clearContentView() {
         if (mDecorCaptionView != null) {
-            if (mDecorCaptionView.getChildCount() > 1) {
-                mDecorCaptionView.removeViewAt(1);
-            }
+            mDecorCaptionView.removeContentView();
         } else {
             // This window doesn't have caption, so we need to just remove the
             // children of the decor view.
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index e22bd10..16e8296 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -23,7 +23,6 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.LinearLayout;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.view.Window;
@@ -50,7 +49,7 @@
  * <li>..</li>
  * </ul>
  */
-public class DecorCaptionView extends LinearLayout
+public class DecorCaptionView extends ViewGroup
         implements View.OnClickListener, View.OnTouchListener {
     private final static String TAG = "DecorCaptionView";
     private PhoneWindow mOwner = null;
@@ -62,6 +61,11 @@
     // True when the left mouse button got released while dragging.
     private boolean mLeftMouseButtonReleased;
 
+    private boolean mOverlayWithAppContent = false;
+
+    private View mCaption;
+    private View mContent;
+
     public DecorCaptionView(Context context) {
         super(context);
     }
@@ -74,9 +78,16 @@
         super(context, attrs, defStyle);
     }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mCaption = getChildAt(0);
+    }
+
     public void setPhoneWindow(PhoneWindow owner, boolean show) {
         mOwner = owner;
         mShow = show;
+        mOverlayWithAppContent = owner.getOverlayDecorCaption();
         updateCaptionVisibility();
         // By changing the outline provider to BOUNDS, the window can remove its
         // background without removing the shadow.
@@ -152,13 +163,61 @@
 
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (!(params instanceof MarginLayoutParams)) {
+            throw new IllegalArgumentException(
+                    "params " + params + " must subclass MarginLayoutParams");
+        }
         // Make sure that we never get more then one client area in our view.
         if (index >= 2 || getChildCount() >= 2) {
             throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
         }
-        super.addView(child, index, params);
+        // To support the overlaying content in the caption, we need to put the content view as the
+        // first child to get the right Z-Ordering.
+        super.addView(child, 0, params);
+        mContent = child;
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int captionHeight;
+        if (mCaption.getVisibility() != View.GONE) {
+            measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            captionHeight = mCaption.getMeasuredHeight();
+        } else {
+            captionHeight = 0;
+        }
+        if (mContent != null) {
+            if (mOverlayWithAppContent) {
+                measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            } else {
+                measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
+                        captionHeight);
+            }
+        }
+
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+                MeasureSpec.getSize(heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int captionHeight;
+        if (mCaption.getVisibility() != View.GONE) {
+            mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
+            captionHeight = mCaption.getBottom() - mCaption.getTop();
+        } else {
+            captionHeight = 0;
+        }
+
+        if (mContent != null) {
+            if (mOverlayWithAppContent) {
+                mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
+            } else {
+                mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
+                        captionHeight + mContent.getMeasuredHeight());
+            }
+        }
+    }
     /**
      * Determine if the workspace is entirely covered by the window.
      * @return Returns true when the window is filling the entire screen/workspace.
@@ -175,9 +234,8 @@
     private void updateCaptionVisibility() {
         // Don't show the caption if the window has e.g. entered full screen.
         boolean invisible = isFillingScreen() || !mShow;
-        View caption = getChildAt(0);
-        caption.setVisibility(invisible ? GONE : VISIBLE);
-        caption.setOnTouchListener(this);
+        mCaption.setVisibility(invisible ? GONE : VISIBLE);
+        mCaption.setOnTouchListener(this);
     }
 
     /**
@@ -199,7 +257,38 @@
     }
 
     public int getCaptionHeight() {
-        final View caption = getChildAt(0);
-        return (caption != null) ? caption.getHeight() : 0;
+        return (mCaption != null) ? mCaption.getHeight() : 0;
+    }
+
+    public void removeContentView() {
+        if (mContent != null) {
+            removeView(mContent);
+            mContent = null;
+        }
+    }
+
+    public View getCaption() {
+        return mCaption;
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new MarginLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
+                MarginLayoutParams.MATCH_PARENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(LayoutParams p) {
+        return new MarginLayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof MarginLayoutParams;
     }
 }
diff --git a/core/res/res/layout/decor_caption_dark.xml b/core/res/res/layout/decor_caption_dark.xml
index 86d68af..273264d 100644
--- a/core/res/res/layout/decor_caption_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -23,6 +23,7 @@
     android:layout_height="match_parent"
     android:descendantFocusability="beforeDescendants" >
     <LinearLayout
+        android:id="@+id/caption"
         android:layout_width="match_parent"
         android:layout_gravity="end"
         android:layout_height="wrap_content"
diff --git a/core/res/res/layout/decor_caption_light.xml b/core/res/res/layout/decor_caption_light.xml
index ee03545..fd9198e 100644
--- a/core/res/res/layout/decor_caption_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -23,6 +23,7 @@
     android:layout_height="match_parent"
     android:descendantFocusability="beforeDescendants" >
     <LinearLayout
+        android:id="@+id/caption"
         android:layout_width="match_parent"
         android:layout_gravity="end"
         android:layout_height="wrap_content"