Pass PictureInPictureParams in TaskInfo.

This way TaskOrganizerImplementations can use the parameters from
taskAppeared. We also wire up taskInfoChanged.

Bug: 146594635
Bug: 139371701
Test: TaskOrganizerTests
Change-Id: I5371af16177ebdf6e2187beb7184efd499c8f36d
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index bfd966c5..67d94de 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -247,6 +247,14 @@
         return mSourceRectHint != null && !mSourceRectHint.isEmpty();
     }
 
+    /**
+     * @return True if no parameters are set
+     * @hide
+     */
+    public boolean empty() {
+        return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio();
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 662ca6e..f7d712d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -149,6 +149,13 @@
     public IWindowContainer token;
 
     /**
+     * The PictureInPictureParams for the Task, if set.
+     * @hide
+     */
+    @Nullable
+    public PictureInPictureParams pictureInPictureParams;
+
+    /**
      * The activity type of the top activity in this task.
      * @hide
      */
@@ -209,6 +216,9 @@
         configuration.readFromParcel(source);
         token = IWindowContainer.Stub.asInterface(source.readStrongBinder());
         topActivityType = source.readInt();
+        pictureInPictureParams = source.readInt() != 0
+            ? PictureInPictureParams.CREATOR.createFromParcel(source)
+            : null;
     }
 
     /**
@@ -246,6 +256,12 @@
         configuration.writeToParcel(dest, flags);
         dest.writeStrongInterface(token);
         dest.writeInt(topActivityType);
+        if (pictureInPictureParams == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            pictureInPictureParams.writeToParcel(dest, flags);
+        }
     }
 
     @Override
@@ -261,6 +277,7 @@
                 + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow
                 + " resizeMode=" + resizeMode
                 + " token=" + token
-                + " topActivityType=" + topActivityType;
+                + " topActivityType=" + topActivityType
+                + " pictureInPictureParams=" + pictureInPictureParams;
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a54f5d4..9b968cb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7692,4 +7692,9 @@
         }
         win.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
     }
+
+    void setPictureInPictureParams(PictureInPictureParams p) {
+        pictureInPictureArgs.copyOnlySet(p);
+        getTask().getRootTask().setPictureInPictureParams(p);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5f3e3a3..80162fb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4153,7 +4153,7 @@
                             return;
                         }
                         // Only update the saved args from the args that are set
-                        r.pictureInPictureArgs.copyOnlySet(params);
+                        r.setPictureInPictureParams(params);
                         final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
                         final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
                         // Adjust the source bounds by the insets for the transition down
@@ -4201,7 +4201,7 @@
                         "setPictureInPictureParams", token, params);
 
                 // Only update the saved args from the args that are set
-                r.pictureInPictureArgs.copyOnlySet(params);
+                r.setPictureInPictureParams(params);
                 if (r.inPinnedWindowingMode()) {
                     // If the activity is already in picture-in-picture, update the pinned stack now
                     // if it is not already expanding to fullscreen. Otherwise, the arguments will
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b1db9d7..028274a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -99,6 +99,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.AppGlobals;
+import android.app.PictureInPictureParams;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -461,6 +462,11 @@
      */
     ITaskOrganizer mTaskOrganizer;
 
+    /**
+     * Last Picture-in-Picture params applicable to the task. Updated when the app
+     * enters Picture-in-Picture or when setPictureInPictureParams is called.
+     */
+    PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
 
     /**
      * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
@@ -3199,6 +3205,12 @@
         //                    order changes.
         final Task top = getTopMostTask();
         info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
+
+        if (mPictureInPictureParams.empty()) {
+            info.pictureInPictureParams = null;
+        } else {
+            info.pictureInPictureParams = mPictureInPictureParams;
+        }
     }
 
     /**
@@ -3929,4 +3941,10 @@
     void onWindowFocusChanged(boolean hasFocus) {
         updateShadowsRadius(hasFocus, getPendingTransaction());
     }
+
+    void setPictureInPictureParams(PictureInPictureParams p) {
+        mPictureInPictureParams.copyOnlySet(p);
+        mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
+                this, true /* force */);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index f7aa3cc..6e738e90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -34,14 +34,21 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
+import android.app.PictureInPictureParams;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -49,6 +56,8 @@
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
+import android.content.pm.ActivityInfo;
+import android.util.Rational;
 import android.view.Display;
 import android.view.ITaskOrganizer;
 import android.view.IWindowContainer;
@@ -483,4 +492,76 @@
         verify(transactionListener)
             .transactionReady(anyInt(), any());
     }
+
+    class StubOrganizer extends ITaskOrganizer.Stub {
+        RunningTaskInfo mInfo;
+
+        @Override
+        public void taskAppeared(RunningTaskInfo info) {
+            mInfo = info;
+        }
+        @Override
+        public void taskVanished(IWindowContainer wc) {
+        }
+        @Override
+        public void transactionReady(int id, SurfaceControl.Transaction t) {
+        }
+        @Override
+        public void onTaskInfoChanged(RunningTaskInfo info) {
+        }
+    };
+
+    private ActivityRecord makePipableActivity() {
+        final ActivityRecord record = createActivityRecord(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+        spyOn(record);
+        doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
+        return record;
+    }
+
+    @Test
+    public void testEnterPipParams() {
+        final StubOrganizer o = new StubOrganizer();
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+        final ActivityRecord record = makePipableActivity();
+
+        final PictureInPictureParams p =
+            new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+        assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
+        waitUntilHandlersIdle();
+        assertNotNull(o.mInfo);
+        assertNotNull(o.mInfo.pictureInPictureParams);
+    }
+
+    @Test
+    public void testChangePipParams() {
+        class ChangeSavingOrganizer extends StubOrganizer {
+            RunningTaskInfo mChangedInfo;
+            @Override
+            public void onTaskInfoChanged(RunningTaskInfo info) {
+                mChangedInfo = info;
+            }
+        }
+        ChangeSavingOrganizer o = new ChangeSavingOrganizer();
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+
+        final ActivityRecord record = makePipableActivity();
+        final PictureInPictureParams p =
+            new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+        assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
+        waitUntilHandlersIdle();
+        assertNotNull(o.mInfo);
+        assertNotNull(o.mInfo.pictureInPictureParams);
+
+        final PictureInPictureParams p2 =
+            new PictureInPictureParams.Builder().setAspectRatio(new Rational(3, 4)).build();
+        mWm.mAtmService.setPictureInPictureParams(record.token, p2);
+        waitUntilHandlersIdle();
+        assertNotNull(o.mChangedInfo);
+        assertNotNull(o.mChangedInfo.pictureInPictureParams);
+        final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatioRational();
+        assertEquals(3, ratio.getNumerator());
+        assertEquals(4, ratio.getDenominator());
+    }
 }