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