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