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