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