1 /*
2  * Copyright (C) 2022 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
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.os.Bundle
22 import android.os.Process.myUserHandle
23 import android.os.RemoteException
24 import android.os.UserHandle
25 import android.util.Log
26 import android.view.IRemoteAnimationFinishedCallback
27 import android.view.IRemoteAnimationRunner
28 import android.view.RemoteAnimationAdapter
29 import android.view.RemoteAnimationTarget
30 import android.view.WindowManager
31 import android.view.WindowManagerGlobal
32 import com.android.internal.infra.ServiceConnector
33 import com.android.systemui.dagger.SysUISingleton
34 import com.android.systemui.dagger.qualifiers.Application
35 import com.android.systemui.dagger.qualifiers.Main
36 import com.android.systemui.settings.DisplayTracker
37 import javax.inject.Inject
38 import kotlinx.coroutines.CompletableDeferred
39 import kotlinx.coroutines.CoroutineDispatcher
40 import kotlinx.coroutines.CoroutineScope
41 import kotlinx.coroutines.launch
42 import kotlinx.coroutines.withContext
43 
44 @SysUISingleton
45 class ActionIntentExecutor
46 @Inject
47 constructor(
48     private val context: Context,
49     @Application private val applicationScope: CoroutineScope,
50     @Main private val mainDispatcher: CoroutineDispatcher,
51     private val displayTracker: DisplayTracker,
52 ) {
53     /**
54      * Execute the given intent with startActivity while performing operations for screenshot action
55      * launching.
56      * - Dismiss the keyguard first
57      * - If the userId is not the current user, proxy to a service running as that user to execute
58      * - After startActivity, optionally override the pending app transition.
59      */
60     fun launchIntentAsync(
61         intent: Intent,
62         options: Bundle?,
63         user: UserHandle,
64         overrideTransition: Boolean,
65     ) {
66         applicationScope.launch { launchIntent(intent, options, user, overrideTransition) }
67     }
68 
69     suspend fun launchIntent(
70         intent: Intent,
71         options: Bundle?,
72         user: UserHandle,
73         overrideTransition: Boolean,
74     ) {
75         dismissKeyguard()
76 
77         if (user == myUserHandle()) {
78             withContext(mainDispatcher) { context.startActivity(intent, options) }
79         } else {
80             launchCrossProfileIntent(user, intent, options)
81         }
82 
83         if (overrideTransition) {
84             val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
85             try {
86                 checkNotNull(WindowManagerGlobal.getWindowManagerService())
87                     .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
88             } catch (e: Exception) {
89                 Log.e(TAG, "Error overriding screenshot app transition", e)
90             }
91         }
92     }
93 
94     private val proxyConnector: ServiceConnector<IScreenshotProxy> =
95         ServiceConnector.Impl(
96             context,
97             Intent(context, ScreenshotProxyService::class.java),
98             Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
99             context.userId,
100             IScreenshotProxy.Stub::asInterface,
101         )
102 
103     private suspend fun dismissKeyguard() {
104         val completion = CompletableDeferred<Unit>()
105         val onDoneBinder =
106             object : IOnDoneCallback.Stub() {
107                 override fun onDone(success: Boolean) {
108                     completion.complete(Unit)
109                 }
110             }
111         proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
112         completion.await()
113     }
114 
115     private fun getCrossProfileConnector(user: UserHandle): ServiceConnector<ICrossProfileService> =
116         ServiceConnector.Impl<ICrossProfileService>(
117             context,
118             Intent(context, ScreenshotCrossProfileService::class.java),
119             Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
120             user.identifier,
121             ICrossProfileService.Stub::asInterface,
122         )
123 
124     private suspend fun launchCrossProfileIntent(
125         user: UserHandle,
126         intent: Intent,
127         bundle: Bundle?
128     ) {
129         val connector = getCrossProfileConnector(user)
130         val completion = CompletableDeferred<Unit>()
131         connector.post {
132             it.launchIntent(intent, bundle)
133             completion.complete(Unit)
134         }
135         completion.await()
136     }
137 }
138 
139 private const val TAG: String = "ActionIntentExecutor"
140 private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
141 
142 /**
143  * This is effectively a no-op, but we need something non-null to pass in, in order to successfully
144  * override the pending activity entrance animation.
145  */
146 private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub =
147     object : IRemoteAnimationRunner.Stub() {
148         override fun onAnimationStart(
149             @WindowManager.TransitionOldType transit: Int,
150             apps: Array<RemoteAnimationTarget>,
151             wallpapers: Array<RemoteAnimationTarget>,
152             nonApps: Array<RemoteAnimationTarget>,
153             finishedCallback: IRemoteAnimationFinishedCallback,
154         ) {
155             try {
156                 finishedCallback.onAnimationFinished()
157             } catch (e: RemoteException) {
158                 Log.e(TAG, "Error finishing screenshot remote animation", e)
159             }
160         }
161 
162         override fun onAnimationCancelled() {}
163     }
164