Adding API to access / change the task bounds

Bug: 21738328
Change-Id: I3aabcbe1dcc5caa70f66554157f41fbfacf4c50f
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 84892a3..07fa52c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -31,7 +31,7 @@
 import android.transition.TransitionManager;
 import android.util.ArrayMap;
 import android.util.SuperNotCalledException;
-import android.view.Window.WindowStackCallback;
+import android.view.Window.WindowControllerCallback;
 import android.widget.Toolbar;
 
 import com.android.internal.app.IVoiceInteractor;
@@ -60,6 +60,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.graphics.Rect;
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.net.Uri;
@@ -673,7 +674,7 @@
         implements LayoutInflater.Factory2,
         Window.Callback, KeyEvent.Callback,
         OnCreateContextMenuListener, ComponentCallbacks2,
-        Window.OnWindowDismissedCallback, WindowStackCallback {
+        Window.OnWindowDismissedCallback, WindowControllerCallback {
     private static final String TAG = "Activity";
     private static final boolean DEBUG_LIFECYCLE = false;
 
@@ -2712,7 +2713,8 @@
     }
 
 
-    /** Called to move the window and its activity/task to a different stack container.
+    /**
+     * Called to move the window and its activity/task to a different stack container.
      * For example, a window can move between
      * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
      * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
@@ -2734,6 +2736,31 @@
     }
 
     /**
+     * Returns the bounds of the task that contains this activity.
+     *
+     * @return Rect The bounds that contains the activity.
+     * @hide
+     */
+    @Override
+    public Rect getActivityBounds() throws RemoteException {
+        return ActivityManagerNative.getDefault().getActivityBounds(mToken);
+    }
+
+    /**
+     * Sets the bounds (size and position) of the task or stack that contains this
+     * activity.
+     * NOTE: The requested bounds might not the fully honored by the system depending
+     * on the window placement policy.
+     *
+     * @param newBounds The new target bounds of the activity in task or stack.
+     * @hide
+     */
+    @Override
+    public void setActivityBounds(Rect newBounds) throws RemoteException {
+        ActivityManagerNative.getDefault().setActivityBounds(mToken, newBounds);
+    }
+
+    /**
      * Called to process key events.  You can override this to intercept all
      * key events before they are dispatched to the window.  Be sure to call
      * this implementation for key events that should be handled normally.
@@ -6211,7 +6238,7 @@
         mFragments.attachHost(null /*parent*/);
 
         mWindow = new PhoneWindow(this);
-        mWindow.setWindowStackCallback(this);
+        mWindow.setWindowControllerCallback(this);
         mWindow.setCallback(this);
         mWindow.setOnWindowDismissedCallback(this);
         mWindow.getLayoutInflater().setPrivateFactory(this);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 10c122a..5f33344 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -752,6 +752,24 @@
             return true;
         }
 
+        case GET_ACTIVITY_BOUNDS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Rect r = getActivityBounds(token);
+            reply.writeNoException();
+            r.writeToParcel(reply, 0);
+            return true;
+        }
+
+        case SET_ACTIVITY_BOUNDS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Rect r = Rect.CREATOR.createFromParcel(data);
+            setActivityBounds(token, r);
+            reply.writeNoException();
+            return true;
+        }
+
         case POSITION_TASK_IN_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int taskId = data.readInt();
@@ -5903,6 +5921,35 @@
     }
 
     @Override
+    public void setActivityBounds(IBinder token, Rect r) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        r.writeToParcel(data, 0);
+        mRemote.transact(SET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
+    public Rect getActivityBounds(IBinder token) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(GET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        Rect rect = Rect.CREATOR.createFromParcel(reply);
+        data.recycle();
+        reply.recycle();
+        return rect;
+    }
+
+    @Override
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index ff1d595..d355219 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -490,6 +490,9 @@
             throws RemoteException;
     public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
     public void resizeTask(int taskId, Rect bounds) throws RemoteException;
+    public void setActivityBounds(IBinder token, Rect bounds) throws RemoteException;
+    public Rect getActivityBounds(IBinder token) throws RemoteException;
+
     public Rect getTaskBounds(int taskId) throws RemoteException;
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
 
@@ -887,4 +890,6 @@
     int GET_ACTIVITY_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 343;
     int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344;
     int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
+    int GET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
+    int SET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index eecda89..1a9fb34 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -28,8 +28,9 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.media.session.MediaController;
 import android.net.Uri;
 import android.os.Bundle;
@@ -183,7 +184,7 @@
     private TypedArray mWindowStyle;
     private Callback mCallback;
     private OnWindowDismissedCallback mOnWindowDismissedCallback;
-    private WindowStackCallback mWindowStackCallback;
+    private WindowControllerCallback mWindowControllerCallback;
     private WindowManager mWindowManager;
     private IBinder mAppToken;
     private String mAppName;
@@ -479,8 +480,9 @@
     }
 
     /** @hide */
