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