1 /* 2 * Copyright (C) 2021 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.camera 18 19 import android.app.ActivityManager 20 import android.app.ActivityOptions 21 import android.app.IActivityTaskManager 22 import android.app.admin.DevicePolicyManager 23 import android.content.ContentResolver 24 import android.content.Context 25 import android.content.Intent 26 import android.content.pm.PackageManager 27 import android.content.pm.ResolveInfo 28 import android.os.RemoteException 29 import android.util.Log 30 import android.view.WindowManager 31 import com.android.systemui.ActivityIntentHelper 32 import com.android.systemui.dagger.SysUISingleton 33 import com.android.systemui.dagger.qualifiers.Main 34 import com.android.systemui.plugins.ActivityStarter 35 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground 36 import com.android.systemui.statusbar.NotificationLockscreenUserManager 37 import com.android.systemui.statusbar.StatusBarState 38 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 39 import com.android.systemui.statusbar.policy.KeyguardStateController 40 import com.android.systemui.user.domain.interactor.SelectedUserInteractor 41 import java.util.concurrent.Executor 42 import javax.inject.Inject 43 44 /** 45 * Helps with handling camera-related gestures (for example, double-tap the power button to launch 46 * the camera). 47 */ 48 @SysUISingleton 49 class CameraGestureHelper 50 @Inject 51 constructor( 52 private val context: Context, 53 private val keyguardStateController: KeyguardStateController, 54 private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, 55 private val packageManager: PackageManager, 56 private val activityManager: ActivityManager, 57 private val activityStarter: ActivityStarter, 58 private val activityIntentHelper: ActivityIntentHelper, 59 private val activityTaskManager: IActivityTaskManager, 60 private val cameraIntents: CameraIntentsWrapper, 61 private val contentResolver: ContentResolver, 62 @Main private val uiExecutor: Executor, 63 private val selectedUserInteractor: SelectedUserInteractor, 64 private val devicePolicyManager: DevicePolicyManager, 65 private val lockscreenUserManager: NotificationLockscreenUserManager, 66 ) { 67 /** Whether the camera application can be launched for the camera launch gesture. */ canCameraGestureBeLaunchednull68 fun canCameraGestureBeLaunched(statusBarState: Int): Boolean { 69 if (!isCameraAllowedByAdmin()) { 70 return false 71 } 72 73 val resolveInfo: ResolveInfo? = 74 packageManager.resolveActivityAsUser( 75 getStartCameraIntent(selectedUserInteractor.getSelectedUserId()), 76 PackageManager.MATCH_DEFAULT_ONLY, 77 selectedUserInteractor.getSelectedUserId() 78 ) 79 val resolvedPackage = resolveInfo?.activityInfo?.packageName 80 return (resolvedPackage != null && 81 (statusBarState != StatusBarState.SHADE || 82 !activityManager.isInForeground(resolvedPackage))) 83 } 84 85 /** 86 * Launches the camera. 87 * 88 * @param source The source of the camera launch, to be passed to the camera app via [Intent] 89 */ launchCameranull90 fun launchCamera(source: Int) { 91 val intent: Intent = getStartCameraIntent(selectedUserInteractor.getSelectedUserId()) 92 intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source) 93 val wouldLaunchResolverActivity = 94 activityIntentHelper.wouldLaunchResolverActivity( 95 intent, 96 selectedUserInteractor.getSelectedUserId() 97 ) 98 if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) { 99 uiExecutor.execute { 100 // Normally an activity will set its requested rotation animation on its window. 101 // However when launching an activity causes the orientation to change this is too 102 // late. In these cases, the default animation is used. This doesn't look good for 103 // the camera (as it rotates the camera contents out of sync with physical reality). 104 // Therefore, we ask the WindowManager to force the cross-fade animation if an 105 // orientation change happens to occur during the launch. 106 val activityOptions = ActivityOptions.makeBasic() 107 activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true) 108 activityOptions.rotationAnimationHint = 109 WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS 110 intent.collectExtraIntentKeys() 111 try { 112 activityTaskManager.startActivityAsUser( 113 null, 114 context.basePackageName, 115 context.attributionTag, 116 intent, 117 intent.resolveTypeIfNeeded(contentResolver), 118 null, 119 null, 120 0, 121 Intent.FLAG_ACTIVITY_NEW_TASK, 122 null, 123 activityOptions.toBundle(), 124 selectedUserInteractor.getSelectedUserId(), 125 ) 126 } catch (e: RemoteException) { 127 Log.w("CameraGestureHelper", "Unable to start camera activity", e) 128 } 129 } 130 } else { 131 // We need to delay starting the activity because ResolverActivity finishes itself if 132 // launched from behind the lock-screen. 133 activityStarter.startActivity(intent, false /* dismissShade */) 134 } 135 136 // Call this to make sure the keyguard is ready to be dismissed once the next intent is 137 // handled by the OS (in our case it is the activity we started right above) 138 statusBarKeyguardViewManager.readyForKeyguardDone() 139 } 140 141 /** 142 * Returns an [Intent] that can be used to start the camera app such that it occludes the 143 * lock-screen, if needed. 144 */ getStartCameraIntentnull145 private fun getStartCameraIntent(userId: Int): Intent { 146 val isLockScreenDismissible = keyguardStateController.canDismissLockScreen() 147 val isSecure = keyguardStateController.isMethodSecure 148 return if (isSecure && !isLockScreenDismissible) { 149 cameraIntents.getSecureCameraIntent(userId) 150 } else { 151 cameraIntents.getInsecureCameraIntent(userId) 152 } 153 } 154 isCameraAllowedByAdminnull155 private fun isCameraAllowedByAdmin(): Boolean { 156 if (devicePolicyManager.getCameraDisabled(null, lockscreenUserManager.getCurrentUserId())) { 157 return false 158 } else if (keyguardStateController.isShowing() && statusBarKeyguardViewManager.isSecure()) { 159 // Check if the admin has disabled the camera specifically for the keyguard 160 return (devicePolicyManager.getKeyguardDisabledFeatures( 161 null, 162 lockscreenUserManager.getCurrentUserId() 163 ) and DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0 164 } 165 return true 166 } 167 } 168