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