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