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.settings
18 
19 import android.hardware.display.DisplayManager
20 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
21 import android.os.Handler
22 import android.view.Display
23 import androidx.annotation.GuardedBy
24 import androidx.annotation.VisibleForTesting
25 import androidx.annotation.WorkerThread
26 import com.android.app.tracing.traceSection
27 import com.android.systemui.dagger.qualifiers.Background
28 import com.android.systemui.util.Assert
29 import java.lang.ref.WeakReference
30 import java.util.concurrent.Executor
31 
32 class DisplayTrackerImpl
33 internal constructor(
34     val displayManager: DisplayManager,
35     @Background val backgroundHandler: Handler
36 ) : DisplayTracker {
37     override val defaultDisplayId: Int = Display.DEFAULT_DISPLAY
38     override val allDisplays: Array<Display>
39         get() = displayManager.displays
40 
41     @GuardedBy("displayCallbacks")
42     private val displayCallbacks: MutableList<DisplayTrackerDataItem> = ArrayList()
43     @GuardedBy("brightnessCallbacks")
44     private val brightnessCallbacks: MutableList<DisplayTrackerDataItem> = ArrayList()
45 
46     @VisibleForTesting
47     val displayChangedListener: DisplayManager.DisplayListener =
48         object : DisplayManager.DisplayListener {
onDisplayAddednull49             override fun onDisplayAdded(displayId: Int) {
50                 traceSection(
51                     "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded",
52                 ) {
53                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
54                     onDisplayAdded(displayId, list)
55                 }
56             }
57 
onDisplayRemovednull58             override fun onDisplayRemoved(displayId: Int) {
59                 traceSection(
60                     "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved",
61                 ) {
62                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
63                     onDisplayRemoved(displayId, list)
64                 }
65             }
66 
onDisplayChangednull67             override fun onDisplayChanged(displayId: Int) {
68                 traceSection(
69                     "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged",
70                 ) {
71                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
72                     onDisplayChanged(displayId, list)
73                 }
74             }
75         }
76 
77     @VisibleForTesting
78     val displayBrightnessChangedListener: DisplayManager.DisplayListener =
79         object : DisplayManager.DisplayListener {
onDisplayAddednull80             override fun onDisplayAdded(displayId: Int) {}
81 
onDisplayRemovednull82             override fun onDisplayRemoved(displayId: Int) {}
83 
onDisplayChangednull84             override fun onDisplayChanged(displayId: Int) {
85                 traceSection(
86                     "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged",
87                 ) {
88                     val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() }
89                     onDisplayChanged(displayId, list)
90                 }
91             }
92         }
93 
addDisplayChangeCallbacknull94     override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) {
95         synchronized(displayCallbacks) {
96             if (displayCallbacks.isEmpty()) {
97                 displayManager.registerDisplayListener(displayChangedListener, backgroundHandler)
98             }
99             displayCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor))
100         }
101     }
102 
addBrightnessChangeCallbacknull103     override fun addBrightnessChangeCallback(
104         callback: DisplayTracker.Callback,
105         executor: Executor
106     ) {
107         synchronized(brightnessCallbacks) {
108             if (brightnessCallbacks.isEmpty()) {
109                 displayManager.registerDisplayListener(
110                     displayBrightnessChangedListener,
111                     backgroundHandler,
112                     EVENT_FLAG_DISPLAY_BRIGHTNESS
113                 )
114             }
115             brightnessCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor))
116         }
117     }
118 
removeCallbacknull119     override fun removeCallback(callback: DisplayTracker.Callback) {
120         synchronized(displayCallbacks) {
121             val changed = displayCallbacks.removeIf { it.sameOrEmpty(callback) }
122             if (changed && displayCallbacks.isEmpty()) {
123                 displayManager.unregisterDisplayListener(displayChangedListener)
124             }
125         }
126 
127         synchronized(brightnessCallbacks) {
128             val changed = brightnessCallbacks.removeIf { it.sameOrEmpty(callback) }
129             if (changed && brightnessCallbacks.isEmpty()) {
130                 displayManager.unregisterDisplayListener(displayBrightnessChangedListener)
131             }
132         }
133     }
134 
getDisplaynull135     override fun getDisplay(displayId: Int): Display {
136         return displayManager.getDisplay(displayId)
137     }
138 
139     @WorkerThread
onDisplayAddednull140     private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) {
141         Assert.isNotMainThread()
142 
143         notifySubscribers({ onDisplayAdded(displayId) }, list)
144     }
145 
146     @WorkerThread
onDisplayRemovednull147     private fun onDisplayRemoved(displayId: Int, list: List<DisplayTrackerDataItem>) {
148         Assert.isNotMainThread()
149 
150         notifySubscribers({ onDisplayRemoved(displayId) }, list)
151     }
152 
153     @WorkerThread
onDisplayChangednull154     private fun onDisplayChanged(displayId: Int, list: List<DisplayTrackerDataItem>) {
155         Assert.isNotMainThread()
156 
157         notifySubscribers({ onDisplayChanged(displayId) }, list)
158     }
159 
notifySubscribersnull160     private inline fun notifySubscribers(
161         crossinline action: DisplayTracker.Callback.() -> Unit,
162         list: List<DisplayTrackerDataItem>
163     ) {
164         list.forEach {
165             if (it.callback.get() != null) {
166                 it.executor.execute { it.callback.get()?.action() }
167             }
168         }
169     }
170 
171     private data class DisplayTrackerDataItem(
172         val callback: WeakReference<DisplayTracker.Callback>,
173         val executor: Executor
174     ) {
sameOrEmptynull175         fun sameOrEmpty(other: DisplayTracker.Callback): Boolean {
176             return callback.get()?.equals(other) ?: true
177         }
178     }
179 }
180