• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.launchTraced as 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.ScreenshotProxy
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 screenshotProxy: ScreenshotProxy,
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         screenshotProxy.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             intent.collectExtraIntentKeys()
136             it.launchIntent(intent, bundle)
137             completion.complete(Unit)
138         }
139         completion.await()
140     }
141 }
142 
143 private const val TAG: String = "ActionIntentExecutor"
144 private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
145 
146 /**
147  * This is effectively a no-op, but we need something non-null to pass in, in order to successfully
148  * override the pending activity entrance animation.
149  */
150 private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub =
151     object : IRemoteAnimationRunner.Stub() {
onAnimationStartnull152         override fun onAnimationStart(
153             @WindowManager.TransitionOldType transit: Int,
154             apps: Array<RemoteAnimationTarget>,
155             wallpapers: Array<RemoteAnimationTarget>,
156             nonApps: Array<RemoteAnimationTarget>,
157             finishedCallback: IRemoteAnimationFinishedCallback,
158         ) {
159             try {
160                 finishedCallback.onAnimationFinished()
161             } catch (e: RemoteException) {
162                 Log.e(TAG, "Error finishing screenshot remote animation", e)
163             }
164         }
165 
onAnimationCancellednull166         override fun onAnimationCancelled() {}
167     }
168