• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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