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