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