| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.wm.shell.common; |
| |
| import android.annotation.BinderThread; |
| import android.annotation.NonNull; |
| import android.os.RemoteException; |
| import android.util.Slog; |
| import android.view.SurfaceControl; |
| import android.view.WindowManager; |
| import android.window.WindowContainerTransaction; |
| import android.window.WindowContainerTransactionCallback; |
| import android.window.WindowOrganizer; |
| |
| import com.android.wm.shell.transition.LegacyTransitions; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Helper for serializing sync-transactions and corresponding callbacks. |
| */ |
| public final class SyncTransactionQueue { |
| private static final boolean DEBUG = false; |
| private static final String TAG = "SyncTransactionQueue"; |
| |
| // Just a little longer than the sync-engine timeout of 5s |
| private static final int REPLY_TIMEOUT = 5300; |
| |
| private final TransactionPool mTransactionPool; |
| private final ShellExecutor mMainExecutor; |
| |
| // Sync Transactions currently don't support nesting or interleaving properly, so |
| // queue up transactions to run them serially. |
| private final ArrayList<SyncCallback> mQueue = new ArrayList<>(); |
| |
| private SyncCallback mInFlight = null; |
| private final ArrayList<TransactionRunnable> mRunnables = new ArrayList<>(); |
| |
| private final Runnable mOnReplyTimeout = () -> { |
| synchronized (mQueue) { |
| if (mInFlight != null && mQueue.contains(mInFlight)) { |
| Slog.w(TAG, "Sync Transaction timed-out: " + mInFlight.mWCT); |
| mInFlight.onTransactionReady(mInFlight.mId, new SurfaceControl.Transaction()); |
| } |
| } |
| }; |
| |
| public SyncTransactionQueue(TransactionPool pool, ShellExecutor mainExecutor) { |
| mTransactionPool = pool; |
| mMainExecutor = mainExecutor; |
| } |
| |
| /** |
| * Queues a sync transaction to be sent serially to WM. |
| */ |
| public void queue(WindowContainerTransaction wct) { |
| if (wct.isEmpty()) { |
| if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty"); |
| return; |
| } |
| SyncCallback cb = new SyncCallback(wct); |
| synchronized (mQueue) { |
| if (DEBUG) Slog.d(TAG, "Queueing up " + wct); |
| mQueue.add(cb); |
| if (mQueue.size() == 1) { |
| cb.send(); |
| } |
| } |
| } |
| |
| /** |
| * Queues a legacy transition to be sent serially to WM |
| */ |
| public void queue(LegacyTransitions.ILegacyTransition transition, |
| @WindowManager.TransitionType int type, WindowContainerTransaction wct) { |
| if (wct.isEmpty()) { |
| if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty"); |
| return; |
| } |
| SyncCallback cb = new SyncCallback(transition, type, wct); |
| synchronized (mQueue) { |
| if (DEBUG) Slog.d(TAG, "Queueing up legacy transition " + wct); |
| mQueue.add(cb); |
| if (mQueue.size() == 1) { |
| cb.send(); |
| } |
| } |
| } |
| |
| /** |
| * Queues a sync transaction only if there are already sync transaction(s) queued or in flight. |
| * Otherwise just returns without queueing. |
| * @return {@code true} if queued, {@code false} if not. |
| */ |
| public boolean queueIfWaiting(WindowContainerTransaction wct) { |
| if (wct.isEmpty()) { |
| if (DEBUG) Slog.d(TAG, "Skip queueIfWaiting due to transaction change is empty"); |
| return false; |
| } |
| synchronized (mQueue) { |
| if (mQueue.isEmpty()) { |
| if (DEBUG) Slog.d(TAG, "Nothing in queue, so skip queueing up " + wct); |
| return false; |
| } |
| if (DEBUG) Slog.d(TAG, "Queue is non-empty, so queueing up " + wct); |
| SyncCallback cb = new SyncCallback(wct); |
| mQueue.add(cb); |
| if (mQueue.size() == 1) { |
| cb.send(); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Runs a runnable in sync with sync transactions (ie. when the current in-flight transaction |
| * returns. If there are no transactions in-flight, runnable executes immediately. |
| */ |
| public void runInSync(TransactionRunnable runnable) { |
| synchronized (mQueue) { |
| if (DEBUG) Slog.d(TAG, "Run in sync. mInFlight=" + mInFlight); |
| if (mInFlight != null) { |
| mRunnables.add(runnable); |
| return; |
| } |
| } |
| SurfaceControl.Transaction t = mTransactionPool.acquire(); |
| runnable.runWithTransaction(t); |
| t.apply(); |
| mTransactionPool.release(t); |
| } |
| |
| // Synchronized on mQueue |
| private void onTransactionReceived(@NonNull SurfaceControl.Transaction t) { |
| if (DEBUG) Slog.d(TAG, " Running " + mRunnables.size() + " sync runnables"); |
| final int n = mRunnables.size(); |
| for (int i = 0; i < n; ++i) { |
| mRunnables.get(i).runWithTransaction(t); |
| } |
| // More runnables may have been added, so only remove the ones that ran. |
| mRunnables.subList(0, n).clear(); |
| } |
| |
| /** Task to run with transaction. */ |
| public interface TransactionRunnable { |
| /** Runs with transaction. */ |
| void runWithTransaction(SurfaceControl.Transaction t); |
| } |
| |
| private class SyncCallback extends WindowContainerTransactionCallback { |
| int mId = -1; |
| final WindowContainerTransaction mWCT; |
| final LegacyTransitions.LegacyTransition mLegacyTransition; |
| |
| SyncCallback(WindowContainerTransaction wct) { |
| mWCT = wct; |
| mLegacyTransition = null; |
| } |
| |
| SyncCallback(LegacyTransitions.ILegacyTransition legacyTransition, |
| @WindowManager.TransitionType int type, WindowContainerTransaction wct) { |
| mWCT = wct; |
| mLegacyTransition = new LegacyTransitions.LegacyTransition(type, legacyTransition); |
| } |
| |
| // Must be sychronized on mQueue |
| void send() { |
| if (mInFlight == this) { |
| // This was probably queued up and sent during a sync runnable of the last callback. |
| // Don't queue it again. |
| return; |
| } |
| if (mInFlight != null) { |
| throw new IllegalStateException("Sync Transactions must be serialized. In Flight: " |
| + mInFlight.mId + " - " + mInFlight.mWCT); |
| } |
| mInFlight = this; |
| if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT); |
| if (mLegacyTransition != null) { |
| mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(), |
| mLegacyTransition.getAdapter(), this, mWCT); |
| } else { |
| mId = new WindowOrganizer().applySyncTransaction(mWCT, this); |
| } |
| if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId); |
| mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT); |
| } |
| |
| @BinderThread |
| @Override |
| public void onTransactionReady(int id, |
| @NonNull SurfaceControl.Transaction t) { |
| mMainExecutor.execute(() -> { |
| synchronized (mQueue) { |
| if (mId != id) { |
| Slog.e(TAG, "Got an unexpected onTransactionReady. Expected " |
| + mId + " but got " + id); |
| return; |
| } |
| mInFlight = null; |
| mMainExecutor.removeCallbacks(mOnReplyTimeout); |
| if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId); |
| mQueue.remove(this); |
| onTransactionReceived(t); |
| if (mLegacyTransition != null) { |
| try { |
| mLegacyTransition.getSyncCallback().onTransactionReady(mId, t); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e); |
| } |
| } else { |
| t.apply(); |
| t.close(); |
| } |
| if (!mQueue.isEmpty()) { |
| mQueue.get(0).send(); |
| } |
| } |
| }); |
| } |
| } |
| } |