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