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 android.view;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import android.annotation.BinderThread;
22 import android.annotation.NonNull;
23 import android.annotation.UiThread;
24 import android.graphics.Point;
25 import android.graphics.Rect;
26 import android.os.CancellationSignal;
27 import android.os.ICancellationSignal;
28 import android.os.RemoteException;
29 import android.os.Trace;
30 import android.util.CloseGuard;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.lang.ref.Reference;
36 import java.lang.ref.WeakReference;
37 import java.util.concurrent.Executor;
38 import java.util.function.Consumer;
39 
40 /**
41  * Mediator between a selected scroll capture target view and a remote process.
42  * <p>
43  * An instance is created to wrap the selected {@link ScrollCaptureCallback}.
44  *
45  * @hide
46  */
47 public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
48 
49     private static final String TAG = "ScrollCaptureConnection";
50 
51     private final Object mLock = new Object();
52     private final Rect mScrollBounds;
53     private final Point mPositionInWindow;
54     private final Executor mUiThread;
55     private final CloseGuard mCloseGuard = new CloseGuard();
56 
57 
58     private ScrollCaptureCallback mLocal;
59     private IScrollCaptureCallbacks mRemote;
60 
61     private ScrollCaptureSession mSession;
62 
63     private CancellationSignal mCancellation;
64 
65     private volatile boolean mActive;
66 
67     /**
68      * Constructs a ScrollCaptureConnection.
69      *
70      * @param uiThread an executor for the UI thread of the containing View
71      * @param selectedTarget  the target the client is controlling
72      *
73      * @hide
74      */
ScrollCaptureConnection( @onNull Executor uiThread, @NonNull ScrollCaptureTarget selectedTarget)75     public ScrollCaptureConnection(
76             @NonNull Executor uiThread,
77             @NonNull ScrollCaptureTarget selectedTarget) {
78         mUiThread = requireNonNull(uiThread, "<uiThread> must non-null");
79         requireNonNull(selectedTarget, "<selectedTarget> must non-null");
80         mScrollBounds = requireNonNull(Rect.copyOrNull(selectedTarget.getScrollBounds()),
81                 "target.getScrollBounds() must be non-null to construct a client");
82         mLocal = selectedTarget.getCallback();
83         mPositionInWindow = new Point(selectedTarget.getPositionInWindow());
84     }
85 
86     @BinderThread
87     @Override
startCapture(@onNull Surface surface, @NonNull IScrollCaptureCallbacks remote)88     public ICancellationSignal startCapture(@NonNull Surface surface,
89             @NonNull IScrollCaptureCallbacks remote) throws RemoteException {
90 
91         mCloseGuard.open("close");
92 
93         if (!surface.isValid()) {
94             throw new RemoteException(new IllegalArgumentException("surface must be valid"));
95         }
96         mRemote = requireNonNull(remote, "<callbacks> must non-null");
97 
98         ICancellationSignal cancellation = CancellationSignal.createTransport();
99         mCancellation = CancellationSignal.fromTransport(cancellation);
100         mSession = new ScrollCaptureSession(surface, mScrollBounds, mPositionInWindow);
101 
102         Runnable listener =
103                 SafeCallback.create(mCancellation, mUiThread, this::onStartCaptureCompleted);
104         // -> UiThread
105         mUiThread.execute(() -> mLocal.onScrollCaptureStart(mSession, mCancellation, listener));
106         return cancellation;
107     }
108 
109     @UiThread
onStartCaptureCompleted()110     private void onStartCaptureCompleted() {
111         mActive = true;
112         try {
113             mRemote.onCaptureStarted();
114         } catch (RemoteException e) {
115             Log.w(TAG, "Shutting down due to error: ", e);
116             close();
117         }
118     }
119 
120     @BinderThread
121     @Override
requestImage(Rect requestRect)122     public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
123         Trace.beginSection("requestImage");
124         checkActive();
125 
126         ICancellationSignal cancellation = CancellationSignal.createTransport();
127         mCancellation = CancellationSignal.fromTransport(cancellation);
128 
129         Consumer<Rect> listener =
130                 SafeCallback.create(mCancellation, mUiThread, this::onImageRequestCompleted);
131         // -> UiThread
132         mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest(
133                 mSession, mCancellation, new Rect(requestRect), listener));
134         Trace.endSection();
135         return cancellation;
136     }
137 
138     @UiThread
onImageRequestCompleted(Rect capturedArea)139     void onImageRequestCompleted(Rect capturedArea) {
140         try {
141             mRemote.onImageRequestCompleted(0, capturedArea);
142         } catch (RemoteException e) {
143             Log.w(TAG, "Shutting down due to error: ", e);
144             close();
145         }
146     }
147 
148     @BinderThread
149     @Override
endCapture()150     public ICancellationSignal endCapture() throws RemoteException {
151         checkActive();
152 
153         ICancellationSignal cancellation = CancellationSignal.createTransport();
154         mCancellation = CancellationSignal.fromTransport(cancellation);
155 
156         Runnable listener =
157                 SafeCallback.create(mCancellation, mUiThread, this::onEndCaptureCompleted);
158         // -> UiThread
159         mUiThread.execute(() -> mLocal.onScrollCaptureEnd(listener));
160         return cancellation;
161     }
162 
163     @UiThread
onEndCaptureCompleted()164     private void onEndCaptureCompleted() {
165         mActive = false;
166         try {
167             if (mRemote != null) {
168                 mRemote.onCaptureEnded();
169             }
170         } catch (RemoteException e) {
171             Log.w(TAG, "Caught exception confirming capture end!", e);
172         } finally {
173             close();
174         }
175     }
176 
177     @BinderThread
178     @Override
close()179     public void close() {
180         if (mActive) {
181             if (mCancellation != null) {
182                 Log.w(TAG, "close(): cancelling pending operation.");
183                 mCancellation.cancel();
184                 mCancellation = null;
185             }
186             Log.w(TAG, "close(): capture session still active! Ending now.");
187             // -> UiThread
188             final ScrollCaptureCallback callback = mLocal;
189             mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
190             mActive = false;
191         }
192         mActive = false;
193         mSession = null;
194         mRemote = null;
195         mLocal = null;
196         mCloseGuard.close();
197         Reference.reachabilityFence(this);
198     }
199 
200     @VisibleForTesting
isActive()201     public boolean isActive() {
202         return mActive;
203     }
204 
checkActive()205     private void checkActive() throws RemoteException {
206         synchronized (mLock) {
207             if (!mActive) {
208                 throw new RemoteException(new IllegalStateException("Not started!"));
209             }
210         }
211     }
212 
213     /** @return a string representation of the state of this client */
toString()214     public String toString() {
215         return "ScrollCaptureConnection{"
216                 + "active=" + mActive
217                 + ", session=" + mSession
218                 + ", remote=" + mRemote
219                 + ", local=" + mLocal
220                 + "}";
221     }
222 
finalize()223     protected void finalize() throws Throwable {
224         try {
225             mCloseGuard.warnIfOpen();
226             close();
227         } finally {
228             super.finalize();
229         }
230     }
231 
232     private static class SafeCallback<T> {
233         private final CancellationSignal mSignal;
234         private final WeakReference<T> mTargetRef;
235         private final Executor mExecutor;
236         private boolean mExecuted;
237 
SafeCallback(CancellationSignal signal, Executor executor, T target)238         protected SafeCallback(CancellationSignal signal, Executor executor, T target) {
239             mSignal = signal;
240             mTargetRef = new WeakReference<>(target);
241             mExecutor = executor;
242         }
243 
244         // Provide the target to the consumer to invoke, forward on handler thread ONCE,
245         // and only if noy cancelled, and the target is still available (not collected)
maybeAccept(Consumer<T> targetConsumer)246         protected final void maybeAccept(Consumer<T> targetConsumer) {
247             if (mExecuted) {
248                 return;
249             }
250             mExecuted = true;
251             if (mSignal.isCanceled()) {
252                 return;
253             }
254             T target = mTargetRef.get();
255             if (target == null) {
256                 return;
257             }
258             mExecutor.execute(() -> targetConsumer.accept(target));
259         }
260 
create(CancellationSignal signal, Executor executor, Runnable target)261         static Runnable create(CancellationSignal signal, Executor executor, Runnable target) {
262             return new RunnableCallback(signal, executor, target);
263         }
264 
create(CancellationSignal signal, Executor executor, Consumer<T> target)265         static <T> Consumer<T> create(CancellationSignal signal, Executor executor,
266                 Consumer<T> target) {
267             return new ConsumerCallback<T>(signal, executor, target);
268         }
269     }
270 
271     private static final class RunnableCallback extends SafeCallback<Runnable> implements Runnable {
RunnableCallback(CancellationSignal signal, Executor executor, Runnable target)272         RunnableCallback(CancellationSignal signal, Executor executor, Runnable target) {
273             super(signal, executor, target);
274         }
275 
276         @Override
run()277         public void run() {
278             maybeAccept(Runnable::run);
279         }
280     }
281 
282     private static final class ConsumerCallback<T> extends SafeCallback<Consumer<T>>
283             implements Consumer<T> {
ConsumerCallback(CancellationSignal signal, Executor executor, Consumer<T> target)284         ConsumerCallback(CancellationSignal signal, Executor executor, Consumer<T> target) {
285             super(signal, executor, target);
286         }
287 
288         @Override
accept(T value)289         public void accept(T value) {
290             maybeAccept((target) -> target.accept(value));
291         }
292     }
293 }
294