• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Slog;
25 import android.view.IDisplayChangeWindowCallback;
26 import android.window.DisplayAreaInfo;
27 import android.window.WindowContainerTransaction;
28 
29 import com.android.internal.protolog.common.ProtoLog;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * A helper class, a wrapper around {@link android.view.IDisplayChangeWindowController} to perform
36  * a synchronous display change in other parts (e.g. in the Shell) and continue the process
37  * in the system server. It handles timeouts and multiple requests.
38  * We have an instance of this controller for each display.
39  */
40 public class RemoteDisplayChangeController {
41 
42     private static final String TAG = "RemoteDisplayChangeController";
43 
44     private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
45 
46     private final WindowManagerService mService;
47     private final int mDisplayId;
48 
49     private final Runnable mTimeoutRunnable = this::onContinueTimedOut;
50 
51     // all remote changes that haven't finished yet.
52     private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>();
53 
RemoteDisplayChangeController(WindowManagerService service, int displayId)54     public RemoteDisplayChangeController(WindowManagerService service, int displayId) {
55         mService = service;
56         mDisplayId = displayId;
57     }
58 
59     /**
60      * A Remote change is when we are waiting for some registered (remote)
61      * {@link IDisplayChangeWindowController} to calculate and return some hierarchy operations
62      *  to perform in sync with the display change.
63      */
isWaitingForRemoteDisplayChange()64     public boolean isWaitingForRemoteDisplayChange() {
65         return !mCallbacks.isEmpty();
66     }
67 
68     /**
69      * Starts remote display change
70      * @param fromRotation rotation before the change
71      * @param toRotation rotation after the change
72      * @param newDisplayAreaInfo display area info after change
73      * @param callback that will be called after completing remote display change
74      * @return true if the change successfully started, false otherwise
75      */
performRemoteDisplayChange( int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, ContinueRemoteDisplayChangeCallback callback)76     public boolean performRemoteDisplayChange(
77             int fromRotation, int toRotation,
78             @Nullable DisplayAreaInfo newDisplayAreaInfo,
79             ContinueRemoteDisplayChangeCallback callback) {
80         if (mService.mDisplayChangeController == null) {
81             return false;
82         }
83         mCallbacks.add(callback);
84 
85         if (newDisplayAreaInfo != null) {
86             ProtoLog.v(WM_DEBUG_CONFIGURATION,
87                     "Starting remote display change: "
88                             + "from [rot = %d], "
89                             + "to [%dx%d, rot = %d]",
90                     fromRotation,
91                     newDisplayAreaInfo.configuration.windowConfiguration
92                             .getMaxBounds().width(),
93                     newDisplayAreaInfo.configuration.windowConfiguration
94                             .getMaxBounds().height(),
95                     toRotation);
96         }
97 
98         final IDisplayChangeWindowCallback remoteCallback = createCallback(callback);
99         try {
100             mService.mH.removeCallbacks(mTimeoutRunnable);
101             mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS);
102             mService.mDisplayChangeController.onDisplayChange(mDisplayId, fromRotation, toRotation,
103                     newDisplayAreaInfo, remoteCallback);
104             return true;
105         } catch (RemoteException e) {
106             Slog.e(TAG, "Exception while dispatching remote display-change", e);
107             mCallbacks.remove(callback);
108             return false;
109         }
110     }
111 
onContinueTimedOut()112     private void onContinueTimedOut() {
113         Slog.e(TAG, "RemoteDisplayChange timed-out, UI might get messed-up after this.");
114         // timed-out, so run all continue callbacks and clear the list
115         synchronized (mService.mGlobalLock) {
116             for (int i = 0; i < mCallbacks.size(); ++i) {
117                 mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
118             }
119             mCallbacks.clear();
120         }
121     }
122 
continueDisplayChange(@onNull ContinueRemoteDisplayChangeCallback callback, @Nullable WindowContainerTransaction transaction)123     private void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback,
124             @Nullable WindowContainerTransaction transaction) {
125         synchronized (mService.mGlobalLock) {
126             int idx = mCallbacks.indexOf(callback);
127             if (idx < 0) {
128                 // already called this callback or a more-recent one (eg. via timeout)
129                 return;
130             }
131             for (int i = 0; i < idx; ++i) {
132                 // Expect remote callbacks in order. If they don't come in order, then force
133                 // ordering by continuing everything up until this one with empty transactions.
134                 mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
135             }
136             mCallbacks.subList(0, idx + 1).clear();
137             if (mCallbacks.isEmpty()) {
138                 mService.mH.removeCallbacks(mTimeoutRunnable);
139             }
140             callback.onContinueRemoteDisplayChange(transaction);
141         }
142     }
143 
createCallback( @onNull ContinueRemoteDisplayChangeCallback callback)144     private IDisplayChangeWindowCallback createCallback(
145             @NonNull ContinueRemoteDisplayChangeCallback callback) {
146         return new IDisplayChangeWindowCallback.Stub() {
147                     @Override
148                     public void continueDisplayChange(WindowContainerTransaction t) {
149                         synchronized (mService.mGlobalLock) {
150                             if (!mCallbacks.contains(callback)) {
151                                 // already ran this callback or a more-recent one.
152                                 return;
153                             }
154                             mService.mH.post(() -> RemoteDisplayChangeController.this
155                                     .continueDisplayChange(callback, t));
156                         }
157                     }
158                 };
159     }
160 
161     /**
162      * Callback interface to handle continuation of the remote display change
163      */
164     public interface ContinueRemoteDisplayChangeCallback {
165         /**
166          * This method is called when the remote display change has been applied
167          * @param transaction window changes collected by the remote display change
168          */
169         void onContinueRemoteDisplayChange(@Nullable WindowContainerTransaction transaction);
170     }
171 }
172