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