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