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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; 20 21 import android.annotation.NonNull; 22 import android.util.ArraySet; 23 import android.util.SparseArray; 24 import android.view.SurfaceControl; 25 26 import com.android.internal.protolog.common.ProtoLog; 27 28 /** 29 * Utility class for collecting WindowContainers that will merge transactions. 30 * For example to use to synchronously resize all the children of a window container 31 * 1. Open a new sync set, and pass the listener that will be invoked 32 * int id startSyncSet(TransactionReadyListener) 33 * the returned ID will be eventually passed to the TransactionReadyListener in combination 34 * with a set of WindowContainers that are ready, meaning onTransactionReady was called for 35 * those WindowContainers. You also use it to refer to the operation in future steps. 36 * 2. Ask each child to participate: 37 * addToSyncSet(int id, WindowContainer wc) 38 * if the child thinks it will be affected by a configuration change (a.k.a. has a visible 39 * window in its sub hierarchy, then we will increment a counter of expected callbacks 40 * At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy 41 * updates in to the sync engine. 42 * 3. Apply your configuration changes to the window containers. 43 * 4. Tell the engine that the sync set is ready 44 * setReady(int id) 45 * 5. If there were no sub windows anywhere in the hierarchy to wait on, then 46 * transactionReady is immediately invoked, otherwise all the windows are poked 47 * to redraw and to deliver a buffer to {@link WindowState#finishDrawing}. 48 * Once all this drawing is complete, all the transactions will be merged and delivered 49 * to TransactionReadyListener. 50 * 51 * This works primarily by setting-up state and then watching/waiting for the registered subtrees 52 * to enter into a "finished" state (either by receiving drawn content or by disappearing). This 53 * checks the subtrees during surface-placement. 54 */ 55 class BLASTSyncEngine { 56 private static final String TAG = "BLASTSyncEngine"; 57 58 interface TransactionReadyListener { onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction)59 void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); 60 } 61 62 /** 63 * Holds state associated with a single synchronous set of operations. 64 */ 65 class SyncGroup { 66 final int mSyncId; 67 final TransactionReadyListener mListener; 68 boolean mReady = false; 69 final ArraySet<WindowContainer> mRootMembers = new ArraySet<>(); 70 private SurfaceControl.Transaction mOrphanTransaction = null; 71 SyncGroup(TransactionReadyListener listener, int id)72 private SyncGroup(TransactionReadyListener listener, int id) { 73 mSyncId = id; 74 mListener = listener; 75 } 76 77 /** 78 * Gets a transaction to dump orphaned operations into. Orphaned operations are operations 79 * that were on the mSyncTransactions of "root" subtrees which have been removed during the 80 * sync period. 81 */ 82 @NonNull getOrphanTransaction()83 SurfaceControl.Transaction getOrphanTransaction() { 84 if (mOrphanTransaction == null) { 85 // Lazy since this isn't common 86 mOrphanTransaction = mWm.mTransactionFactory.get(); 87 } 88 return mOrphanTransaction; 89 } 90 onSurfacePlacement()91 private void onSurfacePlacement() { 92 if (!mReady) return; 93 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s", 94 mSyncId, mRootMembers); 95 for (int i = mRootMembers.size() - 1; i >= 0; --i) { 96 final WindowContainer wc = mRootMembers.valueAt(i); 97 if (!wc.isSyncFinished()) { 98 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Unfinished container: %s", 99 mSyncId, wc); 100 return; 101 } 102 } 103 finishNow(); 104 } 105 finishNow()106 private void finishNow() { 107 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId); 108 SurfaceControl.Transaction merged = mWm.mTransactionFactory.get(); 109 if (mOrphanTransaction != null) { 110 merged.merge(mOrphanTransaction); 111 } 112 for (WindowContainer wc : mRootMembers) { 113 wc.finishSync(merged, false /* cancel */); 114 } 115 mListener.onTransactionReady(mSyncId, merged); 116 mActiveSyncs.remove(mSyncId); 117 } 118 setReady(boolean ready)119 private void setReady(boolean ready) { 120 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId); 121 mReady = ready; 122 if (!ready) return; 123 mWm.mWindowPlacerLocked.requestTraversal(); 124 } 125 addToSync(WindowContainer wc)126 private void addToSync(WindowContainer wc) { 127 if (!mRootMembers.add(wc)) { 128 return; 129 } 130 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc); 131 wc.setSyncGroup(this); 132 wc.prepareSync(); 133 mWm.mWindowPlacerLocked.requestTraversal(); 134 } 135 onCancelSync(WindowContainer wc)136 void onCancelSync(WindowContainer wc) { 137 mRootMembers.remove(wc); 138 } 139 } 140 141 private final WindowManagerService mWm; 142 private int mNextSyncId = 0; 143 private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>(); 144 BLASTSyncEngine(WindowManagerService wms)145 BLASTSyncEngine(WindowManagerService wms) { 146 mWm = wms; 147 } 148 startSyncSet(TransactionReadyListener listener)149 int startSyncSet(TransactionReadyListener listener) { 150 final int id = mNextSyncId++; 151 final SyncGroup s = new SyncGroup(listener, id); 152 mActiveSyncs.put(id, s); 153 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener); 154 return id; 155 } 156 addToSyncSet(int id, WindowContainer wc)157 void addToSyncSet(int id, WindowContainer wc) { 158 mActiveSyncs.get(id).addToSync(wc); 159 } 160 setReady(int id, boolean ready)161 void setReady(int id, boolean ready) { 162 mActiveSyncs.get(id).setReady(ready); 163 } 164 setReady(int id)165 void setReady(int id) { 166 setReady(id, true); 167 } 168 169 /** 170 * Aborts the sync (ie. it doesn't wait for ready or anything to finish) 171 */ abort(int id)172 void abort(int id) { 173 mActiveSyncs.get(id).finishNow(); 174 } 175 onSurfacePlacement()176 void onSurfacePlacement() { 177 // backwards since each state can remove itself if finished 178 for (int i = mActiveSyncs.size() - 1; i >= 0; --i) { 179 mActiveSyncs.valueAt(i).onSurfacePlacement(); 180 } 181 } 182 } 183