blob: cccd3a482ef03b97a30e23ea17bf45af56885387 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.camera
import android.app.ActivityManager
import android.app.ActivityOptions
import android.app.IActivityTaskManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.PanelViewController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Helps with handling camera-related gestures (for example, double-tap the power button to launch
* the camera).
*/
class CameraGestureHelper @Inject constructor(
private val context: Context,
private val centralSurfaces: CentralSurfaces,
private val keyguardStateController: KeyguardStateController,
private val packageManager: PackageManager,
private val activityManager: ActivityManager,
private val activityStarter: ActivityStarter,
private val activityIntentHelper: ActivityIntentHelper,
private val activityTaskManager: IActivityTaskManager,
private val cameraIntents: CameraIntentsWrapper,
private val contentResolver: ContentResolver,
@Main private val uiExecutor: Executor,
) {
/**
* Whether the camera application can be launched for the camera launch gesture.
*/
fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
if (!centralSurfaces.isCameraAllowedByAdmin) {
return false
}
val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
getStartCameraIntent(),
PackageManager.MATCH_DEFAULT_ONLY,
KeyguardUpdateMonitor.getCurrentUser()
)
val resolvedPackage = resolveInfo?.activityInfo?.packageName
return (resolvedPackage != null &&
(statusBarState != StatusBarState.SHADE ||
!activityManager.isInForeground(resolvedPackage)))
}
/**
* Launches the camera.
*
* @param source The source of the camera launch, to be passed to the camera app via [Intent]
*/
fun launchCamera(source: Int) {
val intent: Intent = getStartCameraIntent()
intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser()
)
if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
uiExecutor.execute {
// Normally an activity will set its requested rotation animation on its window.
// However when launching an activity causes the orientation to change this is too
// late. In these cases, the default animation is used. This doesn't look good for
// the camera (as it rotates the camera contents out of sync with physical reality).
// Therefore, we ask the WindowManager to force the cross-fade animation if an
// orientation change happens to occur during the launch.
val activityOptions = ActivityOptions.makeBasic()
activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
activityOptions.rotationAnimationHint =
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
try {
activityTaskManager.startActivityAsUser(
null,
context.basePackageName,
context.attributionTag,
intent,
intent.resolveTypeIfNeeded(contentResolver),
null,
null,
0,
Intent.FLAG_ACTIVITY_NEW_TASK,
null,
activityOptions.toBundle(),
UserHandle.CURRENT.identifier,
)
} catch (e: RemoteException) {
Log.w(
PanelViewController.TAG,
"Unable to start camera activity",
e
)
}
}
} else {
// We need to delay starting the activity because ResolverActivity finishes itself if
// launched from behind the lock-screen.
activityStarter.startActivity(intent, false /* dismissShade */)
}
// Call this to make sure that the keyguard returns if the app that is being launched
// crashes after a timeout.
centralSurfaces.startLaunchTransitionTimeout()
// Call this to make sure the keyguard is ready to be dismissed once the next intent is
// handled by the OS (in our case it is the activity we started right above)
centralSurfaces.readyForKeyguardDone()
}
/**
* Returns an [Intent] that can be used to start the camera app such that it occludes the
* lock-screen, if needed.
*/
private fun getStartCameraIntent(): Intent {
val isLockScreenDismissible = keyguardStateController.canDismissLockScreen()
val isSecure = keyguardStateController.isMethodSecure
return if (isSecure && !isLockScreenDismissible) {
cameraIntents.getSecureCameraIntent()
} else {
cameraIntents.getInsecureCameraIntent()
}
}
companion object {
@VisibleForTesting
const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
}
}