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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.RemoteException;
24 import android.os.Trace;
25 import android.util.Slog;
26 import android.view.IDisplayChangeWindowCallback;
27 import android.window.DisplayAreaInfo;
28 import android.window.WindowContainerTransaction;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.protolog.common.ProtoLog;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * A helper class, a wrapper around {@link android.view.IDisplayChangeWindowController} to perform
38  * a synchronous display change in other parts (e.g. in the Shell) and continue the process
39  * in the system server. It handles timeouts and multiple requests.
40  * We have an instance of this controller for each display.
41  */
42 public class RemoteDisplayChangeController {
43 
44     private static final String TAG = "RemoteDisplayChangeController";
45     private static final String REMOTE_DISPLAY_CHANGE_TRACE_TAG = "RemoteDisplayChange";
46 
47     private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
48 
49     private final WindowManagerService mService;
50     private final DisplayContent mDisplayContent;
51 
52     private final Runnable mTimeoutRunnable = this::onContinueTimedOut;
53 
54     // all remote changes that haven't finished yet.
55     private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>();
56 
RemoteDisplayChangeController(@onNull DisplayContent displayContent)57     RemoteDisplayChangeController(@NonNull DisplayContent displayContent) {
58         mService = displayContent.mWmService;
59         mDisplayContent = displayContent;
60     }
61 
62     /**
63      * A Remote change is when we are waiting for some registered (remote)
64      * {@link IDisplayChangeWindowController} to calculate and return some hierarchy operations
65      *  to perform in sync with the display change.
66      */
isWaitingForRemoteDisplayChange()67     public boolean isWaitingForRemoteDisplayChange() {
68         return !mCallbacks.isEmpty();
69     }
70 
71     /**
72      * Starts remote display change
73      * @param fromRotation rotation before the change
74      * @param toRotation rotation after the change
75      * @param newDisplayAreaInfo display area info after change
76      * @param callback that will be called after completing remote display change
77      * @return true if the change successfully started, false otherwise
78      */
performRemoteDisplayChange( int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, ContinueRemoteDisplayChangeCallback callback)79     public boolean performRemoteDisplayChange(
80             int fromRotation, int toRotation,
81             @Nullable DisplayAreaInfo newDisplayAreaInfo,
82             ContinueRemoteDisplayChangeCallback callback) {
83         if (mService.mDisplayChangeController == null) {
84             return false;
85         }
86         mCallbacks.add(callback);
87 
88         if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
89             Trace.beginAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
90         }
91 
92         if (newDisplayAreaInfo != null) {
93             ProtoLog.v(WM_DEBUG_CONFIGURATION,
94                     "Starting remote display change: "
95                             + "from [rot = %d], "
96                             + "to [%dx%d, rot = %d]",
97                     fromRotation,
98                     newDisplayAreaInfo.configuration.windowConfiguration
99                             .getMaxBounds().width(),
100                     newDisplayAreaInfo.configuration.windowConfiguration
101                             .getMaxBounds().height(),
102                     toRotation);
103         }
104 
105         final IDisplayChangeWindowCallback remoteCallback = createCallback(callback);
106         try {
107             mService.mH.removeCallbacks(mTimeoutRunnable);
108             mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS);
109             mService.mDisplayChangeController.onDisplayChange(mDisplayContent.mDisplayId,
110                     fromRotation, toRotation, newDisplayAreaInfo, remoteCallback);
111             return true;
112         } catch (RemoteException e) {
113             Slog.e(TAG, "Exception while dispatching remote display-change", e);
114             mCallbacks.remove(callback);
115             return false;
116         }
117     }
118 
onContinueTimedOut()119     private void onContinueTimedOut() {
120         Slog.e(TAG, "RemoteDisplayChange timed-out, UI might get messed-up after this.");
121         // timed-out, so run all continue callbacks and clear the list
122         synchronized (mService.mGlobalLock) {
123             for (int i = 0; i < mCallbacks.size(); ++i) {
124                 final ContinueRemoteDisplayChangeCallback callback = mCallbacks.get(i);
125                 if (i == mCallbacks.size() - 1) {
126                     // Clear all callbacks before calling the last one, so that if the callback
127                     // itself calls {@link #isWaitingForRemoteDisplayChange()}, it will get
128                     // {@code false}. After all, there is nothing pending after this one.
129                     mCallbacks.clear();
130                 }
131                 callback.onContinueRemoteDisplayChange(null /* transaction */);
132 
133                 if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
134                     Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
135                 }
136             }
137             onCompleted();
138         }
139     }
140 
141     /** Called when all remote callbacks are done. */
onCompleted()142     private void onCompleted() {
143         // Because DisplayContent#sendNewConfiguration() will be skipped if there are pending remote
144         // changes, check again when all remote callbacks are done. E.g. callback X is done but
145         // there is a pending callback Y so its invocation is skipped, and when the callback Y is
146         // done, it doesn't call sendNewConfiguration().
147         if (mDisplayContent.mWaitingForConfig) {
148             mDisplayContent.sendNewConfiguration();
149         }
150     }
151 
152     @VisibleForTesting
continueDisplayChange(@onNull ContinueRemoteDisplayChangeCallback callback, @Nullable WindowContainerTransaction transaction)153     void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback,
154             @Nullable WindowContainerTransaction transaction) {
155         synchronized (mService.mGlobalLock) {
156             int idx = mCallbacks.indexOf(callback);
157             if (idx < 0) {
158                 // already called this callback or a more-recent one (eg. via timeout)
159                 return;
160             }
161             for (int i = 0; i < idx; ++i) {
162                 // Expect remote callbacks in order. If they don't come in order, then force
163                 // ordering by continuing everything up until this one with empty transactions.
164                 ContinueRemoteDisplayChangeCallback currentCallback = mCallbacks.get(i);
165                 currentCallback.onContinueRemoteDisplayChange(null /* transaction */);
166 
167                 if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
168                     Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG,
169                             currentCallback.hashCode());
170                 }
171             }
172             // The "toIndex" is exclusive, so it needs +1 to clear the current calling callback.
173             mCallbacks.subList(0, idx + 1).clear();
174             final boolean completed = mCallbacks.isEmpty();
175             if (completed) {
176                 mService.mH.removeCallbacks(mTimeoutRunnable);
177             }
178             callback.onContinueRemoteDisplayChange(transaction);
179             if (completed) {
180                 onCompleted();
181             }
182 
183             if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
184                 Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
185             }
186         }
187     }
188 
createCallback( @onNull ContinueRemoteDisplayChangeCallback callback)189     private IDisplayChangeWindowCallback createCallback(
190             @NonNull ContinueRemoteDisplayChangeCallback callback) {
191         return new IDisplayChangeWindowCallback.Stub() {
192                     @Override
193                     public void continueDisplayChange(WindowContainerTransaction t) {
194                         synchronized (mService.mGlobalLock) {
195                             if (!mCallbacks.contains(callback)) {
196                                 // already ran this callback or a more-recent one.
197                                 return;
198                             }
199                             mService.mH.post(() -> RemoteDisplayChangeController.this
200                                     .continueDisplayChange(callback, t));
201                         }
202                     }
203                 };
204     }
205 
206     /**
207      * Callback interface to handle continuation of the remote display change
208      */
209     public interface ContinueRemoteDisplayChangeCallback {
210         /**
211          * This method is called when the remote display change has been applied
212          * @param transaction window changes collected by the remote display change
213          */
214         void onContinueRemoteDisplayChange(@Nullable WindowContainerTransaction transaction);
215     }
216 }
217