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