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