1 /*
2  * Copyright (C) 2016 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.WINDOWING_MODE_FULLSCREEN;
20 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
21 
22 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
23 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
25 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
26 
27 import android.annotation.Nullable;
28 import android.graphics.Rect;
29 import android.os.Bundle;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.util.SparseArray;
33 
34 import com.android.internal.protolog.common.ProtoLog;
35 import com.android.window.flags.Flags;
36 
37 import java.util.function.Consumer;
38 
39 /**
40  * A token that represents a set of wallpaper windows.
41  */
42 class WallpaperWindowToken extends WindowToken {
43 
44     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
45 
46     private boolean mShowWhenLocked = false;
47     float mWallpaperX = -1;
48     float mWallpaperY = -1;
49     float mWallpaperXStep = -1;
50     float mWallpaperYStep = -1;
51     int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
52     int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
53 
54     /**
55      * Map from {@link android.app.WallpaperManager.ScreenOrientation} to crop rectangles.
56      * Crop rectangles represent the part of the wallpaper displayed for each screen orientation.
57      */
58     private SparseArray<Rect> mCropHints = new SparseArray<>();
59 
WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens)60     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
61             DisplayContent dc, boolean ownerCanManageAppTokens) {
62         this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
63     }
64 
WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens, @Nullable Bundle options)65     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
66             DisplayContent dc, boolean ownerCanManageAppTokens, @Nullable Bundle options) {
67         super(service, token, TYPE_WALLPAPER, explicit, dc, ownerCanManageAppTokens,
68                 false /* roundedCornerOverlay */, false /* fromClientToken */, options);
69         dc.mWallpaperController.addWallpaperToken(this);
70         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
71     }
72 
73     @Override
asWallpaperToken()74     WallpaperWindowToken asWallpaperToken() {
75         return this;
76     }
77 
78     @Override
setExiting(boolean animateExit)79     void setExiting(boolean animateExit) {
80         super.setExiting(animateExit);
81         mDisplayContent.mWallpaperController.removeWallpaperToken(this);
82     }
83 
84     @Override
prepareSurfaces()85     public void prepareSurfaces() {
86         super.prepareSurfaces();
87 
88         if (Flags.ensureWallpaperInTransitions()) {
89             // Similar to Task.prepareSurfaces, outside of transitions we need to apply visibility
90             // changes directly. In transitions the transition player will take care of applying the
91             // visibility change.
92             if (!mTransitionController.inTransition(this)) {
93                 getSyncTransaction().setVisibility(mSurfaceControl, isVisible());
94             }
95         }
96     }
97 
98     /**
99      * Controls whether this wallpaper shows underneath the keyguard or is hidden and only
100      * revealed once keyguard is dismissed.
101      */
setShowWhenLocked(boolean showWhenLocked)102     void setShowWhenLocked(boolean showWhenLocked) {
103         if (showWhenLocked == mShowWhenLocked) {
104             return;
105         }
106         mShowWhenLocked = showWhenLocked;
107         // Move the window token to the front (private) or back (showWhenLocked). This is possible
108         // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER windows.
109         final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
110 
111         // Note: Moving all the way to the front or back breaks ordering based on addition times.
112         // There should never have more than one non-animating token of each type.
113         getParent().positionChildAt(position, this /* child */, false /*includingParents */);
114         mDisplayContent.mWallpaperController.onWallpaperTokenReordered();
115     }
116 
canShowWhenLocked()117     boolean canShowWhenLocked() {
118         return mShowWhenLocked;
119     }
120 
setCropHints(SparseArray<Rect> cropHints)121     void setCropHints(SparseArray<Rect> cropHints) {
122         mCropHints = cropHints.clone();
123     }
124 
getCropHints()125     SparseArray<Rect> getCropHints() {
126         return mCropHints;
127     }
128 
sendWindowWallpaperCommand( String action, int x, int y, int z, Bundle extras, boolean sync)129     void sendWindowWallpaperCommand(
130             String action, int x, int y, int z, Bundle extras, boolean sync) {
131         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
132             final WindowState wallpaper = mChildren.get(wallpaperNdx);
133             try {
134                 wallpaper.mClient.dispatchWallpaperCommand(action, x, y, z, extras, sync);
135                 // We only want to be synchronous with one wallpaper.
136                 sync = false;
137             } catch (RemoteException e) {
138             }
139         }
140     }
141 
updateWallpaperOffset(boolean sync)142     void updateWallpaperOffset(boolean sync) {
143         final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
144         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
145             final WindowState wallpaper = mChildren.get(wallpaperNdx);
146             if (wallpaperController.updateWallpaperOffset(wallpaper,
147                     sync && !mWmService.mFlags.mWallpaperOffsetAsync)) {
148                 // We only want to be synchronous with one wallpaper.
149                 sync = false;
150             }
151         }
152     }
153 
updateWallpaperWindows(boolean visible)154     void updateWallpaperWindows(boolean visible) {
155         if (mVisibleRequested != visible) {
156             ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
157                     token, visible);
158             setVisibility(visible);
159         }
160 
161         final WindowState wallpaperTarget =
162                 mDisplayContent.mWallpaperController.getWallpaperTarget();
163 
164         if (visible && wallpaperTarget != null) {
165             final RecentsAnimationController recentsAnimationController =
166                     mWmService.getRecentsAnimationController();
167             if (recentsAnimationController != null
168                     && recentsAnimationController.isAnimatingTask(wallpaperTarget.getTask())) {
169                 // If the Recents animation is running, and the wallpaper target is the animating
170                 // task we want the wallpaper to be rotated in the same orientation as the
171                 // RecentsAnimation's target (e.g the launcher)
172                 recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
173             } else if ((wallpaperTarget.mActivityRecord == null
174                     // Ignore invisible activity because it may be moving to background.
175                     || wallpaperTarget.mActivityRecord.isVisibleRequested())
176                     && wallpaperTarget.mToken.hasFixedRotationTransform()) {
177                 // If the wallpaper target has a fixed rotation, we want the wallpaper to follow its
178                 // rotation
179                 linkFixedRotationTransform(wallpaperTarget.mToken);
180             }
181         }
182         if (mTransitionController.inTransition(this)) {
183             // If wallpaper is in transition, setVisible() will be called from commitVisibility()
184             // when finishing transition. Otherwise commitVisibility() is already called from above
185             // setVisibility().
186             return;
187         }
188 
189         setVisible(visible);
190     }
191 
setVisible(boolean visible)192     private void setVisible(boolean visible) {
193         final boolean wasClientVisible = isClientVisible();
194         setClientVisible(visible);
195         if (visible && !wasClientVisible) {
196             for (int i = mChildren.size() - 1; i >= 0; i--) {
197                 final WindowState wallpaper = mChildren.get(i);
198                 wallpaper.requestUpdateWallpaperIfNeeded();
199             }
200         }
201     }
202 
203     /**
204      * Sets the requested visibility of this token. The visibility may not be if this is part of a
205      * transition. In that situation, make sure to call {@link #commitVisibility} when done.
206      */
setVisibility(boolean visible)207     void setVisibility(boolean visible) {
208         if (mVisibleRequested != visible) {
209             // Before setting mVisibleRequested so we can track changes.
210             final WindowState wpTarget = mDisplayContent.mWallpaperController.getWallpaperTarget();
211             final boolean isTargetNotCollectedActivity = wpTarget == null
212                     || (wpTarget.mActivityRecord != null
213                             && !mTransitionController.isCollecting(wpTarget.mActivityRecord));
214             // Skip collecting requesting-invisible wallpaper if the wallpaper target is empty or
215             // a non-collected activity. Because the visibility change may be called after the
216             // transition of activity is finished, e.g. WallpaperController#hideWallpapers from
217             // hiding surface of the target. Then if there is a next transition, the wallpaper
218             // change may be collected into the unrelated transition and cause a weird animation.
219             if (!isTargetNotCollectedActivity || visible) {
220                 mTransitionController.collect(this);
221             }
222 
223             setVisibleRequested(visible);
224         }
225 
226         // If in a transition, defer commits for activities that are going invisible
227         if (!visible && (mTransitionController.inTransition()
228                 || getDisplayContent().mAppTransition.isRunning())) {
229             return;
230         }
231 
232         commitVisibility(visible);
233     }
234 
235     /** Commits the visibility of this token. This will directly update the visibility. */
commitVisibility(boolean visible)236     void commitVisibility(boolean visible) {
237         if (visible == isVisible()) return;
238 
239         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
240                 "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
241                 isVisible(), mVisibleRequested);
242 
243         setVisibleRequested(visible);
244         setVisible(visible);
245     }
246 
hasVisibleNotDrawnWallpaper()247     boolean hasVisibleNotDrawnWallpaper() {
248         if (!isVisible()) return false;
249         for (int j = mChildren.size() - 1; j >= 0; --j) {
250             final WindowState wallpaper = mChildren.get(j);
251             if (!wallpaper.isDrawn() && wallpaper.isVisible()) {
252                 return true;
253             }
254         }
255         return false;
256     }
257 
258     @Override
forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback)259     void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
260         callback.accept(this);
261     }
262 
263     @Override
fillsParent()264     boolean fillsParent() {
265         return true;
266     }
267 
268     @Override
showWallpaper()269     boolean showWallpaper() {
270         return false;
271     }
272 
273     @Override
setVisibleRequested(boolean visible)274     protected boolean setVisibleRequested(boolean visible) {
275         if (!super.setVisibleRequested(visible)) return false;
276         setInsetsFrozen(!visible);
277         return true;
278     }
279 
280     @Override
onChildVisibleRequestedChanged(@ullable WindowContainer child)281     protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
282         // Wallpaper manages visibleRequested directly (it's not determined by children)
283         return false;
284     }
285 
286     @Override
isVisible()287     boolean isVisible() {
288         return isClientVisible();
289     }
290 
291     @Override
isSyncFinished(BLASTSyncEngine.SyncGroup group)292     boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
293         // TODO(b/233286785): Support sync state for wallpaper. See WindowState#prepareSync.
294         return !mVisibleRequested || !hasVisibleNotDrawnWallpaper();
295     }
296 
297     @Override
toString()298     public String toString() {
299         if (stringName == null) {
300             StringBuilder sb = new StringBuilder();
301             sb.append("WallpaperWindowToken{");
302             sb.append(Integer.toHexString(System.identityHashCode(this)));
303             sb.append(" token="); sb.append(token); sb.append('}');
304             stringName = sb.toString();
305         }
306         return stringName;
307     }
308 }
309