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