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