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