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