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