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