• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.server.wm;
18 
19 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
20 import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA;
21 import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE;
22 import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA;
23 import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
24 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
25 
26 import static com.android.window.flags.Flags.enableSizeCompatModeImprovementsForConnectedDisplays;
27 import static com.android.server.wm.AppCompatUtils.isInDesktopMode;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.content.pm.ActivityInfo;
32 import android.content.res.Configuration;
33 import android.graphics.Rect;
34 
35 import java.io.PrintWriter;
36 import java.util.function.DoubleSupplier;
37 
38 /**
39  * Encapsulate logic related to the SizeCompatMode.
40  */
41 class AppCompatSizeCompatModePolicy {
42 
43     @NonNull
44     private final ActivityRecord mActivityRecord;
45     @NonNull
46     private final AppCompatOverrides mAppCompatOverrides;
47 
48     // Whether this activity is in size compatibility mode because its bounds don't fit in parent
49     // naturally.
50     private boolean mInSizeCompatModeForBounds = false;
51     /**
52      * The scale to fit at least one side of the activity to its parent. If the activity uses
53      * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
54      */
55     private float mSizeCompatScale = 1f;
56 
57     /**
58      * The bounds in global coordinates for activity in size compatibility mode.
59      * @see #hasSizeCompatBounds()
60      */
61     private Rect mSizeCompatBounds;
62 
63     /**
64      * The precomputed display insets for resolving configuration. It will be non-null if
65      * {@link ActivityRecord#shouldCreateAppCompatDisplayInsets} returns {@code true}.
66      */
67     @Nullable
68     private AppCompatDisplayInsets mAppCompatDisplayInsets;
69 
AppCompatSizeCompatModePolicy(@onNull ActivityRecord activityRecord, @NonNull AppCompatOverrides appCompatOverrides)70     AppCompatSizeCompatModePolicy(@NonNull ActivityRecord activityRecord,
71             @NonNull AppCompatOverrides appCompatOverrides) {
72         mActivityRecord = activityRecord;
73         mAppCompatOverrides = appCompatOverrides;
74     }
75 
isInSizeCompatModeForBounds()76     boolean isInSizeCompatModeForBounds() {
77         return mInSizeCompatModeForBounds;
78     }
79 
setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds)80     void setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds) {
81         mInSizeCompatModeForBounds = inSizeCompatModeForBounds;
82     }
83 
hasSizeCompatBounds()84     boolean hasSizeCompatBounds() {
85         return mSizeCompatBounds != null;
86     }
87 
88     /**
89      * @return The {@code true} if the current instance has
90      * {@link AppCompatSizeCompatModePolicy#mAppCompatDisplayInsets} without
91      * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
92      */
hasAppCompatDisplayInsetsWithoutInheritance()93     boolean hasAppCompatDisplayInsetsWithoutInheritance() {
94         return mAppCompatDisplayInsets != null;
95     }
96 
97     @Nullable
getAppCompatDisplayInsets()98     AppCompatDisplayInsets getAppCompatDisplayInsets() {
99         final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController
100                 .getTransparentPolicy();
101         if (transparentPolicy.isRunning()) {
102             return transparentPolicy.getInheritedAppCompatDisplayInsets();
103         }
104         return mAppCompatDisplayInsets;
105     }
106 
getCompatScaleIfAvailable(@onNull DoubleSupplier scaleWhenNotAvailable)107     float getCompatScaleIfAvailable(@NonNull DoubleSupplier scaleWhenNotAvailable) {
108         return hasSizeCompatBounds() ? mSizeCompatScale
109                 : (float) scaleWhenNotAvailable.getAsDouble();
110     }
111 
112     @NonNull
getAppSizeCompatBoundsIfAvailable(@onNull Rect boundsWhenNotAvailable)113     Rect getAppSizeCompatBoundsIfAvailable(@NonNull Rect boundsWhenNotAvailable) {
114         return hasSizeCompatBounds() ? mSizeCompatBounds : boundsWhenNotAvailable;
115     }
116 
117     @NonNull
replaceResolvedBoundsIfNeeded(@onNull Rect resolvedBounds)118     Rect replaceResolvedBoundsIfNeeded(@NonNull Rect resolvedBounds) {
119         return hasSizeCompatBounds() ? mSizeCompatBounds : resolvedBounds;
120     }
121 
applyOffsetIfNeeded(@onNull Rect resolvedBounds, @NonNull Configuration resolvedConfig, int offsetX, int offsetY)122     boolean applyOffsetIfNeeded(@NonNull Rect resolvedBounds,
123             @NonNull Configuration resolvedConfig, int offsetX, int offsetY) {
124         if (hasSizeCompatBounds()) {
125             mSizeCompatBounds.offset(offsetX , offsetY);
126             final int dy = mSizeCompatBounds.top - resolvedBounds.top;
127             final int dx = mSizeCompatBounds.left - resolvedBounds.left;
128             AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
129             return true;
130         }
131         return false;
132     }
133 
alignToTopIfNeeded(@onNull Rect parentBounds)134     void alignToTopIfNeeded(@NonNull Rect parentBounds) {
135         if (hasSizeCompatBounds()) {
136             mSizeCompatBounds.top = parentBounds.top;
137         }
138     }
139 
applySizeCompatScaleIfNeeded(@onNull Rect resolvedBounds, @NonNull Configuration resolvedConfig)140     void applySizeCompatScaleIfNeeded(@NonNull Rect resolvedBounds,
141             @NonNull Configuration resolvedConfig) {
142         if (mSizeCompatScale != 1f) {
143             final int screenPosX = resolvedBounds.left;
144             final int screenPosY = resolvedBounds.top;
145             final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
146             final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
147             AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
148         }
149     }
150 
updateSizeCompatScale(@onNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig)151     void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds,
152             @NonNull Configuration newParentConfig) {
153         mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
154                 .findOpaqueNotFinishingActivityBelow()
155                 .map(ar -> Math.min(1.0f, ar.getCompatScale()))
156                 .orElseGet(() -> calculateSizeCompatScale(
157                         resolvedAppBounds, containerAppBounds, newParentConfig));
158     }
159 
clearSizeCompatModeAttributes()160     void clearSizeCompatModeAttributes() {
161         mInSizeCompatModeForBounds = false;
162         final float lastSizeCompatScale = mSizeCompatScale;
163         mSizeCompatScale = 1f;
164         if (mSizeCompatScale != lastSizeCompatScale) {
165             mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
166                     false /* traverseTopToBottom */);
167         }
168         mSizeCompatBounds = null;
169         mAppCompatDisplayInsets = null;
170         mActivityRecord.mAppCompatController.getTransparentPolicy()
171                 .clearInheritedAppCompatDisplayInsets();
172     }
173 
clearOverrideConfiguration()174     private Configuration clearOverrideConfiguration() {
175         // Clear config override in #updateAppCompatDisplayInsets().
176         final int activityType = mActivityRecord.getActivityType();
177         final Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
178         overrideConfig.unset();
179         // Keep the activity type which was set when attaching to a task to prevent leaving it
180         // undefined.
181         overrideConfig.windowConfiguration.setActivityType(activityType);
182         return overrideConfig;
183     }
184 
clearSizeCompatMode()185     void clearSizeCompatMode() {
186         clearSizeCompatModeAttributes();
187         final Configuration overrideConfig = clearOverrideConfiguration();
188         mActivityRecord.onRequestedOverrideConfigurationChanged(overrideConfig);
189     }
190 
clearSizeCompatModeIfNeededOnResolveOverrideConfiguration()191     void clearSizeCompatModeIfNeededOnResolveOverrideConfiguration() {
192         if (mAppCompatDisplayInsets == null || !mActivityRecord.isUniversalResizeable()) {
193             return;
194         }
195         clearSizeCompatModeAttributes();
196         clearOverrideConfiguration();
197     }
198 
dump(@onNull PrintWriter pw, @NonNull String prefix)199     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
200         if (mSizeCompatScale != 1f || hasSizeCompatBounds()) {
201             pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
202                     + mSizeCompatBounds);
203         }
204     }
205 
206     /**
207      * Resolves consistent screen configuration for orientation and rotation changes without
208      * inheriting the parent bounds.
209      */
resolveSizeCompatModeConfiguration(@onNull Configuration newParentConfiguration, @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds)210     void resolveSizeCompatModeConfiguration(@NonNull Configuration newParentConfiguration,
211             @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds) {
212         final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
213         final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
214 
215         // When an activity needs to be letterboxed because of fixed orientation, use fixed
216         // orientation bounds (stored in resolved bounds) instead of parent bounds since the
217         // activity will be displayed within them even if it is in size compat mode. They should be
218         // saved here before resolved bounds are overridden below.
219         final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
220                 .getAspectRatioPolicy();
221         final boolean useResolvedBounds = aspectRatioPolicy.isAspectRatioApplied();
222         final Rect containerBounds = useResolvedBounds
223                 ? new Rect(resolvedBounds)
224                 : newParentConfiguration.windowConfiguration.getBounds();
225         final Rect containerAppBounds = useResolvedBounds
226                 ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
227                 : mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
228 
229         final int requestedOrientation = mActivityRecord.getRequestedConfigurationOrientation();
230         final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
231         final int parentOrientation = mActivityRecord.mResolveConfigHint.mUseOverrideInsetsForConfig
232                 ? mActivityRecord.mResolveConfigHint.mTmpOverrideConfigOrientation
233                 : newParentConfiguration.orientation;
234         final int orientation = orientationRequested
235                 ? requestedOrientation
236                 // We should use the original orientation of the activity when possible to avoid
237                 // forcing the activity in the opposite orientation.
238                 : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
239                         ? appCompatDisplayInsets.mOriginalRequestedOrientation
240                         : parentOrientation;
241         int rotation = newParentConfiguration.windowConfiguration.getRotation();
242         final boolean isFixedToUserRotation = mActivityRecord.mDisplayContent == null
243                 || mActivityRecord.mDisplayContent.getDisplayRotation().isFixedToUserRotation();
244         final boolean tasksAreFloating =
245                 newParentConfiguration.windowConfiguration.tasksAreFloating();
246         // Ignore parent rotation for floating tasks as window rotation is independent of its parent
247         // and thus will remain, and so should be reconfigured, in its original rotation.
248         if (!isFixedToUserRotation && !tasksAreFloating) {
249             // Use parent rotation because the original display can be rotated.
250             resolvedConfig.windowConfiguration.setRotation(rotation);
251         } else {
252             final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
253             if (overrideRotation != ROTATION_UNDEFINED) {
254                 rotation = overrideRotation;
255             }
256         }
257 
258         // Use compat insets to lock width and height. We should not use the parent width and height
259         // because apps in compat mode should have a constant width and height. The compat insets
260         // are locked when the app is first launched and are never changed after that, so we can
261         // rely on them to contain the original and unchanging width and height of the app.
262         final Rect containingAppBounds = new Rect();
263         final Rect containingBounds = tmpBounds;
264         appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
265                 orientation, orientationRequested, isFixedToUserRotation);
266         resolvedBounds.set(containingBounds);
267         // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
268         if (!appCompatDisplayInsets.mIsFloating) {
269             aspectRatioPolicy.applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
270                             containingBounds);
271         }
272 
273         // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
274         // are calculated in compat container space. The actual position on screen will be applied
275         // later, so the calculation is simpler that doesn't need to involve offset from parent.
276         mActivityRecord.mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
277         mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
278         // Use current screen layout as source because the size of app is independent to parent.
279         resolvedConfig.screenLayout = ActivityRecord.computeScreenLayout(
280                 mActivityRecord.getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
281                 resolvedConfig.screenHeightDp);
282 
283         // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
284         // the parent bounds appropriately.
285         if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
286             resolvedConfig.orientation = parentOrientation;
287         }
288 
289         // Below figure is an example that puts an activity which was launched in a larger container
290         // into a smaller container.
291         //   The outermost rectangle is the real display bounds.
292         //   "@" is the container app bounds (parent bounds or fixed orientation bounds)
293         //   "#" is the {@code resolvedBounds} that applies to application.
294         //   "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
295         // ------------------------------
296         // |                            |
297         // |    @@@@*********@@@@###    |
298         // |    @   *       *   @  #    |
299         // |    @   *       *   @  #    |
300         // |    @   *       *   @  #    |
301         // |    @@@@*********@@@@  #    |
302         // ---------#--------------#-----
303         //          #              #
304         //          ################
305         // The application is still layouted in "#" since it was launched, and it will be visually
306         // scaled and positioned to "*".
307 
308         final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
309         // Calculates the scale the size compatibility bounds into the region which is available
310         // to application.
311         final float lastSizeCompatScale = mSizeCompatScale;
312         updateSizeCompatScale(resolvedAppBounds, containerAppBounds, newParentConfiguration);
313 
314         // Container top inset when floating is not included in height of bounds.
315         final int containerTopInset = tasksAreFloating ? 0
316                 : containerAppBounds.top - containerBounds.top;
317         final boolean topNotAligned =
318                 containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
319         if (mSizeCompatScale != 1f || topNotAligned) {
320             if (mSizeCompatBounds == null) {
321                 mSizeCompatBounds = new Rect();
322             }
323             mSizeCompatBounds.set(resolvedAppBounds);
324             mSizeCompatBounds.offsetTo(0, 0);
325             mSizeCompatBounds.scale(mSizeCompatScale);
326             // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
327             mSizeCompatBounds.bottom += containerTopInset;
328         } else {
329             mSizeCompatBounds = null;
330         }
331         if (mSizeCompatScale != lastSizeCompatScale) {
332             mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
333                     false /* traverseTopToBottom */);
334         }
335 
336         // The position will be later adjusted in updateResolvedBoundsPosition.
337         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
338         final boolean fillContainer = resolvedBounds.equals(containingBounds);
339         final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
340         final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
341 
342         if (screenPosX != 0 || screenPosY != 0) {
343             if (hasSizeCompatBounds()) {
344                 mSizeCompatBounds.offset(screenPosX, screenPosY);
345             }
346             // Add the global coordinates and remove the local coordinates.
347             final int dx = screenPosX - resolvedBounds.left;
348             final int dy = screenPosY - resolvedBounds.top;
349             AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
350         }
351 
352         mInSizeCompatModeForBounds = isInSizeCompatModeForBounds(resolvedAppBounds,
353                 containerAppBounds);
354     }
355 
356     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
updateAppCompatDisplayInsets()357     void updateAppCompatDisplayInsets() {
358         if (getAppCompatDisplayInsets() != null
359                 || !mActivityRecord.shouldCreateAppCompatDisplayInsets()) {
360             // The override configuration is set only once in size compatibility mode.
361             return;
362         }
363 
364         Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
365         final Configuration fullConfig = mActivityRecord.getConfiguration();
366 
367         // Ensure the screen related fields are set. It is used to prevent activity relaunch
368         // when moving between displays. For screenWidthDp and screenWidthDp, because they
369         // are relative to bounds and density, they will be calculated in
370         // {@link Task#computeConfigResourceOverrides} and the result will also be
371         // relatively fixed.
372         overrideConfig.colorMode = fullConfig.colorMode;
373         overrideConfig.densityDpi = fullConfig.densityDpi;
374         if (enableSizeCompatModeImprovementsForConnectedDisplays()) {
375             overrideConfig.touchscreen = fullConfig.touchscreen;
376             overrideConfig.navigation = fullConfig.navigation;
377             overrideConfig.keyboard = fullConfig.keyboard;
378             overrideConfig.keyboardHidden = fullConfig.keyboardHidden;
379             overrideConfig.hardKeyboardHidden = fullConfig.hardKeyboardHidden;
380             overrideConfig.navigationHidden = fullConfig.navigationHidden;
381         }
382         // The smallest screen width is the short side of screen bounds. Because the bounds
383         // and density won't be changed, smallestScreenWidthDp is also fixed.
384         overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
385         if (ActivityInfo.isFixedOrientation(mActivityRecord.getOverrideOrientation())) {
386             // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
387             // apply runtime rotation changes.
388             overrideConfig.windowConfiguration.setRotation(
389                     fullConfig.windowConfiguration.getRotation());
390         }
391 
392         final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController
393                 .getAspectRatioPolicy().getLetterboxedContainerBounds();
394 
395         // The role of AppCompatDisplayInsets is like the override bounds.
396         mAppCompatDisplayInsets =
397                 new AppCompatDisplayInsets(mActivityRecord.mDisplayContent, mActivityRecord,
398                         letterboxedContainerBounds, mActivityRecord.mResolveConfigHint
399                             .mUseOverrideInsetsForConfig);
400     }
401 
402     /**
403      * @return {@code true} if this activity is in size compatibility mode that uses the different
404      *         density than its parent or its bounds don't fit in parent naturally.
405      */
inSizeCompatMode()406     boolean inSizeCompatMode() {
407         if (isInSizeCompatModeForBounds()) {
408             return true;
409         }
410         if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
411                 // The orientation is different from parent when transforming.
412                 || mActivityRecord.isFixedRotationTransforming()) {
413             return false;
414         }
415         final Rect appBounds = mActivityRecord.getConfiguration().windowConfiguration
416                 .getAppBounds();
417         if (appBounds == null) {
418             // The app bounds hasn't been computed yet.
419             return false;
420         }
421         final WindowContainer<?> parent = mActivityRecord.getParent();
422         if (parent == null) {
423             // The parent of detached Activity can be null.
424             return false;
425         }
426         final Configuration parentConfig = parent.getConfiguration();
427         // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
428         // fields should be changed with density and bounds, so here only compares the most
429         // significant field.
430         return parentConfig.densityDpi != mActivityRecord.getConfiguration().densityDpi;
431     }
432 
433     /**
434      * Indicates the activity will keep the bounds and screen configuration when it was first
435      * launched, no matter how its parent changes.
436      *
437      * <p>If {@true}, then {@link AppCompatDisplayInsets} will be created in {@link
438      * ActivityRecord#resolveOverrideConfiguration} to "freeze" activity bounds and insets.
439      *
440      * @return {@code true} if this activity is declared as non-resizable and fixed orientation or
441      *         aspect ratio.
442      */
shouldCreateAppCompatDisplayInsets()443     boolean shouldCreateAppCompatDisplayInsets() {
444         if (mActivityRecord.mAppCompatController.getAspectRatioOverrides()
445                 .hasFullscreenOverride()) {
446             // If the user has forced the applications aspect ratio to be fullscreen, don't use size
447             // compatibility mode in any situation. The user has been warned and therefore accepts
448             // the risk of the application misbehaving.
449             return false;
450         }
451         switch (supportsSizeChanges()) {
452             case SIZE_CHANGES_SUPPORTED_METADATA:
453             case SIZE_CHANGES_SUPPORTED_OVERRIDE:
454                 return false;
455             case SIZE_CHANGES_UNSUPPORTED_OVERRIDE:
456                 return true;
457             default:
458                 // Fall through
459         }
460         // Use root activity's info for tasks in multi-window mode, or fullscreen tasks in freeform
461         // task display areas, to ensure visual consistency across activity launches and exits in
462         // the same task.
463         final TaskDisplayArea tda = mActivityRecord.getTaskDisplayArea();
464         if (mActivityRecord.inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) {
465             final Task task = mActivityRecord.getTask();
466             final ActivityRecord root = task != null ? task.getRootActivity() : null;
467             if (root != null && root != mActivityRecord
468                     && !root.shouldCreateAppCompatDisplayInsets()) {
469                 // If the root activity doesn't use size compatibility mode, the activities above
470                 // are forced to be the same for consistent visual appearance.
471                 return false;
472             }
473         }
474         final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
475                 .getAspectRatioPolicy();
476         return !mActivityRecord.isResizeable() && (mActivityRecord.info.isFixedOrientation()
477                 || aspectRatioPolicy.hasFixedAspectRatio())
478                 // The configuration of non-standard type should be enforced by system.
479                 // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is
480                 // added to a task, but this function is called when resolving the launch params, at
481                 // which point, the activity type is still undefined if it will be standard.
482                 // For other non-standard types, the type is set in the constructor, so this should
483                 // not be a problem.
484                 && mActivityRecord.isActivityTypeStandardOrUndefined();
485     }
486 
487     /**
488      * Returns whether the activity supports size changes.
489      */
490     @ActivityInfo.SizeChangesSupportMode
supportsSizeChanges()491     int supportsSizeChanges() {
492         final AppCompatResizeOverrides resizeOverrides = mAppCompatOverrides.getResizeOverrides();
493         if (resizeOverrides.shouldOverrideForceNonResizeApp()) {
494             return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
495         }
496 
497         if (mActivityRecord.info.supportsSizeChanges) {
498             return SIZE_CHANGES_SUPPORTED_METADATA;
499         }
500 
501         if (resizeOverrides.shouldOverrideForceResizeApp()) {
502             return SIZE_CHANGES_SUPPORTED_OVERRIDE;
503         }
504 
505         return SIZE_CHANGES_UNSUPPORTED_METADATA;
506     }
507 
508 
isInSizeCompatModeForBounds(final @NonNull Rect appBounds, final @NonNull Rect containerBounds)509     private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds,
510             final @NonNull Rect containerBounds) {
511         if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()) {
512             // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
513             // is letterboxed.
514             return false;
515         }
516         final int appWidth = appBounds.width();
517         final int appHeight = appBounds.height();
518         final int containerAppWidth = containerBounds.width();
519         final int containerAppHeight = containerBounds.height();
520 
521         if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
522             // Matched the container bounds.
523             return false;
524         }
525         if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
526             // Both sides are smaller than the container.
527             return true;
528         }
529         if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
530             // One side is larger than the container.
531             return true;
532         }
533 
534         // The rest of the condition is that only one side is smaller than the container, but it
535         // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
536         final float maxAspectRatio = mActivityRecord.getMaxAspectRatio();
537         if (maxAspectRatio > 0) {
538             final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
539                     / Math.min(appWidth, appHeight);
540             if (aspectRatio >= maxAspectRatio) {
541                 // The current size has reached the max aspect ratio.
542                 return false;
543             }
544         }
545         final float minAspectRatio = mActivityRecord.getMinAspectRatio();
546         if (minAspectRatio > 0) {
547             // The activity should have at least the min aspect ratio, so this checks if the
548             // container still has available space to provide larger aspect ratio.
549             final float containerAspectRatio =
550                     (0.5f + Math.max(containerAppWidth, containerAppHeight))
551                             / Math.min(containerAppWidth, containerAppHeight);
552             if (containerAspectRatio <= minAspectRatio) {
553                 // The long side has reached the parent.
554                 return false;
555             }
556         }
557         return true;
558     }
559 
calculateSizeCompatScale(@onNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig)560     private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds,
561             @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig) {
562         final int contentW = resolvedAppBounds.width();
563         final int contentH = resolvedAppBounds.height();
564         final int viewportW = containerAppBounds.width();
565         final int viewportH = containerAppBounds.height();
566         // Allow an application to be up-scaled if its window is smaller than its
567         // original container or if it's a freeform window in desktop mode.
568         boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
569                 || isInDesktopMode(mActivityRecord.mAtmService.mContext,
570                 newParentConfig.windowConfiguration.getWindowingMode());
571         return shouldAllowUpscaling ? Math.min(
572                 (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
573     }
574 }
575