1 /* 2 * Copyright (C) 2020 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.os.Trace.TRACE_TAG_WINDOW_MANAGER; 20 21 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; 22 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.os.Trace; 27 import android.util.ArraySet; 28 import android.util.Slog; 29 import android.util.SparseArray; 30 import android.view.SurfaceControl; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.protolog.common.ProtoLog; 34 35 import java.util.ArrayList; 36 37 /** 38 * Utility class for collecting WindowContainers that will merge transactions. 39 * For example to use to synchronously resize all the children of a window container 40 * 1. Open a new sync set, and pass the listener that will be invoked 41 * int id startSyncSet(TransactionReadyListener) 42 * the returned ID will be eventually passed to the TransactionReadyListener in combination 43 * with a set of WindowContainers that are ready, meaning onTransactionReady was called for 44 * those WindowContainers. You also use it to refer to the operation in future steps. 45 * 2. Ask each child to participate: 46 * addToSyncSet(int id, WindowContainer wc) 47 * if the child thinks it will be affected by a configuration change (a.k.a. has a visible 48 * window in its sub hierarchy, then we will increment a counter of expected callbacks 49 * At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy 50 * updates in to the sync engine. 51 * 3. Apply your configuration changes to the window containers. 52 * 4. Tell the engine that the sync set is ready 53 * setReady(int id) 54 * 5. If there were no sub windows anywhere in the hierarchy to wait on, then 55 * transactionReady is immediately invoked, otherwise all the windows are poked 56 * to redraw and to deliver a buffer to {@link WindowState#finishDrawing}. 57 * Once all this drawing is complete, all the transactions will be merged and delivered 58 * to TransactionReadyListener. 59 * 60 * This works primarily by setting-up state and then watching/waiting for the registered subtrees 61 * to enter into a "finished" state (either by receiving drawn content or by disappearing). This 62 * checks the subtrees during surface-placement. 63 */ 64 class BLASTSyncEngine { 65 private static final String TAG = "BLASTSyncEngine"; 66 67 /** No specific method. Used by override specifiers. */ 68 public static final int METHOD_UNDEFINED = -1; 69 70 /** No sync method. Apps will draw/present internally and just report. */ 71 public static final int METHOD_NONE = 0; 72 73 /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */ 74 public static final int METHOD_BLAST = 1; 75 76 interface TransactionReadyListener { onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction)77 void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); 78 } 79 80 /** 81 * Represents the desire to make a {@link BLASTSyncEngine.SyncGroup} while another is active. 82 * 83 * @see #queueSyncSet 84 */ 85 private static class PendingSyncSet { 86 /** Called immediately when the {@link BLASTSyncEngine} is free. */ 87 private Runnable mStartSync; 88 89 /** Posted to the main handler after {@link #mStartSync} is called. */ 90 private Runnable mApplySync; 91 } 92 93 /** 94 * Holds state associated with a single synchronous set of operations. 95 */ 96 class SyncGroup { 97 final int mSyncId; 98 final int mSyncMethod; 99 final TransactionReadyListener mListener; 100 final Runnable mOnTimeout; 101 boolean mReady = false; 102 final ArraySet<WindowContainer> mRootMembers = new ArraySet<>(); 103 private SurfaceControl.Transaction mOrphanTransaction = null; 104 private String mTraceName; 105 SyncGroup(TransactionReadyListener listener, int id, String name, int method)106 private SyncGroup(TransactionReadyListener listener, int id, String name, int method) { 107 mSyncId = id; 108 mSyncMethod = method; 109 mListener = listener; 110 mOnTimeout = () -> { 111 Slog.w(TAG, "Sync group " + mSyncId + " timeout"); 112 synchronized (mWm.mGlobalLock) { 113 onTimeout(); 114 } 115 }; 116 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 117 mTraceName = name + "SyncGroupReady"; 118 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, mTraceName, id); 119 } 120 } 121 122 /** 123 * Gets a transaction to dump orphaned operations into. Orphaned operations are operations 124 * that were on the mSyncTransactions of "root" subtrees which have been removed during the 125 * sync period. 126 */ 127 @NonNull getOrphanTransaction()128 SurfaceControl.Transaction getOrphanTransaction() { 129 if (mOrphanTransaction == null) { 130 // Lazy since this isn't common 131 mOrphanTransaction = mWm.mTransactionFactory.get(); 132 } 133 return mOrphanTransaction; 134 } 135 onSurfacePlacement()136 private void onSurfacePlacement() { 137 if (!mReady) return; 138 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s", 139 mSyncId, mRootMembers); 140 for (int i = mRootMembers.size() - 1; i >= 0; --i) { 141 final WindowContainer wc = mRootMembers.valueAt(i); 142 if (!wc.isSyncFinished()) { 143 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Unfinished container: %s", 144 mSyncId, wc); 145 return; 146 } 147 } 148 finishNow(); 149 } 150 finishNow()151 private void finishNow() { 152 if (mTraceName != null) { 153 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, mTraceName, mSyncId); 154 } 155 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId); 156 SurfaceControl.Transaction merged = mWm.mTransactionFactory.get(); 157 if (mOrphanTransaction != null) { 158 merged.merge(mOrphanTransaction); 159 } 160 for (WindowContainer wc : mRootMembers) { 161 wc.finishSync(merged, false /* cancel */); 162 } 163 164 final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>(); 165 for (WindowContainer wc : mRootMembers) { 166 wc.waitForSyncTransactionCommit(wcAwaitingCommit); 167 } 168 class CommitCallback implements Runnable { 169 // Can run a second time if the action completes after the timeout. 170 boolean ran = false; 171 public void onCommitted() { 172 synchronized (mWm.mGlobalLock) { 173 if (ran) { 174 return; 175 } 176 mWm.mH.removeCallbacks(this); 177 ran = true; 178 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 179 for (WindowContainer wc : wcAwaitingCommit) { 180 wc.onSyncTransactionCommitted(t); 181 } 182 t.apply(); 183 wcAwaitingCommit.clear(); 184 } 185 } 186 187 // Called in timeout 188 @Override 189 public void run() { 190 // Sometimes we get a trace, sometimes we get a bugreport without 191 // a trace. Since these kind of ANRs can trigger such an issue, 192 // try and ensure we will have some visibility in both cases. 193 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout"); 194 Slog.e(TAG, "WM sent Transaction to organized, but never received" + 195 " commit callback. Application ANR likely to follow."); 196 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 197 onCommitted(); 198 199 } 200 }; 201 CommitCallback callback = new CommitCallback(); 202 merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted); 203 mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION); 204 205 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady"); 206 mListener.onTransactionReady(mSyncId, merged); 207 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 208 mActiveSyncs.remove(mSyncId); 209 mWm.mH.removeCallbacks(mOnTimeout); 210 211 // Immediately start the next pending sync-transaction if there is one. 212 if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) { 213 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "PendingStartTransaction found"); 214 final PendingSyncSet pt = mPendingSyncSets.remove(0); 215 pt.mStartSync.run(); 216 if (mActiveSyncs.size() == 0) { 217 throw new IllegalStateException("Pending Sync Set didn't start a sync."); 218 } 219 // Post this so that the now-playing transition setup isn't interrupted. 220 mWm.mH.post(() -> { 221 synchronized (mWm.mGlobalLock) { 222 pt.mApplySync.run(); 223 } 224 }); 225 } 226 } 227 setReady(boolean ready)228 private void setReady(boolean ready) { 229 if (mReady == ready) { 230 return; 231 } 232 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId); 233 mReady = ready; 234 if (!ready) return; 235 mWm.mWindowPlacerLocked.requestTraversal(); 236 } 237 addToSync(WindowContainer wc)238 private void addToSync(WindowContainer wc) { 239 if (!mRootMembers.add(wc)) { 240 return; 241 } 242 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc); 243 wc.setSyncGroup(this); 244 wc.prepareSync(); 245 if (mReady) { 246 mWm.mWindowPlacerLocked.requestTraversal(); 247 } 248 } 249 onCancelSync(WindowContainer wc)250 void onCancelSync(WindowContainer wc) { 251 mRootMembers.remove(wc); 252 } 253 onTimeout()254 private void onTimeout() { 255 if (!mActiveSyncs.contains(mSyncId)) return; 256 boolean allFinished = true; 257 for (int i = mRootMembers.size() - 1; i >= 0; --i) { 258 final WindowContainer<?> wc = mRootMembers.valueAt(i); 259 if (!wc.isSyncFinished()) { 260 allFinished = false; 261 Slog.i(TAG, "Unfinished container: " + wc); 262 } 263 } 264 if (allFinished && !mReady) { 265 Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see " 266 + "this, please file a bug."); 267 } 268 finishNow(); 269 } 270 } 271 272 private final WindowManagerService mWm; 273 private int mNextSyncId = 0; 274 private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>(); 275 276 /** 277 * A queue of pending sync-sets waiting for their turn to run. 278 * 279 * @see #queueSyncSet 280 */ 281 private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>(); 282 BLASTSyncEngine(WindowManagerService wms)283 BLASTSyncEngine(WindowManagerService wms) { 284 mWm = wms; 285 } 286 287 /** 288 * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet} 289 * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}. 290 */ prepareSyncSet(TransactionReadyListener listener, String name, int method)291 SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) { 292 return new SyncGroup(listener, mNextSyncId++, name, method); 293 } 294 startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, int method)295 int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, 296 int method) { 297 final SyncGroup s = prepareSyncSet(listener, name, method); 298 startSyncSet(s, timeoutMs); 299 return s.mSyncId; 300 } 301 startSyncSet(SyncGroup s)302 void startSyncSet(SyncGroup s) { 303 startSyncSet(s, BLAST_TIMEOUT_DURATION); 304 } 305 startSyncSet(SyncGroup s, long timeoutMs)306 void startSyncSet(SyncGroup s, long timeoutMs) { 307 if (mActiveSyncs.size() != 0) { 308 // We currently only support one sync at a time, so start a new SyncGroup when there is 309 // another may cause issue. 310 ProtoLog.w(WM_DEBUG_SYNC_ENGINE, 311 "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId); 312 } 313 mActiveSyncs.put(s.mSyncId, s); 314 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", 315 s.mSyncId, s.mListener); 316 scheduleTimeout(s, timeoutMs); 317 } 318 319 @Nullable getSyncSet(int id)320 SyncGroup getSyncSet(int id) { 321 return mActiveSyncs.get(id); 322 } 323 hasActiveSync()324 boolean hasActiveSync() { 325 return mActiveSyncs.size() != 0; 326 } 327 328 @VisibleForTesting scheduleTimeout(SyncGroup s, long timeoutMs)329 void scheduleTimeout(SyncGroup s, long timeoutMs) { 330 mWm.mH.postDelayed(s.mOnTimeout, timeoutMs); 331 } 332 addToSyncSet(int id, WindowContainer wc)333 void addToSyncSet(int id, WindowContainer wc) { 334 getSyncGroup(id).addToSync(wc); 335 } 336 setReady(int id, boolean ready)337 void setReady(int id, boolean ready) { 338 getSyncGroup(id).setReady(ready); 339 } 340 setReady(int id)341 void setReady(int id) { 342 setReady(id, true); 343 } 344 isReady(int id)345 boolean isReady(int id) { 346 return getSyncGroup(id).mReady; 347 } 348 349 /** 350 * Aborts the sync (ie. it doesn't wait for ready or anything to finish) 351 */ abort(int id)352 void abort(int id) { 353 getSyncGroup(id).finishNow(); 354 } 355 getSyncGroup(int id)356 private SyncGroup getSyncGroup(int id) { 357 final SyncGroup syncGroup = mActiveSyncs.get(id); 358 if (syncGroup == null) { 359 throw new IllegalStateException("SyncGroup is not started yet id=" + id); 360 } 361 return syncGroup; 362 } 363 onSurfacePlacement()364 void onSurfacePlacement() { 365 // backwards since each state can remove itself if finished 366 for (int i = mActiveSyncs.size() - 1; i >= 0; --i) { 367 mActiveSyncs.valueAt(i).onSurfacePlacement(); 368 } 369 } 370 371 /** 372 * Queues a sync operation onto this engine. It will wait until any current/prior sync-sets 373 * have finished to run. This is needed right now because currently {@link BLASTSyncEngine} 374 * only supports 1 sync at a time. 375 * 376 * Code-paths should avoid using this unless absolutely necessary. Usually, we use this for 377 * difficult edge-cases that we hope to clean-up later. 378 * 379 * @param startSync will be called immediately when the {@link BLASTSyncEngine} is free to 380 * "reserve" the {@link BLASTSyncEngine} by calling one of the 381 * {@link BLASTSyncEngine#startSyncSet} variants. 382 * @param applySync will be posted to the main handler after {@code startSync} has been 383 * called. This is posted so that it doesn't interrupt any clean-up for the 384 * prior sync-set. 385 */ queueSyncSet(@onNull Runnable startSync, @NonNull Runnable applySync)386 void queueSyncSet(@NonNull Runnable startSync, @NonNull Runnable applySync) { 387 final PendingSyncSet pt = new PendingSyncSet(); 388 pt.mStartSync = startSync; 389 pt.mApplySync = applySync; 390 mPendingSyncSets.add(pt); 391 } 392 393 /** @return {@code true} if there are any sync-sets waiting to start. */ hasPendingSyncSets()394 boolean hasPendingSyncSets() { 395 return !mPendingSyncSets.isEmpty(); 396 } 397 } 398