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