1 /*
2  * Copyright (C) 2023 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 package com.android.dream.lowlight
17 
18 import android.Manifest
19 import android.annotation.IntDef
20 import android.annotation.RequiresPermission
21 import android.app.DreamManager
22 import android.content.ComponentName
23 import android.util.Log
24 import com.android.dream.lowlight.dagger.LowLightDreamModule
25 import com.android.dream.lowlight.dagger.qualifiers.Application
26 import kotlinx.coroutines.CancellationException
27 import kotlinx.coroutines.CoroutineScope
28 import kotlinx.coroutines.Job
29 import kotlinx.coroutines.TimeoutCancellationException
30 import kotlinx.coroutines.launch
31 import javax.inject.Inject
32 import javax.inject.Named
33 import kotlin.time.DurationUnit
34 import kotlin.time.toDuration
35 
36 /**
37  * Maintains the ambient light mode of the environment the device is in, and sets a low light dream
38  * component, if present, as the system dream when the ambient light mode is low light.
39  *
40  * @hide
41  */
42 class LowLightDreamManager @Inject constructor(
43     @Application private val coroutineScope: CoroutineScope,
44     private val dreamManager: DreamManager,
45     private val lowLightTransitionCoordinator: LowLightTransitionCoordinator,
46     @param:Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
47     private val lowLightDreamComponent: ComponentName?,
48     @param:Named(LowLightDreamModule.LOW_LIGHT_TRANSITION_TIMEOUT_MS)
49     private val lowLightTransitionTimeoutMs: Long
50 ) {
51     /**
52      * @hide
53      */
54     @Retention(AnnotationRetention.SOURCE)
55     @IntDef(
56         prefix = ["AMBIENT_LIGHT_MODE_"],
57         value = [
58             AMBIENT_LIGHT_MODE_UNKNOWN,
59             AMBIENT_LIGHT_MODE_REGULAR,
60             AMBIENT_LIGHT_MODE_LOW_LIGHT
61         ]
62     )
63     annotation class AmbientLightMode
64 
65     private var mTransitionJob: Job? = null
66     private var mAmbientLightMode = AMBIENT_LIGHT_MODE_UNKNOWN
67     private val mLowLightTransitionTimeout =
68         lowLightTransitionTimeoutMs.toDuration(DurationUnit.MILLISECONDS)
69 
70     /**
71      * Sets the current ambient light mode.
72      *
73      * @hide
74      */
75     @RequiresPermission(Manifest.permission.WRITE_DREAM_STATE)
setAmbientLightModenull76     fun setAmbientLightMode(@AmbientLightMode ambientLightMode: Int) {
77         if (lowLightDreamComponent == null) {
78             if (DEBUG) {
79                 Log.d(
80                     TAG,
81                     "ignore ambient light mode change because low light dream component is empty"
82                 )
83             }
84             return
85         }
86         if (mAmbientLightMode == ambientLightMode) {
87             return
88         }
89         if (DEBUG) {
90             Log.d(
91                 TAG, "ambient light mode changed from $mAmbientLightMode to $ambientLightMode"
92             )
93         }
94         mAmbientLightMode = ambientLightMode
95         val shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
96 
97         // Cancel any previous transitions
98         mTransitionJob?.cancel()
99         mTransitionJob = coroutineScope.launch {
100             try {
101                 lowLightTransitionCoordinator.waitForLowLightTransitionAnimation(
102                     timeout = mLowLightTransitionTimeout,
103                     entering = shouldEnterLowLight
104                 )
105             } catch (ex: TimeoutCancellationException) {
106                 Log.e(TAG, "timed out while waiting for low light animation", ex)
107             } catch (ex: CancellationException) {
108                 Log.w(TAG, "low light transition animation cancelled")
109                 // Catch the cancellation so that we still set the system dream component if the
110                 // animation is cancelled, such as by a user tapping to wake as the transition to
111                 // low light happens.
112             }
113             dreamManager.setSystemDreamComponent(
114                 if (shouldEnterLowLight) lowLightDreamComponent else null
115             )
116         }
117     }
118 
119     companion object {
120         private const val TAG = "LowLightDreamManager"
121         private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
122 
123         /**
124          * Constant for ambient light mode being unknown.
125          *
126          * @hide
127          */
128         const val AMBIENT_LIGHT_MODE_UNKNOWN = 0
129 
130         /**
131          * Constant for ambient light mode being regular / bright.
132          *
133          * @hide
134          */
135         const val AMBIENT_LIGHT_MODE_REGULAR = 1
136 
137         /**
138          * Constant for ambient light mode being low light / dim.
139          *
140          * @hide
141          */
142         const val AMBIENT_LIGHT_MODE_LOW_LIGHT = 2
143     }
144 }
145