1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.pip2.phone; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 21 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; 22 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.graphics.Matrix; 28 import android.graphics.Rect; 29 import android.view.SurfaceControl; 30 import android.window.WindowContainerTransaction; 31 32 import androidx.annotation.IntDef; 33 import androidx.annotation.Nullable; 34 import androidx.core.content.ContextCompat; 35 36 import com.android.internal.protolog.common.ProtoLog; 37 import com.android.wm.shell.common.ShellExecutor; 38 import com.android.wm.shell.common.pip.PipBoundsState; 39 import com.android.wm.shell.common.pip.PipUtils; 40 import com.android.wm.shell.pip.PipTransitionController; 41 import com.android.wm.shell.protolog.ShellProtoLogGroup; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 46 /** 47 * Scheduler for Shell initiated PiP transitions and animations. 48 */ 49 public class PipScheduler { 50 private static final String TAG = PipScheduler.class.getSimpleName(); 51 private static final String BROADCAST_FILTER = PipScheduler.class.getCanonicalName(); 52 53 private final Context mContext; 54 private final PipBoundsState mPipBoundsState; 55 private final ShellExecutor mMainExecutor; 56 private final PipTransitionState mPipTransitionState; 57 private PipSchedulerReceiver mSchedulerReceiver; 58 private PipTransitionController mPipTransitionController; 59 60 /** 61 * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell. 62 * This is used for a broadcast receiver to resolve intents. This should be removed once 63 * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2. 64 */ 65 private static final int PIP_EXIT_VIA_EXPAND_CODE = 0; 66 private static final int PIP_DOUBLE_TAP = 1; 67 68 @IntDef(value = { 69 PIP_EXIT_VIA_EXPAND_CODE, 70 PIP_DOUBLE_TAP 71 }) 72 @Retention(RetentionPolicy.SOURCE) 73 @interface PipUserJourneyCode {} 74 75 /** 76 * A temporary broadcast receiver to initiate PiP CUJs. 77 */ 78 private class PipSchedulerReceiver extends BroadcastReceiver { 79 @Override onReceive(Context context, Intent intent)80 public void onReceive(Context context, Intent intent) { 81 int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0); 82 switch (userJourneyCode) { 83 case PIP_EXIT_VIA_EXPAND_CODE: 84 scheduleExitPipViaExpand(); 85 break; 86 case PIP_DOUBLE_TAP: 87 scheduleDoubleTapToResize(); 88 break; 89 default: 90 throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode); 91 } 92 } 93 } 94 PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor, PipTransitionState pipTransitionState)95 public PipScheduler(Context context, 96 PipBoundsState pipBoundsState, 97 ShellExecutor mainExecutor, 98 PipTransitionState pipTransitionState) { 99 mContext = context; 100 mPipBoundsState = pipBoundsState; 101 mMainExecutor = mainExecutor; 102 mPipTransitionState = pipTransitionState; 103 104 if (PipUtils.isPip2ExperimentEnabled()) { 105 // temporary broadcast receiver to initiate exit PiP via expand 106 mSchedulerReceiver = new PipSchedulerReceiver(); 107 ContextCompat.registerReceiver(mContext, mSchedulerReceiver, 108 new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED); 109 } 110 } 111 getMainExecutor()112 ShellExecutor getMainExecutor() { 113 return mMainExecutor; 114 } 115 setPipTransitionController(PipTransitionController pipTransitionController)116 void setPipTransitionController(PipTransitionController pipTransitionController) { 117 mPipTransitionController = pipTransitionController; 118 } 119 120 @Nullable getExitPipViaExpandTransaction()121 private WindowContainerTransaction getExitPipViaExpandTransaction() { 122 if (mPipTransitionState.mPipTaskToken == null) { 123 return null; 124 } 125 WindowContainerTransaction wct = new WindowContainerTransaction(); 126 // final expanded bounds to be inherited from the parent 127 wct.setBounds(mPipTransitionState.mPipTaskToken, null); 128 // if we are hitting a multi-activity case 129 // windowing mode change will reparent to original host task 130 wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED); 131 return wct; 132 } 133 134 /** 135 * Schedules exit PiP via expand transition. 136 */ scheduleExitPipViaExpand()137 public void scheduleExitPipViaExpand() { 138 WindowContainerTransaction wct = getExitPipViaExpandTransaction(); 139 if (wct != null) { 140 mMainExecutor.execute(() -> { 141 mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, 142 null /* destinationBounds */); 143 }); 144 } 145 } 146 147 /** 148 * Schedules resize PiP via double tap. 149 */ scheduleDoubleTapToResize()150 public void scheduleDoubleTapToResize() {} 151 152 /** 153 * Animates resizing of the pinned stack given the duration. 154 */ scheduleAnimateResizePip(Rect toBounds)155 public void scheduleAnimateResizePip(Rect toBounds) { 156 scheduleAnimateResizePip(toBounds, false /* configAtEnd */); 157 } 158 159 /** 160 * Animates resizing of the pinned stack given the duration. 161 * 162 * @param configAtEnd true if we are delaying config updates until the transition ends. 163 */ scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd)164 public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) { 165 if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) { 166 return; 167 } 168 WindowContainerTransaction wct = new WindowContainerTransaction(); 169 wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds); 170 if (configAtEnd) { 171 wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken); 172 } 173 mPipTransitionController.startResizeTransition(wct); 174 } 175 176 /** 177 * Signals to Core to finish the PiP resize transition. 178 * Note that we do not allow any actual WM Core changes at this point. 179 * 180 * @param configAtEnd true if we are waiting for config updates at the end of the transition. 181 */ scheduleFinishResizePip(boolean configAtEnd)182 public void scheduleFinishResizePip(boolean configAtEnd) { 183 SurfaceControl.Transaction tx = null; 184 if (configAtEnd) { 185 tx = new SurfaceControl.Transaction(); 186 tx.addTransactionCommittedListener(mMainExecutor, () -> { 187 mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); 188 }); 189 } else { 190 mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); 191 } 192 mPipTransitionController.finishTransition(tx); 193 } 194 195 /** 196 * Directly perform a scaled matrix transformation on the leash. This will not perform any 197 * {@link WindowContainerTransaction}. 198 */ scheduleUserResizePip(Rect toBounds)199 public void scheduleUserResizePip(Rect toBounds) { 200 scheduleUserResizePip(toBounds, 0f /* degrees */); 201 } 202 203 /** 204 * Directly perform a scaled matrix transformation on the leash. This will not perform any 205 * {@link WindowContainerTransaction}. 206 * 207 * @param degrees the angle to rotate the bounds to. 208 */ scheduleUserResizePip(Rect toBounds, float degrees)209 public void scheduleUserResizePip(Rect toBounds, float degrees) { 210 if (toBounds.isEmpty()) { 211 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 212 "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); 213 return; 214 } 215 SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; 216 final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 217 218 Matrix transformTensor = new Matrix(); 219 final float[] mMatrixTmp = new float[9]; 220 final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width(); 221 222 transformTensor.setScale(scale, scale); 223 transformTensor.postTranslate(toBounds.left, toBounds.top); 224 transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY()); 225 226 tx.setMatrix(leash, transformTensor, mMatrixTmp); 227 tx.apply(); 228 } 229 } 230