1 /* 2 * Copyright (C) 2020 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.keyguard 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.ValueAnimator 22 import android.content.res.Resources 23 import android.database.ContentObserver 24 import android.graphics.Bitmap 25 import android.graphics.BitmapFactory 26 import android.graphics.Color 27 import android.graphics.drawable.ColorDrawable 28 import android.hardware.biometrics.BiometricSourceType 29 import android.os.Handler 30 import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT 31 import android.util.MathUtils 32 import android.view.View 33 import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE 34 import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 35 import com.android.internal.annotations.VisibleForTesting 36 import com.android.keyguard.KeyguardUpdateMonitor 37 import com.android.keyguard.KeyguardUpdateMonitorCallback 38 import com.android.systemui.Dumpable 39 import com.android.systemui.R 40 import com.android.systemui.dagger.SysUISingleton 41 import com.android.systemui.dump.DumpManager 42 import com.android.systemui.statusbar.NotificationShadeWindowController 43 import com.android.systemui.util.settings.GlobalSettings 44 import com.android.systemui.util.settings.SystemSettings 45 import java.io.FileDescriptor 46 import java.io.PrintWriter 47 import java.lang.Float.max 48 import java.util.concurrent.TimeUnit 49 50 val DEFAULT_ANIMATION_DURATION = TimeUnit.SECONDS.toMillis(4) 51 val MAX_SCREEN_BRIGHTNESS = 100 // 0..100 52 val MAX_SCRIM_OPACTY = 50 // 0..100 53 val DEFAULT_USE_FACE_WALLPAPER = false 54 55 /** 56 * This class is responsible for ramping up the display brightness (and white overlay) in order 57 * to mitigate low light conditions when running face auth without an IR camera. 58 */ 59 @SysUISingleton 60 open class FaceAuthScreenBrightnessController( 61 private val notificationShadeWindowController: NotificationShadeWindowController, 62 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 63 private val resources: Resources, 64 private val globalSettings: GlobalSettings, 65 private val systemSettings: SystemSettings, 66 private val mainHandler: Handler, 67 private val dumpManager: DumpManager, 68 private val enabled: Boolean 69 ) : Dumpable { 70 71 private var userDefinedBrightness: Float = 1f 72 @VisibleForTesting 73 var useFaceAuthWallpaper = globalSettings 74 .getInt("sysui.use_face_auth_wallpaper", if (DEFAULT_USE_FACE_WALLPAPER) 1 else 0) == 1 75 private val brightnessAnimationDuration = globalSettings 76 .getLong("sysui.face_brightness_anim_duration", DEFAULT_ANIMATION_DURATION) 77 private val maxScreenBrightness = globalSettings 78 .getInt("sysui.face_max_brightness", MAX_SCREEN_BRIGHTNESS) / 100f 79 private val maxScrimOpacity = globalSettings 80 .getInt("sysui.face_max_scrim_opacity", MAX_SCRIM_OPACTY) / 100f 81 private val keyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() { onBiometricRunningStateChangednull82 override fun onBiometricRunningStateChanged( 83 running: Boolean, 84 biometricSourceType: BiometricSourceType? 85 ) { 86 if (biometricSourceType != BiometricSourceType.FACE) { 87 return 88 } 89 // TODO enable only when receiving a low-light error 90 overridingBrightness = if (enabled) running else false 91 } 92 } 93 private lateinit var whiteOverlay: View 94 private var brightnessAnimator: ValueAnimator? = null 95 private var overridingBrightness = false 96 set(value) { 97 if (field == value) { 98 return 99 } 100 field = value 101 brightnessAnimator?.cancel() 102 103 if (!value) { 104 notificationShadeWindowController.setFaceAuthDisplayBrightness(BRIGHTNESS_OVERRIDE_NONE) 105 if (whiteOverlay.alpha > 0) { <lambda>null106 brightnessAnimator = createAnimator(whiteOverlay.alpha, 0f).apply { 107 duration = 200 108 addUpdateListener { 109 whiteOverlay.alpha = it.animatedValue as Float 110 } 111 addListener(object : AnimatorListenerAdapter() { 112 override fun onAnimationEnd(animation: Animator?) { 113 whiteOverlay.visibility = View.INVISIBLE 114 brightnessAnimator = null 115 } 116 }) 117 start() 118 } 119 } 120 return 121 } 122 123 val targetBrightness = max(maxScreenBrightness, userDefinedBrightness) 124 whiteOverlay.visibility = View.VISIBLE <lambda>null125 brightnessAnimator = createAnimator(0f, 1f).apply { 126 duration = brightnessAnimationDuration 127 addUpdateListener { 128 val progress = it.animatedValue as Float 129 val brightnessProgress = MathUtils.constrainedMap( 130 userDefinedBrightness, targetBrightness, 0f, 0.5f, progress) 131 val scrimProgress = MathUtils.constrainedMap( 132 0f, maxScrimOpacity, 0.5f, 1f, progress) 133 notificationShadeWindowController.setFaceAuthDisplayBrightness(brightnessProgress) 134 whiteOverlay.alpha = scrimProgress 135 } 136 addListener(object : AnimatorListenerAdapter() { 137 override fun onAnimationEnd(animation: Animator?) { 138 brightnessAnimator = null 139 } 140 }) 141 start() 142 } 143 } 144 145 @VisibleForTesting createAnimatornull146 open fun createAnimator(start: Float, end: Float) = ValueAnimator.ofFloat(start, end) 147 148 /** 149 * Returns a bitmap that should be used by the lock screen as a wallpaper, if face auth requires 150 * a secure wallpaper. 151 */ 152 var faceAuthWallpaper: Bitmap? = null 153 get() { 154 val user = KeyguardUpdateMonitor.getCurrentUser() 155 if (useFaceAuthWallpaper && keyguardUpdateMonitor.isFaceAuthEnabledForUser(user)) { 156 val options = BitmapFactory.Options().apply { 157 inScaled = false 158 } 159 return BitmapFactory.decodeResource(resources, R.drawable.face_auth_wallpaper, options) 160 } 161 return null 162 } 163 private set 164 attachnull165 fun attach(overlayView: View) { 166 whiteOverlay = overlayView 167 whiteOverlay.focusable = FLAG_NOT_FOCUSABLE 168 whiteOverlay.background = ColorDrawable(Color.WHITE) 169 whiteOverlay.isEnabled = false 170 whiteOverlay.alpha = 0f 171 whiteOverlay.visibility = View.INVISIBLE 172 173 dumpManager.registerDumpable(this.javaClass.name, this) 174 keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback) 175 systemSettings.registerContentObserver(SCREEN_BRIGHTNESS_FLOAT, 176 object : ContentObserver(mainHandler) { 177 override fun onChange(selfChange: Boolean) { 178 userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT) 179 } 180 }) 181 userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT, 1f) 182 } 183 dumpnull184 override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { 185 pw.apply { 186 println("overridingBrightness: $overridingBrightness") 187 println("useFaceAuthWallpaper: $useFaceAuthWallpaper") 188 println("brightnessAnimator: $brightnessAnimator") 189 println("brightnessAnimationDuration: $brightnessAnimationDuration") 190 println("maxScreenBrightness: $maxScreenBrightness") 191 println("userDefinedBrightness: $userDefinedBrightness") 192 println("enabled: $enabled") 193 } 194 } 195 }