1 /*
2  * Copyright (C) 2022 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.view.WindowManager.TRANSIT_CHANGE;
20 
21 import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
22 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
23 import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
24 import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
25 import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
26 
27 import android.animation.ValueAnimator;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.content.Context;
31 import android.graphics.Rect;
32 import android.window.DisplayAreaInfo;
33 import android.window.TransitionRequestInfo;
34 import android.window.WindowContainerTransaction;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.server.wm.DeviceStateController.DeviceState;
38 
39 public class PhysicalDisplaySwitchTransitionLauncher {
40 
41     private final DisplayContent mDisplayContent;
42     private final ActivityTaskManagerService mAtmService;
43     private final Context mContext;
44     private final TransitionController mTransitionController;
45 
46     /**
47      * If on a foldable device represents whether we need to show unfold animation when receiving
48      * a physical display switch event
49      */
50     private boolean mShouldRequestTransitionOnDisplaySwitch = false;
51     /**
52      * Current device state from {@link android.hardware.devicestate.DeviceStateManager}
53      */
54     private DeviceState mDeviceState = DeviceState.UNKNOWN;
55     private Transition mTransition;
56 
PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent, TransitionController transitionController)57     public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
58             TransitionController transitionController) {
59         this(displayContent, displayContent.mWmService.mAtmService,
60                 displayContent.mWmService.mContext, transitionController);
61     }
62 
63     @VisibleForTesting
PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent, ActivityTaskManagerService service, Context context, TransitionController transitionController)64     public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
65             ActivityTaskManagerService service, Context context,
66             TransitionController transitionController) {
67         mDisplayContent = displayContent;
68         mAtmService = service;
69         mContext = context;
70         mTransitionController = transitionController;
71     }
72 
73     /**
74      * Called by the display manager just before it applied the device state, it is guaranteed
75      * that in case of physical display change the
76      * {@link PhysicalDisplaySwitchTransitionLauncher#requestDisplaySwitchTransitionIfNeeded}
77      * method will be invoked *after* this one.
78      */
foldStateChanged(DeviceState newDeviceState)79     void foldStateChanged(DeviceState newDeviceState) {
80         boolean isUnfolding = mDeviceState == FOLDED
81                 && (newDeviceState == HALF_FOLDED || newDeviceState == OPEN);
82 
83         if (isUnfolding) {
84             // Request transition only if we are unfolding the device
85             mShouldRequestTransitionOnDisplaySwitch = true;
86         } else if (newDeviceState != HALF_FOLDED && newDeviceState != OPEN) {
87             // Cancel the transition request in case if we are folding or switching to back
88             // to the rear display before the displays got switched
89             mShouldRequestTransitionOnDisplaySwitch = false;
90         }
91 
92         mDeviceState = newDeviceState;
93     }
94 
95     /**
96      * Requests to start a transition for the physical display switch
97      */
requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth, int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight)98     public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth,
99             int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) {
100         if (!mShouldRequestTransitionOnDisplaySwitch) return;
101         if (!mTransitionController.isShellTransitionsEnabled()) return;
102         if (!mDisplayContent.getLastHasContent()) return;
103 
104         boolean shouldRequestUnfoldTransition = mContext.getResources()
105                 .getBoolean(config_unfoldTransitionEnabled) && ValueAnimator.areAnimatorsEnabled();
106 
107         if (!shouldRequestUnfoldTransition) {
108             return;
109         }
110 
111         final TransitionRequestInfo.DisplayChange displayChange =
112                 new TransitionRequestInfo.DisplayChange(displayId);
113 
114         final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight);
115         displayChange.setStartAbsBounds(startAbsBounds);
116         final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight);
117         displayChange.setEndAbsBounds(endAbsBounds);
118         displayChange.setPhysicalDisplayChanged(true);
119 
120         final Transition t = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE,
121                 0 /* flags */,
122                 mDisplayContent, mDisplayContent, null /* remoteTransition */,
123                 displayChange);
124 
125         if (t != null) {
126             mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
127             mTransition = t;
128         }
129 
130         mShouldRequestTransitionOnDisplaySwitch = false;
131     }
132 
133     /**
134      * Called when physical display is getting updated, this could happen e.g. on foldable
135      * devices when the physical underlying display is replaced.
136      *
137      * @param fromRotation rotation before the display change
138      * @param toRotation rotation after the display change
139      * @param newDisplayAreaInfo display area info after the display change
140      */
onDisplayUpdated(int fromRotation, int toRotation, @NonNull DisplayAreaInfo newDisplayAreaInfo)141     public void onDisplayUpdated(int fromRotation, int toRotation,
142             @NonNull DisplayAreaInfo newDisplayAreaInfo) {
143         if (mTransition == null) return;
144 
145         final boolean started = mDisplayContent.mRemoteDisplayChangeController
146                 .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
147                         this::continueDisplayUpdate);
148 
149         if (!started) {
150             markTransitionAsReady();
151         }
152     }
153 
continueDisplayUpdate(@ullable WindowContainerTransaction transaction)154     private void continueDisplayUpdate(@Nullable WindowContainerTransaction transaction) {
155         if (mTransition == null) return;
156 
157         if (transaction != null) {
158             mAtmService.mWindowOrganizerController.applyTransaction(transaction);
159         }
160 
161         markTransitionAsReady();
162     }
163 
markTransitionAsReady()164     private void markTransitionAsReady() {
165         if (mTransition == null) return;
166 
167         mTransition.setAllReady();
168         mTransition = null;
169     }
170 
171 }
172