1 /* 2 * Copyright (C) 2017 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 android.app; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.TestApi; 22 import android.graphics.Rect; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.util.Rational; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Objects; 30 31 /** 32 * Represents a set of parameters used to initialize and update an Activity in picture-in-picture 33 * mode. 34 */ 35 public final class PictureInPictureParams implements Parcelable { 36 37 /** 38 * Builder class for {@link PictureInPictureParams} objects. 39 */ 40 public static class Builder { 41 42 @Nullable 43 private Rational mAspectRatio; 44 45 @Nullable 46 private List<RemoteAction> mUserActions; 47 48 @Nullable 49 private Rect mSourceRectHint; 50 51 private Boolean mAutoEnterEnabled; 52 53 private Boolean mSeamlessResizeEnabled; 54 55 /** 56 * Sets the aspect ratio. This aspect ratio is defined as the desired width / height, and 57 * does not change upon device rotation. 58 * 59 * @param aspectRatio the new aspect ratio for the activity in picture-in-picture, must be 60 * between 2.39:1 and 1:2.39 (inclusive). 61 * 62 * @return this builder instance. 63 */ setAspectRatio(Rational aspectRatio)64 public Builder setAspectRatio(Rational aspectRatio) { 65 mAspectRatio = aspectRatio; 66 return this; 67 } 68 69 /** 70 * Sets the user actions. If there are more than 71 * {@link Activity#getMaxNumPictureInPictureActions()} actions, then the input list 72 * will be truncated to that number. 73 * 74 * @param actions the new actions to show in the picture-in-picture menu. 75 * 76 * @return this builder instance. 77 * 78 * @see RemoteAction 79 */ setActions(List<RemoteAction> actions)80 public Builder setActions(List<RemoteAction> actions) { 81 if (mUserActions != null) { 82 mUserActions = null; 83 } 84 if (actions != null) { 85 mUserActions = new ArrayList<>(actions); 86 } 87 return this; 88 } 89 90 /** 91 * Sets the source bounds hint. These bounds are only used when an activity first enters 92 * picture-in-picture, and describe the bounds in window coordinates of activity entering 93 * picture-in-picture that will be visible following the transition. For the best effect, 94 * these bounds should also match the aspect ratio in the arguments. 95 * 96 * @param launchBounds window-coordinate bounds indicating the area of the activity that 97 * will still be visible following the transition into picture-in-picture (eg. the video 98 * view bounds in a video player) 99 * 100 * @return this builder instance. 101 */ setSourceRectHint(Rect launchBounds)102 public Builder setSourceRectHint(Rect launchBounds) { 103 if (launchBounds == null) { 104 mSourceRectHint = null; 105 } else { 106 mSourceRectHint = new Rect(launchBounds); 107 } 108 return this; 109 } 110 111 /** 112 * Sets whether the system will automatically put the activity in picture-in-picture mode 113 * without needing/waiting for the activity to call 114 * {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}. 115 * 116 * If true, {@link Activity#onPictureInPictureRequested()} will never be called. 117 * 118 * This property is {@code false} by default. 119 * @param autoEnterEnabled {@code true} if the system will automatically put the activity 120 * in picture-in-picture mode. 121 * 122 * @return this builder instance. 123 */ 124 @NonNull setAutoEnterEnabled(boolean autoEnterEnabled)125 public Builder setAutoEnterEnabled(boolean autoEnterEnabled) { 126 mAutoEnterEnabled = autoEnterEnabled; 127 return this; 128 } 129 130 /** 131 * Sets whether the system can seamlessly resize the window while the activity is in 132 * picture-in-picture mode. This should normally be the case for video content and 133 * when it's set to {@code false}, system will perform transitions to overcome the 134 * artifacts due to resize. 135 * 136 * This property is {@code true} by default for backwards compatibility. 137 * @param seamlessResizeEnabled {@code true} if the system can seamlessly resize the window 138 * while activity is in picture-in-picture mode. 139 * @return this builder instance. 140 */ 141 @NonNull setSeamlessResizeEnabled(boolean seamlessResizeEnabled)142 public Builder setSeamlessResizeEnabled(boolean seamlessResizeEnabled) { 143 mSeamlessResizeEnabled = seamlessResizeEnabled; 144 return this; 145 } 146 147 /** 148 * @return an immutable {@link PictureInPictureParams} to be used when entering or updating 149 * the activity in picture-in-picture. 150 * 151 * @see Activity#enterPictureInPictureMode(PictureInPictureParams) 152 * @see Activity#setPictureInPictureParams(PictureInPictureParams) 153 */ build()154 public PictureInPictureParams build() { 155 PictureInPictureParams params = new PictureInPictureParams(mAspectRatio, mUserActions, 156 mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled); 157 return params; 158 } 159 } 160 161 /** 162 * The expected aspect ratio of the picture-in-picture. 163 */ 164 @Nullable 165 private Rational mAspectRatio; 166 167 /** 168 * The set of actions that are associated with this activity when in picture-in-picture. 169 */ 170 @Nullable 171 private List<RemoteAction> mUserActions; 172 173 /** 174 * The source bounds hint used when entering picture-in-picture, relative to the window bounds. 175 * We can use this internally for the transition into picture-in-picture to ensure that a 176 * particular source rect is visible throughout the whole transition. 177 */ 178 @Nullable 179 private Rect mSourceRectHint; 180 181 /** 182 * Whether the system is allowed to automatically put the activity in picture-in-picture mode. 183 * {@link #isAutoEnterEnabled()} defaults to {@code false} if this is not set. 184 */ 185 private Boolean mAutoEnterEnabled; 186 187 /** 188 * Whether system can seamlessly resize the window when activity is in picture-in-picture mode. 189 * {@link #isSeamlessResizeEnabled()} defaults to {@code true} if this is not set for 190 * backwards compatibility. 191 */ 192 private Boolean mSeamlessResizeEnabled; 193 194 /** {@hide} */ PictureInPictureParams()195 PictureInPictureParams() { 196 } 197 198 /** {@hide} */ PictureInPictureParams(Parcel in)199 PictureInPictureParams(Parcel in) { 200 if (in.readInt() != 0) { 201 mAspectRatio = new Rational(in.readInt(), in.readInt()); 202 } 203 if (in.readInt() != 0) { 204 mUserActions = new ArrayList<>(); 205 in.readTypedList(mUserActions, RemoteAction.CREATOR); 206 } 207 if (in.readInt() != 0) { 208 mSourceRectHint = Rect.CREATOR.createFromParcel(in); 209 } 210 if (in.readInt() != 0) { 211 mAutoEnterEnabled = in.readBoolean(); 212 } 213 if (in.readInt() != 0) { 214 mSeamlessResizeEnabled = in.readBoolean(); 215 } 216 } 217 218 /** {@hide} */ PictureInPictureParams(Rational aspectRatio, List<RemoteAction> actions, Rect sourceRectHint, Boolean autoEnterEnabled, Boolean seamlessResizeEnabled)219 PictureInPictureParams(Rational aspectRatio, List<RemoteAction> actions, 220 Rect sourceRectHint, Boolean autoEnterEnabled, Boolean seamlessResizeEnabled) { 221 mAspectRatio = aspectRatio; 222 mUserActions = actions; 223 mSourceRectHint = sourceRectHint; 224 mAutoEnterEnabled = autoEnterEnabled; 225 mSeamlessResizeEnabled = seamlessResizeEnabled; 226 } 227 228 /** 229 * Makes a copy from the other picture-in-picture args. 230 * @hide 231 */ PictureInPictureParams(PictureInPictureParams other)232 public PictureInPictureParams(PictureInPictureParams other) { 233 this(other.mAspectRatio, other.mUserActions, 234 other.hasSourceBoundsHint() ? new Rect(other.getSourceRectHint()) : null, 235 other.mAutoEnterEnabled, other.mSeamlessResizeEnabled); 236 } 237 238 /** 239 * Copies the set parameters from the other picture-in-picture args. 240 * @hide 241 */ copyOnlySet(PictureInPictureParams otherArgs)242 public void copyOnlySet(PictureInPictureParams otherArgs) { 243 if (otherArgs.hasSetAspectRatio()) { 244 mAspectRatio = otherArgs.mAspectRatio; 245 } 246 if (otherArgs.hasSetActions()) { 247 mUserActions = otherArgs.mUserActions; 248 } 249 if (otherArgs.hasSourceBoundsHint()) { 250 mSourceRectHint = new Rect(otherArgs.getSourceRectHint()); 251 } 252 if (otherArgs.mAutoEnterEnabled != null) { 253 mAutoEnterEnabled = otherArgs.mAutoEnterEnabled; 254 } 255 if (otherArgs.mSeamlessResizeEnabled != null) { 256 mSeamlessResizeEnabled = otherArgs.mSeamlessResizeEnabled; 257 } 258 } 259 260 /** 261 * @return the aspect ratio. If none is set, return 0. 262 * @hide 263 */ 264 @TestApi getAspectRatio()265 public float getAspectRatio() { 266 if (mAspectRatio != null) { 267 return mAspectRatio.floatValue(); 268 } 269 return 0f; 270 } 271 272 /** @hide */ getAspectRatioRational()273 public Rational getAspectRatioRational() { 274 return mAspectRatio; 275 } 276 277 /** 278 * @return whether the aspect ratio is set. 279 * @hide 280 */ hasSetAspectRatio()281 public boolean hasSetAspectRatio() { 282 return mAspectRatio != null; 283 } 284 285 /** 286 * @return the set of user actions. 287 * @hide 288 */ 289 @TestApi getActions()290 public List<RemoteAction> getActions() { 291 return mUserActions; 292 } 293 294 /** 295 * @return whether the user actions are set. 296 * @hide 297 */ hasSetActions()298 public boolean hasSetActions() { 299 return mUserActions != null; 300 } 301 302 /** 303 * Truncates the set of actions to the given {@param size}. 304 * @hide 305 */ truncateActions(int size)306 public void truncateActions(int size) { 307 if (hasSetActions()) { 308 mUserActions = mUserActions.subList(0, Math.min(mUserActions.size(), size)); 309 } 310 } 311 312 /** 313 * @return the source rect hint 314 * @hide 315 */ 316 @TestApi getSourceRectHint()317 public Rect getSourceRectHint() { 318 return mSourceRectHint; 319 } 320 321 /** 322 * @return whether there are launch bounds set 323 * @hide 324 */ hasSourceBoundsHint()325 public boolean hasSourceBoundsHint() { 326 return mSourceRectHint != null && !mSourceRectHint.isEmpty(); 327 } 328 329 /** 330 * @return whether auto pip is enabled. 331 * @hide 332 */ isAutoEnterEnabled()333 public boolean isAutoEnterEnabled() { 334 return mAutoEnterEnabled == null ? false : mAutoEnterEnabled; 335 } 336 337 /** 338 * @return whether seamless resize is enabled. 339 * @hide 340 */ 341 @TestApi isSeamlessResizeEnabled()342 public boolean isSeamlessResizeEnabled() { 343 return mSeamlessResizeEnabled == null ? true : mSeamlessResizeEnabled; 344 } 345 346 /** 347 * @return True if no parameters are set 348 * @hide 349 */ empty()350 public boolean empty() { 351 return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio() 352 && mAutoEnterEnabled != null && mSeamlessResizeEnabled != null; 353 } 354 355 @Override equals(Object o)356 public boolean equals(Object o) { 357 if (this == o) return true; 358 if (!(o instanceof PictureInPictureParams)) return false; 359 PictureInPictureParams that = (PictureInPictureParams) o; 360 return Objects.equals(mAutoEnterEnabled, that.mAutoEnterEnabled) 361 && Objects.equals(mSeamlessResizeEnabled, that.mSeamlessResizeEnabled) 362 && Objects.equals(mAspectRatio, that.mAspectRatio) 363 && Objects.equals(mUserActions, that.mUserActions) 364 && Objects.equals(mSourceRectHint, that.mSourceRectHint); 365 } 366 367 @Override hashCode()368 public int hashCode() { 369 return Objects.hash(mAspectRatio, mUserActions, mSourceRectHint, 370 mAutoEnterEnabled, mSeamlessResizeEnabled); 371 } 372 373 @Override describeContents()374 public int describeContents() { 375 return 0; 376 } 377 378 @Override writeToParcel(Parcel out, int flags)379 public void writeToParcel(Parcel out, int flags) { 380 if (mAspectRatio != null) { 381 out.writeInt(1); 382 out.writeInt(mAspectRatio.getNumerator()); 383 out.writeInt(mAspectRatio.getDenominator()); 384 } else { 385 out.writeInt(0); 386 } 387 if (mUserActions != null) { 388 out.writeInt(1); 389 out.writeTypedList(mUserActions, 0); 390 } else { 391 out.writeInt(0); 392 } 393 if (mSourceRectHint != null) { 394 out.writeInt(1); 395 mSourceRectHint.writeToParcel(out, 0); 396 } else { 397 out.writeInt(0); 398 } 399 if (mAutoEnterEnabled != null) { 400 out.writeInt(1); 401 out.writeBoolean(mAutoEnterEnabled); 402 } else { 403 out.writeInt(0); 404 } 405 if (mSeamlessResizeEnabled != null) { 406 out.writeInt(1); 407 out.writeBoolean(mSeamlessResizeEnabled); 408 } else { 409 out.writeInt(0); 410 } 411 } 412 413 @Override toString()414 public String toString() { 415 return "PictureInPictureParams(" 416 + " aspectRatio=" + getAspectRatioRational() 417 + " sourceRectHint=" + getSourceRectHint() 418 + " hasSetActions=" + hasSetActions() 419 + " isAutoPipEnabled=" + isAutoEnterEnabled() 420 + " isSeamlessResizeEnabled=" + isSeamlessResizeEnabled() 421 + ")"; 422 } 423 424 public static final @android.annotation.NonNull Creator<PictureInPictureParams> CREATOR = 425 new Creator<PictureInPictureParams>() { 426 public PictureInPictureParams createFromParcel(Parcel in) { 427 return new PictureInPictureParams(in); 428 } 429 public PictureInPictureParams[] newArray(int size) { 430 return new PictureInPictureParams[size]; 431 } 432 }; 433 } 434