1 /* <lambda>null2 * Copyright (C) 2024 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 com.android.systemui.screenshot.scroll 18 19 import android.app.ActivityManager 20 import android.graphics.Rect 21 import android.os.IBinder 22 import android.util.Log 23 import android.view.ScrollCaptureResponse 24 import com.android.systemui.dagger.qualifiers.Main 25 import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot 26 import com.google.common.util.concurrent.ListenableFuture 27 import java.util.concurrent.ExecutionException 28 import java.util.concurrent.Executor 29 import java.util.concurrent.Future 30 import javax.inject.Inject 31 32 class ScrollCaptureExecutor 33 @Inject 34 constructor( 35 activityManager: ActivityManager, 36 private val scrollCaptureClient: ScrollCaptureClient, 37 private val scrollCaptureController: ScrollCaptureController, 38 private val longScreenshotHolder: LongScreenshotData, 39 @Main private val mainExecutor: Executor 40 ) { 41 private val isLowRamDevice = activityManager.isLowRamDevice 42 private var lastScrollCaptureRequest: ListenableFuture<ScrollCaptureResponse>? = null 43 private var lastScrollCaptureResponse: ScrollCaptureResponse? = null 44 private var longScreenshotFuture: ListenableFuture<LongScreenshot>? = null 45 46 fun requestScrollCapture( 47 displayId: Int, 48 token: IBinder, 49 callback: (ScrollCaptureResponse) -> Unit 50 ) { 51 if (!allowLongScreenshots()) { 52 Log.d(TAG, "Long screenshots not supported on this device") 53 return 54 } 55 scrollCaptureClient.setHostWindowToken(token) 56 lastScrollCaptureRequest?.cancel(true) 57 val scrollRequest = 58 scrollCaptureClient.request(displayId).apply { 59 addListener( 60 { onScrollCaptureResponseReady(this)?.let { callback.invoke(it) } }, 61 mainExecutor 62 ) 63 } 64 lastScrollCaptureRequest = scrollRequest 65 } 66 67 fun interface ScrollTransitionReady { 68 fun onTransitionReady( 69 destRect: Rect, 70 onTransitionEnd: Runnable, 71 longScreenshot: LongScreenshot 72 ) 73 } 74 75 fun executeBatchScrollCapture( 76 response: ScrollCaptureResponse, 77 onCaptureComplete: Runnable, 78 onFailure: Runnable, 79 transition: ScrollTransitionReady, 80 ) { 81 // Clear the reference to prevent close() on reset 82 lastScrollCaptureResponse = null 83 longScreenshotFuture?.cancel(true) 84 longScreenshotFuture = 85 scrollCaptureController.run(response).apply { 86 addListener( 87 { 88 getLongScreenshotChecked(this, onFailure)?.let { 89 longScreenshotHolder.setLongScreenshot(it) 90 longScreenshotHolder.setTransitionDestinationCallback { 91 destinationRect: Rect, 92 onTransitionEnd: Runnable -> 93 transition.onTransitionReady(destinationRect, onTransitionEnd, it) 94 } 95 onCaptureComplete.run() 96 } 97 }, 98 mainExecutor 99 ) 100 } 101 } 102 103 fun close() { 104 lastScrollCaptureRequest?.cancel(true) 105 lastScrollCaptureRequest = null 106 lastScrollCaptureResponse?.close() 107 lastScrollCaptureResponse = null 108 longScreenshotFuture?.cancel(true) 109 } 110 111 private fun getLongScreenshotChecked( 112 future: ListenableFuture<LongScreenshot>, 113 onFailure: Runnable 114 ): LongScreenshot? { 115 var longScreenshot: LongScreenshot? = null 116 runCatching { longScreenshot = future.get() } 117 .onFailure { 118 Log.e(TAG, "Caught exception", it) 119 onFailure.run() 120 return null 121 } 122 if (longScreenshot?.height != 0) { 123 return longScreenshot 124 } 125 onFailure.run() 126 return null 127 } 128 129 private fun onScrollCaptureResponseReady( 130 responseFuture: Future<ScrollCaptureResponse> 131 ): ScrollCaptureResponse? { 132 try { 133 lastScrollCaptureResponse?.close() 134 lastScrollCaptureResponse = null 135 if (responseFuture.isCancelled) { 136 return null 137 } 138 val captureResponse = responseFuture.get().apply { lastScrollCaptureResponse = this } 139 if (!captureResponse.isConnected) { 140 // No connection means that the target window wasn't found 141 // or that it cannot support scroll capture. 142 Log.d( 143 TAG, 144 "ScrollCapture: ${captureResponse.description} [${captureResponse.windowTitle}]" 145 ) 146 return null 147 } 148 Log.d(TAG, "ScrollCapture: connected to window [${captureResponse.windowTitle}]") 149 return captureResponse 150 } catch (e: InterruptedException) { 151 Log.e(TAG, "requestScrollCapture interrupted", e) 152 } catch (e: ExecutionException) { 153 Log.e(TAG, "requestScrollCapture failed", e) 154 } 155 return null 156 } 157 158 private fun allowLongScreenshots(): Boolean { 159 return !isLowRamDevice 160 } 161 162 private companion object { 163 private const val TAG = "ScrollCaptureExecutor" 164 } 165 } 166