1 /* 2 * Copyright (C) 2020 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.pip; 18 19 import static android.util.TypedValue.COMPLEX_UNIT_DIP; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.PictureInPictureParams; 24 import android.content.Context; 25 import android.content.pm.ActivityInfo; 26 import android.content.res.Resources; 27 import android.graphics.Point; 28 import android.graphics.PointF; 29 import android.graphics.Rect; 30 import android.util.DisplayMetrics; 31 import android.util.Size; 32 import android.util.TypedValue; 33 import android.view.Gravity; 34 35 import com.android.wm.shell.common.DisplayLayout; 36 37 import java.io.PrintWriter; 38 39 /** 40 * Calculates the default, normal, entry, inset and movement bounds of the PIP. 41 */ 42 public class PipBoundsAlgorithm { 43 44 private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); 45 private static final float INVALID_SNAP_FRACTION = -1f; 46 47 private final @NonNull PipBoundsState mPipBoundsState; 48 private final PipSnapAlgorithm mSnapAlgorithm; 49 50 private float mDefaultSizePercent; 51 private float mMinAspectRatioForMinSize; 52 private float mMaxAspectRatioForMinSize; 53 private float mDefaultAspectRatio; 54 private float mMinAspectRatio; 55 private float mMaxAspectRatio; 56 private int mDefaultStackGravity; 57 private int mDefaultMinSize; 58 private int mOverridableMinSize; 59 private Point mScreenEdgeInsets; 60 PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm)61 public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, 62 @NonNull PipSnapAlgorithm pipSnapAlgorithm) { 63 mPipBoundsState = pipBoundsState; 64 mSnapAlgorithm = pipSnapAlgorithm; 65 reloadResources(context); 66 // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload 67 // resources as it would clobber mAspectRatio when entering PiP from fullscreen which 68 // triggers a configuration change and the resources to be reloaded. 69 mPipBoundsState.setAspectRatio(mDefaultAspectRatio); 70 mPipBoundsState.setMinEdgeSize(mDefaultMinSize); 71 } 72 73 /** 74 * TODO: move the resources to SysUI package. 75 */ reloadResources(Context context)76 private void reloadResources(Context context) { 77 final Resources res = context.getResources(); 78 mDefaultAspectRatio = res.getFloat( 79 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio); 80 mDefaultStackGravity = res.getInteger( 81 com.android.internal.R.integer.config_defaultPictureInPictureGravity); 82 mDefaultMinSize = res.getDimensionPixelSize( 83 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task); 84 mOverridableMinSize = res.getDimensionPixelSize( 85 com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task); 86 final String screenEdgeInsetsDpString = res.getString( 87 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets); 88 final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() 89 ? Size.parseSize(screenEdgeInsetsDpString) 90 : null; 91 mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point() 92 : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()), 93 dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics())); 94 mMinAspectRatio = res.getFloat( 95 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); 96 mMaxAspectRatio = res.getFloat( 97 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio); 98 mDefaultSizePercent = res.getFloat( 99 com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent); 100 mMaxAspectRatioForMinSize = res.getFloat( 101 com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize); 102 mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize; 103 } 104 105 /** 106 * The {@link PipSnapAlgorithm} is couple on display bounds 107 * @return {@link PipSnapAlgorithm}. 108 */ getSnapAlgorithm()109 public PipSnapAlgorithm getSnapAlgorithm() { 110 return mSnapAlgorithm; 111 } 112 113 /** Responds to configuration change. */ onConfigurationChanged(Context context)114 public void onConfigurationChanged(Context context) { 115 reloadResources(context); 116 } 117 118 /** Returns the normal bounds (i.e. the default entry bounds). */ getNormalBounds()119 public Rect getNormalBounds() { 120 // The normal bounds are the default bounds adjusted to the current aspect ratio. 121 return transformBoundsToAspectRatioIfValid(getDefaultBounds(), 122 mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, 123 false /* useCurrentSize */); 124 } 125 126 /** Returns the default bounds. */ getDefaultBounds()127 public Rect getDefaultBounds() { 128 return getDefaultBounds(INVALID_SNAP_FRACTION, null /* size */); 129 } 130 131 /** Returns the destination bounds to place the PIP window on entry. */ getEntryDestinationBounds()132 public Rect getEntryDestinationBounds() { 133 final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState(); 134 135 final Rect destinationBounds = reentryState != null 136 ? getDefaultBounds(reentryState.getSnapFraction(), reentryState.getSize()) 137 : getDefaultBounds(); 138 139 final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null; 140 return transformBoundsToAspectRatioIfValid(destinationBounds, 141 mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, 142 useCurrentSize); 143 } 144 145 /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio)146 public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) { 147 return transformBoundsToAspectRatioIfValid(currentBounds, newAspectRatio, 148 true /* useCurrentMinEdgeSize */, false /* useCurrentSize */); 149 } 150 151 /** 152 * 153 * Get the smallest/most minimal size allowed. 154 */ getMinimalSize(ActivityInfo activityInfo)155 public Size getMinimalSize(ActivityInfo activityInfo) { 156 if (activityInfo == null || activityInfo.windowLayout == null) { 157 return null; 158 } 159 final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout; 160 // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout> 161 // without minWidth/minHeight 162 if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) { 163 // If either dimension is smaller than the allowed minimum, adjust them 164 // according to mOverridableMinSize 165 return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize), 166 Math.max(windowLayout.minHeight, mOverridableMinSize)); 167 } 168 return null; 169 } 170 171 /** 172 * Returns the source hint rect if it is valid (if provided and is contained by the current 173 * task bounds). 174 */ getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds)175 public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds) { 176 final Rect sourceHintRect = params != null && params.hasSourceBoundsHint() 177 ? params.getSourceRectHint() 178 : null; 179 if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) { 180 return sourceHintRect; 181 } 182 return null; 183 } 184 getDefaultAspectRatio()185 public float getDefaultAspectRatio() { 186 return mDefaultAspectRatio; 187 } 188 189 /** 190 * 191 * Give the aspect ratio if the supplied PiP params have one, or else return default. 192 */ getAspectRatioOrDefault( @ndroid.annotation.Nullable PictureInPictureParams params)193 public float getAspectRatioOrDefault( 194 @android.annotation.Nullable PictureInPictureParams params) { 195 return params != null && params.hasSetAspectRatio() 196 ? params.getAspectRatio() 197 : getDefaultAspectRatio(); 198 } 199 200 /** 201 * @return whether the given {@param aspectRatio} is valid. 202 */ isValidPictureInPictureAspectRatio(float aspectRatio)203 private boolean isValidPictureInPictureAspectRatio(float aspectRatio) { 204 return Float.compare(mMinAspectRatio, aspectRatio) <= 0 205 && Float.compare(aspectRatio, mMaxAspectRatio) <= 0; 206 } 207 transformBoundsToAspectRatioIfValid(Rect bounds, float aspectRatio, boolean useCurrentMinEdgeSize, boolean useCurrentSize)208 private Rect transformBoundsToAspectRatioIfValid(Rect bounds, float aspectRatio, 209 boolean useCurrentMinEdgeSize, boolean useCurrentSize) { 210 final Rect destinationBounds = new Rect(bounds); 211 if (isValidPictureInPictureAspectRatio(aspectRatio)) { 212 transformBoundsToAspectRatio(destinationBounds, aspectRatio, 213 useCurrentMinEdgeSize, useCurrentSize); 214 } 215 return destinationBounds; 216 } 217 218 /** 219 * Set the current bounds (or the default bounds if there are no current bounds) with the 220 * specified aspect ratio. 221 */ transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio, boolean useCurrentMinEdgeSize, boolean useCurrentSize)222 public void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio, 223 boolean useCurrentMinEdgeSize, boolean useCurrentSize) { 224 // Save the snap fraction and adjust the size based on the new aspect ratio. 225 final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds, 226 getMovementBounds(stackBounds), mPipBoundsState.getStashedState()); 227 228 final Size overrideMinSize = mPipBoundsState.getOverrideMinSize(); 229 final Size size; 230 if (useCurrentMinEdgeSize || useCurrentSize) { 231 // The default minimum edge size, or the override min edge size if set. 232 final int defaultMinEdgeSize = overrideMinSize == null ? mDefaultMinSize 233 : mPipBoundsState.getOverrideMinEdgeSize(); 234 final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize() 235 : defaultMinEdgeSize; 236 // Use the existing size but adjusted to the aspect ratio and min edge size. 237 size = getSizeForAspectRatio( 238 new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize); 239 } else { 240 if (overrideMinSize != null) { 241 // The override minimal size is set, use that as the default size making sure it's 242 // adjusted to the aspect ratio. 243 size = adjustSizeToAspectRatio(overrideMinSize, aspectRatio); 244 } else { 245 // Calculate the default size using the display size and default min edge size. 246 final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout(); 247 size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize, 248 displayLayout.width(), displayLayout.height()); 249 } 250 } 251 252 final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); 253 final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f); 254 stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight()); 255 mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction); 256 } 257 258 /** Adjusts the given size to conform to the given aspect ratio. */ adjustSizeToAspectRatio(@onNull Size size, float aspectRatio)259 private Size adjustSizeToAspectRatio(@NonNull Size size, float aspectRatio) { 260 final float sizeAspectRatio = size.getWidth() / (float) size.getHeight(); 261 if (sizeAspectRatio > aspectRatio) { 262 // Size is wider, fix the width and increase the height 263 return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio)); 264 } else { 265 // Size is taller, fix the height and adjust the width. 266 return new Size((int) (size.getHeight() * aspectRatio), size.getHeight()); 267 } 268 } 269 270 /** 271 * @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are 272 * provided, then it will apply the default bounds to the provided snap fraction and size. 273 */ getDefaultBounds(float snapFraction, Size size)274 private Rect getDefaultBounds(float snapFraction, Size size) { 275 final Rect defaultBounds = new Rect(); 276 if (snapFraction != INVALID_SNAP_FRACTION && size != null) { 277 // The default bounds are the given size positioned at the given snap fraction. 278 defaultBounds.set(0, 0, size.getWidth(), size.getHeight()); 279 final Rect movementBounds = getMovementBounds(defaultBounds); 280 mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction); 281 return defaultBounds; 282 } 283 284 // Calculate the default size. 285 final Size defaultSize; 286 final Rect insetBounds = new Rect(); 287 getInsetBounds(insetBounds); 288 final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout(); 289 final Size overrideMinSize = mPipBoundsState.getOverrideMinSize(); 290 if (overrideMinSize != null) { 291 // The override minimal size is set, use that as the default size making sure it's 292 // adjusted to the aspect ratio. 293 defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio); 294 } else { 295 // Calculate the default size using the display size and default min edge size. 296 defaultSize = getSizeForAspectRatio(mDefaultAspectRatio, 297 mDefaultMinSize, displayLayout.width(), displayLayout.height()); 298 } 299 300 // Now that we have the default size, apply the snap fraction if valid or position the 301 // bounds using the default gravity. 302 if (snapFraction != INVALID_SNAP_FRACTION) { 303 defaultBounds.set(0, 0, defaultSize.getWidth(), defaultSize.getHeight()); 304 final Rect movementBounds = getMovementBounds(defaultBounds); 305 mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction); 306 } else { 307 Gravity.apply(mDefaultStackGravity, defaultSize.getWidth(), defaultSize.getHeight(), 308 insetBounds, 0, Math.max( 309 mPipBoundsState.isImeShowing() ? mPipBoundsState.getImeHeight() : 0, 310 mPipBoundsState.isShelfShowing() 311 ? mPipBoundsState.getShelfHeight() : 0), defaultBounds); 312 } 313 return defaultBounds; 314 } 315 316 /** 317 * Populates the bounds on the screen that the PIP can be visible in. 318 */ getInsetBounds(Rect outRect)319 public void getInsetBounds(Rect outRect) { 320 final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout(); 321 Rect insets = mPipBoundsState.getDisplayLayout().stableInsets(); 322 outRect.set(insets.left + mScreenEdgeInsets.x, 323 insets.top + mScreenEdgeInsets.y, 324 displayLayout.width() - insets.right - mScreenEdgeInsets.x, 325 displayLayout.height() - insets.bottom - mScreenEdgeInsets.y); 326 } 327 328 /** 329 * @return the movement bounds for the given stackBounds and the current state of the 330 * controller. 331 */ getMovementBounds(Rect stackBounds)332 public Rect getMovementBounds(Rect stackBounds) { 333 return getMovementBounds(stackBounds, true /* adjustForIme */); 334 } 335 336 /** 337 * @return the movement bounds for the given stackBounds and the current state of the 338 * controller. 339 */ getMovementBounds(Rect stackBounds, boolean adjustForIme)340 public Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) { 341 final Rect movementBounds = new Rect(); 342 getInsetBounds(movementBounds); 343 344 // Apply the movement bounds adjustments based on the current state. 345 getMovementBounds(stackBounds, movementBounds, movementBounds, 346 (adjustForIme && mPipBoundsState.isImeShowing()) 347 ? mPipBoundsState.getImeHeight() : 0); 348 349 return movementBounds; 350 } 351 352 /** 353 * Adjusts movementBoundsOut so that it is the movement bounds for the given stackBounds. 354 */ getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut, int bottomOffset)355 public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut, 356 int bottomOffset) { 357 // Adjust the right/bottom to ensure the stack bounds never goes offscreen 358 movementBoundsOut.set(insetBounds); 359 movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right 360 - stackBounds.width()); 361 movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom 362 - stackBounds.height()); 363 movementBoundsOut.bottom -= bottomOffset; 364 } 365 366 /** 367 * @return the default snap fraction to apply instead of the default gravity when calculating 368 * the default stack bounds when first entering PiP. 369 */ getSnapFraction(Rect stackBounds)370 public float getSnapFraction(Rect stackBounds) { 371 return getSnapFraction(stackBounds, getMovementBounds(stackBounds)); 372 } 373 374 /** 375 * @return the default snap fraction to apply instead of the default gravity when calculating 376 * the default stack bounds when first entering PiP. 377 */ getSnapFraction(Rect stackBounds, Rect movementBounds)378 public float getSnapFraction(Rect stackBounds, Rect movementBounds) { 379 return mSnapAlgorithm.getSnapFraction(stackBounds, movementBounds); 380 } 381 382 /** 383 * Applies the given snap fraction to the given stack bounds. 384 */ applySnapFraction(Rect stackBounds, float snapFraction)385 public void applySnapFraction(Rect stackBounds, float snapFraction) { 386 final Rect movementBounds = getMovementBounds(stackBounds); 387 mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction); 388 } 389 getDefaultMinSize()390 public int getDefaultMinSize() { 391 return mDefaultMinSize; 392 } 393 394 /** 395 * @return the pixels for a given dp value. 396 */ dpToPx(float dpValue, DisplayMetrics dm)397 private int dpToPx(float dpValue, DisplayMetrics dm) { 398 return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm); 399 } 400 401 /** 402 * @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge 403 * is at least minEdgeSize. 404 */ getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth, int displayHeight)405 public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth, 406 int displayHeight) { 407 final int smallestDisplaySize = Math.min(displayWidth, displayHeight); 408 final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent); 409 410 final int width; 411 final int height; 412 if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) { 413 // Beyond these points, we can just use the min size as the shorter edge 414 if (aspectRatio <= 1) { 415 // Portrait, width is the minimum size 416 width = minSize; 417 height = Math.round(width / aspectRatio); 418 } else { 419 // Landscape, height is the minimum size 420 height = minSize; 421 width = Math.round(height * aspectRatio); 422 } 423 } else { 424 // Within these points, we ensure that the bounds fit within the radius of the limits 425 // at the points 426 final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize; 427 final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize); 428 height = (int) Math.round(Math.sqrt((radius * radius) 429 / (aspectRatio * aspectRatio + 1))); 430 width = Math.round(height * aspectRatio); 431 } 432 return new Size(width, height); 433 } 434 435 /** 436 * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the 437 * minimum edge is at least minEdgeSize. 438 */ getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize)439 public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) { 440 final int smallestSize = Math.min(size.getWidth(), size.getHeight()); 441 final int minSize = (int) Math.max(minEdgeSize, smallestSize); 442 443 final int width; 444 final int height; 445 if (aspectRatio <= 1) { 446 // Portrait, width is the minimum size. 447 width = minSize; 448 height = Math.round(width / aspectRatio); 449 } else { 450 // Landscape, height is the minimum size 451 height = minSize; 452 width = Math.round(height * aspectRatio); 453 } 454 return new Size(width, height); 455 } 456 457 /** 458 * @return the normal bounds adjusted so that they fit the menu actions. 459 */ adjustNormalBoundsToFitMenu(@onNull Rect normalBounds, @Nullable Size minMenuSize)460 public Rect adjustNormalBoundsToFitMenu(@NonNull Rect normalBounds, 461 @Nullable Size minMenuSize) { 462 if (minMenuSize == null) { 463 return normalBounds; 464 } 465 if (normalBounds.width() >= minMenuSize.getWidth() 466 && normalBounds.height() >= minMenuSize.getHeight()) { 467 // The normal bounds can fit the menu as is, no need to adjust the bounds. 468 return normalBounds; 469 } 470 final Rect adjustedNormalBounds = new Rect(); 471 final boolean needsWidthAdj = minMenuSize.getWidth() > normalBounds.width(); 472 final boolean needsHeightAdj = minMenuSize.getHeight() > normalBounds.height(); 473 final int adjWidth; 474 final int adjHeight; 475 if (needsWidthAdj && needsHeightAdj) { 476 // Both the width and the height are too small - find the edge that needs the larger 477 // adjustment and scale that edge. The other edge will scale beyond the minMenuSize 478 // when the aspect ratio is applied. 479 final float widthScaleFactor = 480 ((float) (minMenuSize.getWidth())) / ((float) (normalBounds.width())); 481 final float heightScaleFactor = 482 ((float) (minMenuSize.getHeight())) / ((float) (normalBounds.height())); 483 if (widthScaleFactor > heightScaleFactor) { 484 adjWidth = minMenuSize.getWidth(); 485 adjHeight = Math.round(adjWidth / mPipBoundsState.getAspectRatio()); 486 } else { 487 adjHeight = minMenuSize.getHeight(); 488 adjWidth = Math.round(adjHeight * mPipBoundsState.getAspectRatio()); 489 } 490 } else if (needsWidthAdj) { 491 // Width is too small - use the min menu size width instead. 492 adjWidth = minMenuSize.getWidth(); 493 adjHeight = Math.round(adjWidth / mPipBoundsState.getAspectRatio()); 494 } else { 495 // Height is too small - use the min menu size height instead. 496 adjHeight = minMenuSize.getHeight(); 497 adjWidth = Math.round(adjHeight * mPipBoundsState.getAspectRatio()); 498 } 499 adjustedNormalBounds.set(0, 0, adjWidth, adjHeight); 500 // Make sure the bounds conform to the aspect ratio and min edge size. 501 transformBoundsToAspectRatio(adjustedNormalBounds, 502 mPipBoundsState.getAspectRatio(), true /* useCurrentMinEdgeSize */, 503 true /* useCurrentSize */); 504 return adjustedNormalBounds; 505 } 506 507 /** 508 * Dumps internal states. 509 */ dump(PrintWriter pw, String prefix)510 public void dump(PrintWriter pw, String prefix) { 511 final String innerPrefix = prefix + " "; 512 pw.println(prefix + TAG); 513 pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio); 514 pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio); 515 pw.println(innerPrefix + "mMaxAspectRatio=" + mMaxAspectRatio); 516 pw.println(innerPrefix + "mDefaultStackGravity=" + mDefaultStackGravity); 517 pw.println(innerPrefix + "mSnapAlgorithm" + mSnapAlgorithm); 518 } 519 } 520