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.systemui.stackdivider; 18 19 import android.os.Handler; 20 import android.util.Slog; 21 import android.view.SurfaceControl; 22 import android.window.WindowContainerTransaction; 23 import android.window.WindowContainerTransactionCallback; 24 import android.window.WindowOrganizer; 25 26 import androidx.annotation.NonNull; 27 28 import com.android.systemui.TransactionPool; 29 30 import java.util.ArrayList; 31 32 /** 33 * Helper for serializing sync-transactions and corresponding callbacks. 34 */ 35 class SyncTransactionQueue { 36 private static final boolean DEBUG = Divider.DEBUG; 37 private static final String TAG = "SyncTransactionQueue"; 38 39 // Just a little longer than the sync-engine timeout of 5s 40 private static final int REPLY_TIMEOUT = 5300; 41 42 private final TransactionPool mTransactionPool; 43 private final Handler mHandler; 44 45 // Sync Transactions currently don't support nesting or interleaving properly, so 46 // queue up transactions to run them serially. 47 private final ArrayList<SyncCallback> mQueue = new ArrayList<>(); 48 49 private SyncCallback mInFlight = null; 50 private final ArrayList<TransactionRunnable> mRunnables = new ArrayList<>(); 51 52 private final Runnable mOnReplyTimeout = () -> { 53 synchronized (mQueue) { 54 if (mInFlight != null && mQueue.contains(mInFlight)) { 55 Slog.w(TAG, "Sync Transaction timed-out: " + mInFlight.mWCT); 56 mInFlight.onTransactionReady(mInFlight.mId, new SurfaceControl.Transaction()); 57 } 58 } 59 }; 60 SyncTransactionQueue(TransactionPool pool, Handler handler)61 SyncTransactionQueue(TransactionPool pool, Handler handler) { 62 mTransactionPool = pool; 63 mHandler = handler; 64 } 65 66 /** 67 * Queues a sync transaction to be sent serially to WM. 68 */ queue(WindowContainerTransaction wct)69 void queue(WindowContainerTransaction wct) { 70 SyncCallback cb = new SyncCallback(wct); 71 synchronized (mQueue) { 72 if (DEBUG) Slog.d(TAG, "Queueing up " + wct); 73 mQueue.add(cb); 74 if (mQueue.size() == 1) { 75 cb.send(); 76 } 77 } 78 } 79 80 /** 81 * Queues a sync transaction only if there are already sync transaction(s) queued or in flight. 82 * Otherwise just returns without queueing. 83 * @return {@code true} if queued, {@code false} if not. 84 */ queueIfWaiting(WindowContainerTransaction wct)85 boolean queueIfWaiting(WindowContainerTransaction wct) { 86 synchronized (mQueue) { 87 if (mQueue.isEmpty()) { 88 if (DEBUG) Slog.d(TAG, "Nothing in queue, so skip queueing up " + wct); 89 return false; 90 } 91 if (DEBUG) Slog.d(TAG, "Queue is non-empty, so queueing up " + wct); 92 SyncCallback cb = new SyncCallback(wct); 93 mQueue.add(cb); 94 if (mQueue.size() == 1) { 95 cb.send(); 96 } 97 } 98 return true; 99 } 100 101 /** 102 * Runs a runnable in sync with sync transactions (ie. when the current in-flight transaction 103 * returns. If there are no transactions in-flight, runnable executes immediately. 104 */ runInSync(TransactionRunnable runnable)105 void runInSync(TransactionRunnable runnable) { 106 synchronized (mQueue) { 107 if (DEBUG) Slog.d(TAG, "Run in sync. mInFlight=" + mInFlight); 108 if (mInFlight != null) { 109 mRunnables.add(runnable); 110 return; 111 } 112 } 113 SurfaceControl.Transaction t = mTransactionPool.acquire(); 114 runnable.runWithTransaction(t); 115 t.apply(); 116 mTransactionPool.release(t); 117 } 118 119 // Synchronized on mQueue onTransactionReceived(@onNull SurfaceControl.Transaction t)120 private void onTransactionReceived(@NonNull SurfaceControl.Transaction t) { 121 if (DEBUG) Slog.d(TAG, " Running " + mRunnables.size() + " sync runnables"); 122 for (int i = 0, n = mRunnables.size(); i < n; ++i) { 123 mRunnables.get(i).runWithTransaction(t); 124 } 125 mRunnables.clear(); 126 t.apply(); 127 t.close(); 128 } 129 130 interface TransactionRunnable { runWithTransaction(SurfaceControl.Transaction t)131 void runWithTransaction(SurfaceControl.Transaction t); 132 } 133 134 private class SyncCallback extends WindowContainerTransactionCallback { 135 int mId = -1; 136 final WindowContainerTransaction mWCT; 137 SyncCallback(WindowContainerTransaction wct)138 SyncCallback(WindowContainerTransaction wct) { 139 mWCT = wct; 140 } 141 142 // Must be sychronized on mQueue send()143 void send() { 144 if (mInFlight != null) { 145 throw new IllegalStateException("Sync Transactions must be serialized. In Flight: " 146 + mInFlight.mId + " - " + mInFlight.mWCT); 147 } 148 mInFlight = this; 149 if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT); 150 mId = new WindowOrganizer().applySyncTransaction(mWCT, this); 151 if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId); 152 mHandler.postDelayed(mOnReplyTimeout, REPLY_TIMEOUT); 153 } 154 155 @Override onTransactionReady(int id, @androidx.annotation.NonNull SurfaceControl.Transaction t)156 public void onTransactionReady(int id, 157 @androidx.annotation.NonNull SurfaceControl.Transaction t) { 158 mHandler.post(() -> { 159 synchronized (mQueue) { 160 if (mId != id) { 161 Slog.e(TAG, "Got an unexpected onTransactionReady. Expected " 162 + mId + " but got " + id); 163 return; 164 } 165 mInFlight = null; 166 mHandler.removeCallbacks(mOnReplyTimeout); 167 if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId); 168 mQueue.remove(this); 169 onTransactionReceived(t); 170 if (!mQueue.isEmpty()) { 171 mQueue.get(0).send(); 172 } 173 } 174 }); 175 } 176 } 177 } 178