• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.appzoomout;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.util.ArrayMap;
22 import android.view.SurfaceControl;
23 import android.window.DisplayAreaAppearedInfo;
24 import android.window.DisplayAreaInfo;
25 import android.window.DisplayAreaOrganizer;
26 import android.window.WindowContainerToken;
27 
28 import com.android.internal.policy.ScreenDecorationsUtils;
29 import com.android.wm.shell.common.DisplayLayout;
30 
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.Executor;
34 
35 /** Display area organizer that manages the app zoom out UI and states. */
36 public class AppZoomOutDisplayAreaOrganizer extends DisplayAreaOrganizer {
37 
38     private static final float PUSHBACK_SCALE_FOR_LAUNCHER = 0.05f;
39     private static final float PUSHBACK_SCALE_FOR_APP = 0.025f;
40     private static final float INVALID_PROGRESS = -1;
41 
42     private final DisplayLayout mDisplayLayout = new DisplayLayout();
43     private final Context mContext;
44     private final float mCornerRadius;
45     private final Map<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap =
46             new ArrayMap<>();
47 
48     private float mProgress = INVALID_PROGRESS;
49     // Denote whether the home task is focused, null when it's not yet initialized.
50     @Nullable private Boolean mIsHomeTaskFocused;
51 
AppZoomOutDisplayAreaOrganizer(Context context, DisplayLayout displayLayout, Executor mainExecutor)52     public AppZoomOutDisplayAreaOrganizer(Context context,
53             DisplayLayout displayLayout, Executor mainExecutor) {
54         super(mainExecutor);
55         mContext = context;
56         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
57         setDisplayLayout(displayLayout);
58     }
59 
60     @Override
onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo, SurfaceControl leash)61     public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo, SurfaceControl leash) {
62         leash.setUnreleasedWarningCallSite(
63                 "AppZoomOutDisplayAreaOrganizer.onDisplayAreaAppeared");
64         mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
65     }
66 
67     @Override
onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo)68     public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
69         final SurfaceControl leash = mDisplayAreaTokenMap.get(displayAreaInfo.token);
70         if (leash != null) {
71             leash.release();
72         }
73         mDisplayAreaTokenMap.remove(displayAreaInfo.token);
74     }
75 
registerOrganizer()76     public void registerOrganizer() {
77         final List<DisplayAreaAppearedInfo> displayAreaInfos = registerOrganizer(
78                 AppZoomOutDisplayAreaOrganizer.FEATURE_APP_ZOOM_OUT);
79         for (int i = 0; i < displayAreaInfos.size(); i++) {
80             final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
81             onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
82         }
83     }
84 
85     @Override
unregisterOrganizer()86     public void unregisterOrganizer() {
87         super.unregisterOrganizer();
88         reset();
89     }
90 
setProgress(float progress)91     void setProgress(float progress) {
92         if (mProgress == progress) {
93             return;
94         }
95 
96         mProgress = progress;
97         apply();
98     }
99 
setIsHomeTaskFocused(boolean isHomeTaskFocused)100     void setIsHomeTaskFocused(boolean isHomeTaskFocused) {
101         if (mIsHomeTaskFocused != null && mIsHomeTaskFocused == isHomeTaskFocused) {
102             return;
103         }
104 
105         mIsHomeTaskFocused = isHomeTaskFocused;
106         apply();
107     }
108 
apply()109     private void apply() {
110         if (mIsHomeTaskFocused == null || mProgress == INVALID_PROGRESS) {
111             return;
112         }
113 
114         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
115         float scale = mProgress * (mIsHomeTaskFocused
116                 ? PUSHBACK_SCALE_FOR_LAUNCHER : PUSHBACK_SCALE_FOR_APP);
117         mDisplayAreaTokenMap.forEach((token, leash) -> updateSurface(tx, leash, scale));
118         tx.apply();
119     }
120 
setDisplayLayout(DisplayLayout displayLayout)121     void setDisplayLayout(DisplayLayout displayLayout) {
122         mDisplayLayout.set(displayLayout);
123     }
124 
reset()125     private void reset() {
126         setProgress(0);
127         mProgress = INVALID_PROGRESS;
128         mIsHomeTaskFocused = null;
129     }
130 
updateSurface(SurfaceControl.Transaction tx, SurfaceControl leash, float scale)131     private void updateSurface(SurfaceControl.Transaction tx, SurfaceControl leash, float scale) {
132         if (scale == 0) {
133             // Reset when scale is set back to 0.
134             tx
135                     .setCrop(leash, null)
136                     .setScale(leash, 1, 1)
137                     .setPosition(leash, 0, 0)
138                     .setCornerRadius(leash, 0);
139             return;
140         }
141 
142         tx
143                 // Rounded corner can only be applied if a crop is set.
144                 .setCrop(leash, 0, 0, mDisplayLayout.width(), mDisplayLayout.height())
145                 .setScale(leash, 1 - scale, 1 - scale)
146                 .setPosition(leash, scale * mDisplayLayout.width() * 0.5f,
147                         scale * mDisplayLayout.height() * 0.5f)
148                 .setCornerRadius(leash, mCornerRadius * (1 - scale));
149     }
150 
onRotateDisplay(Context context, int toRotation)151     void onRotateDisplay(Context context, int toRotation) {
152         if (mDisplayLayout.rotation() == toRotation) {
153             return;
154         }
155         mDisplayLayout.rotateTo(context.getResources(), toRotation);
156     }
157 }
158