-    public interface WindowStackCallback {
-        /** Called to move the window and its activity/task to a different stack container.
+    public interface WindowControllerCallback {
+        /**
+         * Called to move the window and its activity/task to a different stack container.
          * For example, a window can move between
          * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
          * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
@@ -491,6 +493,23 @@
 
         /** Returns the current stack Id for the window. */
         int getWindowStackId() throws RemoteException;
+
+        /**
+         * Returns the bounds of the task that contains this activity.
+         *
+         * @return Rect The bounds that contains the activity.
+         */
+        Rect getActivityBounds() throws RemoteException;
+
+        /**
+         * Sets the bounds (size and position) of the task or stack that contains this
+         * activity.
+         * NOTE: The requested bounds might not the fully honored by the system depending
+         * on the window placement policy.
+         *
+         * @param newBounds The new target bounds of the activity in task or stack.
+         */
+        void setActivityBounds(Rect newBounds) throws RemoteException;
     }
 
     public Window(Context context) {
@@ -682,13 +701,13 @@
     }
 
     /** @hide */
-    public final void setWindowStackCallback(WindowStackCallback wscb) {
-        mWindowStackCallback = wscb;
+    public final void setWindowControllerCallback(WindowControllerCallback wccb) {
+        mWindowControllerCallback = wccb;
     }
 
     /** @hide */
-    public final WindowStackCallback getWindowStackCallback() {
-        return mWindowStackCallback;
+    public final WindowControllerCallback getWindowControllerCallback() {
+        return mWindowControllerCallback;
     }
 
     /**
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 1068b51..57891a1 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -5187,7 +5187,7 @@
      **/
     private int getWorkspaceId() {
         int workspaceId = FULLSCREEN_WORKSPACE_STACK_ID;
-        WindowStackCallback callback = getWindowStackCallback();
+        WindowControllerCallback callback = getWindowControllerCallback();
         if (callback != null) {
             try {
                 workspaceId = callback.getWindowStackId();
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index d8626cd..dd5c5d7 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -17,6 +17,7 @@
 package com.android.internal.widget;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.view.View;
@@ -243,7 +244,7 @@
      * Maximize the window by moving it to the maximized workspace stack.
      **/
     private void maximizeWindow() {
-        Window.WindowStackCallback callback = mOwner.getWindowStackCallback();
+        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
         if (callback != null) {
             try {
                 callback.changeWindowStack(
@@ -253,4 +254,40 @@
             }
         }
     }
+
+    /**
+     * Returns the bounds of this activity.
+     * @return Returns bounds of the activity. It will return null if either the window is
+     *     fullscreen or the bounds could not be retrieved.
+     */
+    private Rect getActivityBounds() {
+        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+        if (callback != null) {
+            try {
+                return callback.getActivityBounds();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to get the activity bounds.");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the bounds of this Activity on the stack.
+     * @param newBounds The bounds of the activity. Passing null is not allowed.
+     */
+    private void setActivityBounds(Rect newBounds) {
+        if (newBounds == null) {
+            Log.e(TAG, "Failed to set null bounds to the activity.");
+            return;
+        }
+        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+        if (callback != null) {
+            try {
+                callback.setActivityBounds(newBounds);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to set the activity bounds.");
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2be3bf7..19ad930 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8639,6 +8639,54 @@
     }
 
     @Override
+    public void setActivityBounds(IBinder token, Rect bounds) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r == null) {
+                    Slog.w(TAG, "setActivityBounds: token=" + token + " not found");
+                    return;
+                }
+                final TaskRecord task = r.task;
+                if (task == null) {
+                    Slog.e(TAG, "setActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
+                    return;
+                }
+                mStackSupervisor.resizeTaskLocked(task, bounds);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public Rect getActivityBounds(IBinder token) {
+        long ident = Binder.clearCallingIdentity();
+        Rect rect = null;
+        try {
+            synchronized (this) {
+                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r == null) {
+                    Slog.w(TAG, "getActivityBounds: token=" + token + " not found");
+                    return rect;
+                }
+                final TaskRecord task = r.task;
+                if (task == null) {
+                    Slog.e(TAG, "getActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
+                    return rect;
+                }
+                if (task.mBounds != null) {
+                    rect = new Rect(task.mBounds);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return rect;
+    }
+
+    @Override
     public Bitmap getTaskDescriptionIcon(String filename) {
         if (!FileUtils.isValidExtFilename(filename)
                 || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {