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.server.display;
18 
19 import android.annotation.NonNull;
20 import android.os.Trace;
21 import android.util.Slog;
22 import android.view.Display;
23 import android.view.DisplayAddress;
24 import android.view.Surface;
25 
26 import com.android.internal.annotations.GuardedBy;
27 import com.android.server.display.DisplayManagerService.SyncRoot;
28 import com.android.server.display.utils.DebugUtils;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.function.Consumer;
33 
34 /**
35  * Container for all the display devices present in the system.  If an object wants to get events
36  * about all the DisplayDevices without needing to listen to all of the DisplayAdapters, they can
37  * listen and interact with the instance of this class.
38  * <p>
39  * The collection of {@link DisplayDevice}s and their usage is protected by the provided
40  * {@link DisplayManagerService.SyncRoot} lock object.
41  */
42 class DisplayDeviceRepository implements DisplayAdapter.Listener {
43     private static final String TAG = "DisplayDeviceRepository";
44 
45     // To enable these logs, run:
46     // 'adb shell setprop persist.log.tag.DisplayDeviceRepository DEBUG && adb reboot'
47     private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
48 
49     public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
50     public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
51 
52     /**
53      * List of all currently connected display devices. Indexed by the displayId.
54      * TODO: multi-display - break the notion that this is indexed by displayId.
55      */
56     @GuardedBy("mSyncRoot")
57     private final List<DisplayDevice> mDisplayDevices = new ArrayList<>();
58 
59     /** Listeners for {link DisplayDevice} events. */
60     @GuardedBy("mSyncRoot")
61     private final List<Listener> mListeners = new ArrayList<>();
62 
63     /** Global lock object from {@link DisplayManagerService}. */
64     private final SyncRoot mSyncRoot;
65 
66     private final PersistentDataStore mPersistentDataStore;
67 
68     /**
69      * @param syncRoot The global lock for DisplayManager related objects.
70      * @param persistentDataStore Settings data store from {@link DisplayManagerService}.
71      */
DisplayDeviceRepository(@onNull SyncRoot syncRoot, @NonNull PersistentDataStore persistentDataStore)72     DisplayDeviceRepository(@NonNull SyncRoot syncRoot,
73             @NonNull PersistentDataStore persistentDataStore) {
74         mSyncRoot = syncRoot;
75         mPersistentDataStore = persistentDataStore;
76     }
77 
addListener(@onNull Listener listener)78     public void addListener(@NonNull Listener listener) {
79         mListeners.add(listener);
80     }
81 
82     @Override
onDisplayDeviceEvent(DisplayDevice device, int event)83     public void onDisplayDeviceEvent(DisplayDevice device, int event) {
84         String tag = null;
85         if (DEBUG) {
86             tag = "DisplayDeviceRepository#onDisplayDeviceEvent (event=" + event + ")";
87             Trace.beginAsyncSection(tag, 0);
88         }
89         switch (event) {
90             case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
91                 handleDisplayDeviceAdded(device);
92                 break;
93 
94             case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
95                 handleDisplayDeviceChanged(device);
96                 break;
97 
98             case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
99                 handleDisplayDeviceRemoved(device);
100                 break;
101         }
102         if (DEBUG) {
103             Trace.endAsyncSection(tag, 0);
104         }
105     }
106 
107     @Override
onTraversalRequested()108     public void onTraversalRequested() {
109         final int size = mListeners.size();
110         for (int i = 0; i < size; i++) {
111             mListeners.get(i).onTraversalRequested();
112         }
113     }
114 
containsLocked(DisplayDevice d)115     public boolean containsLocked(DisplayDevice d) {
116         return mDisplayDevices.contains(d);
117     }
118 
sizeLocked()119     public int sizeLocked() {
120         return mDisplayDevices.size();
121     }
122 
forEachLocked(Consumer<DisplayDevice> consumer)123     public void forEachLocked(Consumer<DisplayDevice> consumer) {
124         final int count = mDisplayDevices.size();
125         for (int i = 0; i < count; i++) {
126             consumer.accept(mDisplayDevices.get(i));
127         }
128     }
129 
getByAddressLocked(@onNull DisplayAddress address)130     public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
131         for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
132             final DisplayDevice device = mDisplayDevices.get(i);
133             final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
134             if (address.equals(info.address)
135                     || DisplayAddress.Physical.isPortMatch(address, info.address)) {
136                 return device;
137             }
138         }
139         return null;
140     }
141 
142     // String uniqueId -> DisplayDevice object with that given uniqueId
getByUniqueIdLocked(@onNull String uniqueId)143     public DisplayDevice getByUniqueIdLocked(@NonNull String uniqueId) {
144         for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
145             final DisplayDevice displayDevice = mDisplayDevices.get(i);
146             if (displayDevice.getUniqueId().equals(uniqueId)) {
147                 return displayDevice;
148             }
149         }
150         return null;
151     }
152 
handleDisplayDeviceAdded(DisplayDevice device)153     private void handleDisplayDeviceAdded(DisplayDevice device) {
154         synchronized (mSyncRoot) {
155             DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
156             if (mDisplayDevices.contains(device)) {
157                 Slog.w(TAG, "Attempted to add already added display device: " + info);
158                 return;
159             }
160             Slog.i(TAG, "Display device added: " + info);
161             device.mDebugLastLoggedDeviceInfo = info;
162 
163             mDisplayDevices.add(device);
164             sendEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
165         }
166     }
167 
handleDisplayDeviceChanged(DisplayDevice device)168     private void handleDisplayDeviceChanged(DisplayDevice device) {
169         synchronized (mSyncRoot) {
170             final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
171             if (!mDisplayDevices.contains(device)) {
172                 Slog.w(TAG, "Attempted to change non-existent display device: " + info);
173                 return;
174             }
175             if (DEBUG) {
176                 Trace.traceBegin(Trace.TRACE_TAG_POWER,
177                         "handleDisplayDeviceChanged");
178             }
179             int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
180             if (diff == DisplayDeviceInfo.DIFF_STATE) {
181                 Slog.i(TAG, "Display device changed state: \"" + info.name
182                         + "\", " + Display.stateToString(info.state));
183             } else if (diff == DisplayDeviceInfo.DIFF_ROTATION) {
184                 Slog.i(TAG, "Display device rotated: \"" + info.name
185                         + "\", " + Surface.rotationToString(info.rotation));
186             } else if (diff
187                     == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) {
188                 Slog.i(TAG, "Display device changed render timings: \"" + info.name
189                         + "\", renderFrameRate=" + info.renderFrameRate
190                         + ", presentationDeadlineNanos=" + info.presentationDeadlineNanos
191                         + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos);
192             } else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) {
193                 if (DEBUG) {
194                     Slog.i(TAG, "Display device changed committed state: \"" + info.name
195                             + "\", " + Display.stateToString(info.committedState));
196                 }
197             } else if (diff != DisplayDeviceInfo.DIFF_HDR_SDR_RATIO) {
198                 Slog.i(TAG, "Display device changed: " + info);
199             }
200 
201             if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {
202                 try {
203                     mPersistentDataStore.setColorMode(device, info.colorMode);
204                 } finally {
205                     mPersistentDataStore.saveIfNeeded();
206                 }
207             }
208             device.mDebugLastLoggedDeviceInfo = info;
209 
210             device.applyPendingDisplayDeviceInfoChangesLocked();
211             sendChangedEventLocked(device, diff);
212             if (DEBUG) {
213                 Trace.traceEnd(Trace.TRACE_TAG_POWER);
214             }
215         }
216     }
217 
handleDisplayDeviceRemoved(DisplayDevice device)218     private void handleDisplayDeviceRemoved(DisplayDevice device) {
219         synchronized (mSyncRoot) {
220             DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
221             if (!mDisplayDevices.remove(device)) {
222                 Slog.w(TAG, "Attempted to remove non-existent display device: " + info);
223                 return;
224             }
225 
226             Slog.i(TAG, "Display device removed: " + info);
227             device.mDebugLastLoggedDeviceInfo = info;
228             sendEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
229         }
230     }
231 
sendEventLocked(DisplayDevice device, int event)232     private void sendEventLocked(DisplayDevice device, int event) {
233         final int size = mListeners.size();
234         for (int i = 0; i < size; i++) {
235             mListeners.get(i).onDisplayDeviceEventLocked(device, event);
236         }
237     }
238 
239     @GuardedBy("mSyncRoot")
sendChangedEventLocked(DisplayDevice device, int diff)240     private void sendChangedEventLocked(DisplayDevice device, int diff) {
241         final int size = mListeners.size();
242         for (int i = 0; i < size; i++) {
243             mListeners.get(i).onDisplayDeviceChangedLocked(device, diff);
244         }
245     }
246 
247     /**
248      * Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}.
249      */
250     public interface Listener {
onDisplayDeviceEventLocked(DisplayDevice device, int event)251         void onDisplayDeviceEventLocked(DisplayDevice device, int event);
252 
onDisplayDeviceChangedLocked(DisplayDevice device, int diff)253         void onDisplayDeviceChangedLocked(DisplayDevice device, int diff);
254 
255         // TODO: multi-display - Try to remove the need for requestTraversal...it feels like
256         // a shoe-horned method for a shoe-horned feature.
onTraversalRequested()257         void onTraversalRequested();
258     };
259 }
260