1 /*
2  * Copyright (C) 2012 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 android.hardware.display;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.content.pm.ParceledListSlice;
24 import android.content.res.Resources;
25 import android.graphics.ColorSpace;
26 import android.graphics.Point;
27 import android.hardware.display.DisplayManager.DisplayListener;
28 import android.media.projection.IMediaProjection;
29 import android.media.projection.MediaProjection;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.util.Pair;
39 import android.util.SparseArray;
40 import android.view.Display;
41 import android.view.DisplayAdjustments;
42 import android.view.DisplayInfo;
43 import android.view.Surface;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 
49 /**
50  * Manager communication with the display manager service on behalf of
51  * an application process.  You're probably looking for {@link DisplayManager}.
52  *
53  * @hide
54  */
55 public final class DisplayManagerGlobal {
56     private static final String TAG = "DisplayManager";
57     private static final boolean DEBUG = false;
58 
59     // True if display info and display ids should be cached.
60     //
61     // FIXME: The cache is currently disabled because it's unclear whether we have the
62     // necessary guarantees that the caches will always be flushed before clients
63     // attempt to observe their new state.  For example, depending on the order
64     // in which the binder transactions take place, we might have a problem where
65     // an application could start processing a configuration change due to a display
66     // orientation change before the display info cache has actually been invalidated.
67     private static final boolean USE_CACHE = false;
68 
69     public static final int EVENT_DISPLAY_ADDED = 1;
70     public static final int EVENT_DISPLAY_CHANGED = 2;
71     public static final int EVENT_DISPLAY_REMOVED = 3;
72 
73     @UnsupportedAppUsage
74     private static DisplayManagerGlobal sInstance;
75 
76     private final Object mLock = new Object();
77 
78     @UnsupportedAppUsage
79     private final IDisplayManager mDm;
80 
81     private DisplayManagerCallback mCallback;
82     private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
83             new ArrayList<DisplayListenerDelegate>();
84 
85     private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
86     private final ColorSpace mWideColorSpace;
87     private int[] mDisplayIdCache;
88 
89     private int mWifiDisplayScanNestCount;
90 
DisplayManagerGlobal(IDisplayManager dm)91     private DisplayManagerGlobal(IDisplayManager dm) {
92         mDm = dm;
93         try {
94             mWideColorSpace =
95                     ColorSpace.get(
96                             ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]);
97         } catch (RemoteException ex) {
98             throw ex.rethrowFromSystemServer();
99         }
100     }
101 
102     /**
103      * Gets an instance of the display manager global singleton.
104      *
105      * @return The display manager instance, may be null early in system startup
106      * before the display manager has been fully initialized.
107      */
108     @UnsupportedAppUsage
getInstance()109     public static DisplayManagerGlobal getInstance() {
110         synchronized (DisplayManagerGlobal.class) {
111             if (sInstance == null) {
112                 IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
113                 if (b != null) {
114                     sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
115                 }
116             }
117             return sInstance;
118         }
119     }
120 
121     /**
122      * Get information about a particular logical display.
123      *
124      * @param displayId The logical display id.
125      * @return Information about the specified display, or null if it does not exist.
126      * This object belongs to an internal cache and should be treated as if it were immutable.
127      */
128     @UnsupportedAppUsage
getDisplayInfo(int displayId)129     public DisplayInfo getDisplayInfo(int displayId) {
130         try {
131             synchronized (mLock) {
132                 DisplayInfo info;
133                 if (USE_CACHE) {
134                     info = mDisplayInfoCache.get(displayId);
135                     if (info != null) {
136                         return info;
137                     }
138                 }
139 
140                 info = mDm.getDisplayInfo(displayId);
141                 if (info == null) {
142                     return null;
143                 }
144 
145                 if (USE_CACHE) {
146                     mDisplayInfoCache.put(displayId, info);
147                 }
148                 registerCallbackIfNeededLocked();
149 
150                 if (DEBUG) {
151                     Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
152                 }
153                 return info;
154             }
155         } catch (RemoteException ex) {
156             throw ex.rethrowFromSystemServer();
157         }
158     }
159 
160     /**
161      * Gets all currently valid logical display ids.
162      *
163      * @return An array containing all display ids.
164      */
165     @UnsupportedAppUsage
getDisplayIds()166     public int[] getDisplayIds() {
167         try {
168             synchronized (mLock) {
169                 if (USE_CACHE) {
170                     if (mDisplayIdCache != null) {
171                         return mDisplayIdCache;
172                     }
173                 }
174 
175                 int[] displayIds = mDm.getDisplayIds();
176                 if (USE_CACHE) {
177                     mDisplayIdCache = displayIds;
178                 }
179                 registerCallbackIfNeededLocked();
180                 return displayIds;
181             }
182         } catch (RemoteException ex) {
183             throw ex.rethrowFromSystemServer();
184         }
185     }
186 
187     /**
188      * Check if specified UID's content is present on display and should be granted access to it.
189      *
190      * @param uid UID to be checked.
191      * @param displayId id of the display where presence of the content is checked.
192      * @return {@code true} if UID is present on display, {@code false} otherwise.
193      */
isUidPresentOnDisplay(int uid, int displayId)194     public boolean isUidPresentOnDisplay(int uid, int displayId) {
195         try {
196             return mDm.isUidPresentOnDisplay(uid, displayId);
197         } catch (RemoteException ex) {
198             throw ex.rethrowFromSystemServer();
199         }
200     }
201 
202     /**
203      * Gets information about a logical display.
204      *
205      * The display metrics may be adjusted to provide compatibility
206      * for legacy applications or limited screen areas.
207      *
208      * @param displayId The logical display id.
209      * @param daj The compatibility info and activityToken.
210      * @return The display object, or null if there is no display with the given id.
211      */
getCompatibleDisplay(int displayId, DisplayAdjustments daj)212     public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) {
213         DisplayInfo displayInfo = getDisplayInfo(displayId);
214         if (displayInfo == null) {
215             return null;
216         }
217         return new Display(this, displayId, displayInfo, daj);
218     }
219 
220     /**
221      * Gets information about a logical display.
222      *
223      * The display metrics may be adjusted to provide compatibility
224      * for legacy applications or limited screen areas.
225      *
226      * @param displayId The logical display id.
227      * @param resources Resources providing compatibility info.
228      * @return The display object, or null if there is no display with the given id.
229      */
getCompatibleDisplay(int displayId, Resources resources)230     public Display getCompatibleDisplay(int displayId, Resources resources) {
231         DisplayInfo displayInfo = getDisplayInfo(displayId);
232         if (displayInfo == null) {
233             return null;
234         }
235         return new Display(this, displayId, displayInfo, resources);
236     }
237 
238     /**
239      * Gets information about a logical display without applying any compatibility metrics.
240      *
241      * @param displayId The logical display id.
242      * @return The display object, or null if there is no display with the given id.
243      */
244     @UnsupportedAppUsage
getRealDisplay(int displayId)245     public Display getRealDisplay(int displayId) {
246         return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
247     }
248 
249     /**
250      * Register a listener for display-related changes.
251      *
252      * @param listener The listener that will be called when display changes occur.
253      * @param handler Handler for the thread that will be receiving the callbacks. May be null.
254      * If null, listener will use the handler for the current thread, and if still null,
255      * the handler for the main thread.
256      * If that is still null, a runtime exception will be thrown.
257      */
registerDisplayListener(@onNull DisplayListener listener, @Nullable Handler handler)258     public void registerDisplayListener(@NonNull DisplayListener listener,
259             @Nullable Handler handler) {
260         if (listener == null) {
261             throw new IllegalArgumentException("listener must not be null");
262         }
263 
264         synchronized (mLock) {
265             int index = findDisplayListenerLocked(listener);
266             if (index < 0) {
267                 Looper looper = getLooperForHandler(handler);
268                 mDisplayListeners.add(new DisplayListenerDelegate(listener, looper));
269                 registerCallbackIfNeededLocked();
270             }
271         }
272     }
273 
unregisterDisplayListener(DisplayListener listener)274     public void unregisterDisplayListener(DisplayListener listener) {
275         if (listener == null) {
276             throw new IllegalArgumentException("listener must not be null");
277         }
278 
279         synchronized (mLock) {
280             int index = findDisplayListenerLocked(listener);
281             if (index >= 0) {
282                 DisplayListenerDelegate d = mDisplayListeners.get(index);
283                 d.clearEvents();
284                 mDisplayListeners.remove(index);
285             }
286         }
287     }
288 
getLooperForHandler(@ullable Handler handler)289     private static Looper getLooperForHandler(@Nullable Handler handler) {
290         Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
291         if (looper == null) {
292             looper = Looper.getMainLooper();
293         }
294         if (looper == null) {
295             throw new RuntimeException("Could not get Looper for the UI thread.");
296         }
297         return looper;
298     }
299 
findDisplayListenerLocked(DisplayListener listener)300     private int findDisplayListenerLocked(DisplayListener listener) {
301         final int numListeners = mDisplayListeners.size();
302         for (int i = 0; i < numListeners; i++) {
303             if (mDisplayListeners.get(i).mListener == listener) {
304                 return i;
305             }
306         }
307         return -1;
308     }
309 
registerCallbackIfNeededLocked()310     private void registerCallbackIfNeededLocked() {
311         if (mCallback == null) {
312             mCallback = new DisplayManagerCallback();
313             try {
314                 mDm.registerCallback(mCallback);
315             } catch (RemoteException ex) {
316                 throw ex.rethrowFromSystemServer();
317             }
318         }
319     }
320 
handleDisplayEvent(int displayId, int event)321     private void handleDisplayEvent(int displayId, int event) {
322         synchronized (mLock) {
323             if (USE_CACHE) {
324                 mDisplayInfoCache.remove(displayId);
325 
326                 if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
327                     mDisplayIdCache = null;
328                 }
329             }
330 
331             final int numListeners = mDisplayListeners.size();
332             for (int i = 0; i < numListeners; i++) {
333                 mDisplayListeners.get(i).sendDisplayEvent(displayId, event);
334             }
335         }
336     }
337 
startWifiDisplayScan()338     public void startWifiDisplayScan() {
339         synchronized (mLock) {
340             if (mWifiDisplayScanNestCount++ == 0) {
341                 registerCallbackIfNeededLocked();
342                 try {
343                     mDm.startWifiDisplayScan();
344                 } catch (RemoteException ex) {
345                     throw ex.rethrowFromSystemServer();
346                 }
347             }
348         }
349     }
350 
stopWifiDisplayScan()351     public void stopWifiDisplayScan() {
352         synchronized (mLock) {
353             if (--mWifiDisplayScanNestCount == 0) {
354                 try {
355                     mDm.stopWifiDisplayScan();
356                 } catch (RemoteException ex) {
357                     throw ex.rethrowFromSystemServer();
358                 }
359             } else if (mWifiDisplayScanNestCount < 0) {
360                 Log.wtf(TAG, "Wifi display scan nest count became negative: "
361                         + mWifiDisplayScanNestCount);
362                 mWifiDisplayScanNestCount = 0;
363             }
364         }
365     }
366 
connectWifiDisplay(String deviceAddress)367     public void connectWifiDisplay(String deviceAddress) {
368         if (deviceAddress == null) {
369             throw new IllegalArgumentException("deviceAddress must not be null");
370         }
371 
372         try {
373             mDm.connectWifiDisplay(deviceAddress);
374         } catch (RemoteException ex) {
375             throw ex.rethrowFromSystemServer();
376         }
377     }
378 
pauseWifiDisplay()379     public void pauseWifiDisplay() {
380         try {
381             mDm.pauseWifiDisplay();
382         } catch (RemoteException ex) {
383             throw ex.rethrowFromSystemServer();
384         }
385     }
386 
resumeWifiDisplay()387     public void resumeWifiDisplay() {
388         try {
389             mDm.resumeWifiDisplay();
390         } catch (RemoteException ex) {
391             throw ex.rethrowFromSystemServer();
392         }
393     }
394 
395     @UnsupportedAppUsage
disconnectWifiDisplay()396     public void disconnectWifiDisplay() {
397         try {
398             mDm.disconnectWifiDisplay();
399         } catch (RemoteException ex) {
400             throw ex.rethrowFromSystemServer();
401         }
402     }
403 
renameWifiDisplay(String deviceAddress, String alias)404     public void renameWifiDisplay(String deviceAddress, String alias) {
405         if (deviceAddress == null) {
406             throw new IllegalArgumentException("deviceAddress must not be null");
407         }
408 
409         try {
410             mDm.renameWifiDisplay(deviceAddress, alias);
411         } catch (RemoteException ex) {
412             throw ex.rethrowFromSystemServer();
413         }
414     }
415 
forgetWifiDisplay(String deviceAddress)416     public void forgetWifiDisplay(String deviceAddress) {
417         if (deviceAddress == null) {
418             throw new IllegalArgumentException("deviceAddress must not be null");
419         }
420 
421         try {
422             mDm.forgetWifiDisplay(deviceAddress);
423         } catch (RemoteException ex) {
424             throw ex.rethrowFromSystemServer();
425         }
426     }
427 
428     @UnsupportedAppUsage
getWifiDisplayStatus()429     public WifiDisplayStatus getWifiDisplayStatus() {
430         try {
431             return mDm.getWifiDisplayStatus();
432         } catch (RemoteException ex) {
433             throw ex.rethrowFromSystemServer();
434         }
435     }
436 
requestColorMode(int displayId, int colorMode)437     public void requestColorMode(int displayId, int colorMode) {
438         try {
439             mDm.requestColorMode(displayId, colorMode);
440         } catch (RemoteException ex) {
441             throw ex.rethrowFromSystemServer();
442         }
443     }
444 
createVirtualDisplay(Context context, MediaProjection projection, String name, int width, int height, int densityDpi, Surface surface, int flags, VirtualDisplay.Callback callback, Handler handler, String uniqueId)445     public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
446             String name, int width, int height, int densityDpi, Surface surface, int flags,
447             VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
448         if (TextUtils.isEmpty(name)) {
449             throw new IllegalArgumentException("name must be non-null and non-empty");
450         }
451         if (width <= 0 || height <= 0 || densityDpi <= 0) {
452             throw new IllegalArgumentException("width, height, and densityDpi must be "
453                     + "greater than 0");
454         }
455 
456         VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
457         IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
458         int displayId;
459         try {
460             displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken,
461                     context.getPackageName(), name, width, height, densityDpi, surface, flags,
462                     uniqueId);
463         } catch (RemoteException ex) {
464             throw ex.rethrowFromSystemServer();
465         }
466         if (displayId < 0) {
467             Log.e(TAG, "Could not create virtual display: " + name);
468             return null;
469         }
470         Display display = getRealDisplay(displayId);
471         if (display == null) {
472             Log.wtf(TAG, "Could not obtain display info for newly created "
473                     + "virtual display: " + name);
474             try {
475                 mDm.releaseVirtualDisplay(callbackWrapper);
476             } catch (RemoteException ex) {
477                 throw ex.rethrowFromSystemServer();
478             }
479             return null;
480         }
481         return new VirtualDisplay(this, display, callbackWrapper, surface);
482     }
483 
setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface)484     public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
485         try {
486             mDm.setVirtualDisplaySurface(token, surface);
487             setVirtualDisplayState(token, surface != null);
488         } catch (RemoteException ex) {
489             throw ex.rethrowFromSystemServer();
490         }
491     }
492 
resizeVirtualDisplay(IVirtualDisplayCallback token, int width, int height, int densityDpi)493     public void resizeVirtualDisplay(IVirtualDisplayCallback token,
494             int width, int height, int densityDpi) {
495         try {
496             mDm.resizeVirtualDisplay(token, width, height, densityDpi);
497         } catch (RemoteException ex) {
498             throw ex.rethrowFromSystemServer();
499         }
500     }
501 
releaseVirtualDisplay(IVirtualDisplayCallback token)502     public void releaseVirtualDisplay(IVirtualDisplayCallback token) {
503         try {
504             mDm.releaseVirtualDisplay(token);
505         } catch (RemoteException ex) {
506             throw ex.rethrowFromSystemServer();
507         }
508     }
509 
setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn)510     void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
511         try {
512             mDm.setVirtualDisplayState(token, isOn);
513         } catch (RemoteException ex) {
514             throw ex.rethrowFromSystemServer();
515         }
516     }
517 
518     /**
519      * Gets the stable device display size, in pixels.
520      */
getStableDisplaySize()521     public Point getStableDisplaySize() {
522         try {
523             return mDm.getStableDisplaySize();
524         } catch (RemoteException ex) {
525             throw ex.rethrowFromSystemServer();
526         }
527     }
528 
529     /**
530      * Retrieves brightness change events.
531      */
getBrightnessEvents(String callingPackage)532     public List<BrightnessChangeEvent> getBrightnessEvents(String callingPackage) {
533         try {
534             ParceledListSlice<BrightnessChangeEvent> events =
535                     mDm.getBrightnessEvents(callingPackage);
536             if (events == null) {
537                 return Collections.emptyList();
538             }
539             return events.getList();
540         } catch (RemoteException ex) {
541             throw ex.rethrowFromSystemServer();
542         }
543     }
544 
545     /**
546      * Gets the preferred wide gamut color space for all displays.
547      * The wide gamut color space is returned from composition pipeline
548      * based on hardware capability.
549      *
550      * @hide
551      */
getPreferredWideGamutColorSpace()552     public ColorSpace getPreferredWideGamutColorSpace() {
553         return mWideColorSpace;
554     }
555 
556     /**
557      * Sets the global brightness configuration for a given user.
558      *
559      * @hide
560      */
setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId, String packageName)561     public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId,
562             String packageName) {
563         try {
564             mDm.setBrightnessConfigurationForUser(c, userId, packageName);
565         } catch (RemoteException ex) {
566             throw ex.rethrowFromSystemServer();
567         }
568     }
569 
570     /**
571      * Gets the global brightness configuration for a given user or null if one hasn't been set.
572      *
573      * @hide
574      */
getBrightnessConfigurationForUser(int userId)575     public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) {
576         try {
577             return mDm.getBrightnessConfigurationForUser(userId);
578         } catch (RemoteException ex) {
579             throw ex.rethrowFromSystemServer();
580         }
581     }
582 
583     /**
584      * Gets the default brightness configuration or null if one hasn't been configured.
585      *
586      * @hide
587      */
getDefaultBrightnessConfiguration()588     public BrightnessConfiguration getDefaultBrightnessConfiguration() {
589         try {
590             return mDm.getDefaultBrightnessConfiguration();
591         } catch (RemoteException ex) {
592             throw ex.rethrowFromSystemServer();
593         }
594     }
595 
596     /**
597      * Temporarily sets the brightness of the display.
598      * <p>
599      * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
600      * </p>
601      *
602      * @param brightness The brightness value from 0 to 255.
603      *
604      * @hide Requires signature permission.
605      */
setTemporaryBrightness(int brightness)606     public void setTemporaryBrightness(int brightness) {
607         try {
608             mDm.setTemporaryBrightness(brightness);
609         } catch (RemoteException ex) {
610             throw ex.rethrowFromSystemServer();
611         }
612     }
613 
614     /**
615      * Temporarily sets the auto brightness adjustment factor.
616      * <p>
617      * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
618      * </p>
619      *
620      * @param adjustment The adjustment factor from -1.0 to 1.0.
621      *
622      * @hide Requires signature permission.
623      */
setTemporaryAutoBrightnessAdjustment(float adjustment)624     public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
625         try {
626             mDm.setTemporaryAutoBrightnessAdjustment(adjustment);
627         } catch (RemoteException ex) {
628             throw ex.rethrowFromSystemServer();
629         }
630     }
631 
632     /**
633      * Returns the minimum brightness curve, which guarantess that any brightness curve that dips
634      * below it is rejected by the system.
635      * This prevent auto-brightness from setting the screen so dark as to prevent the user from
636      * resetting or disabling it, and maps lux to the absolute minimum nits that are still readable
637      * in that ambient brightness.
638      *
639      * @return The minimum brightness curve (as lux values and their corresponding nits values).
640      */
getMinimumBrightnessCurve()641     public Pair<float[], float[]> getMinimumBrightnessCurve() {
642         try {
643             Curve curve = mDm.getMinimumBrightnessCurve();
644             return Pair.create(curve.getX(), curve.getY());
645         } catch (RemoteException ex) {
646             throw ex.rethrowFromSystemServer();
647         }
648     }
649 
650     /**
651      * Retrieves ambient brightness stats.
652      */
getAmbientBrightnessStats()653     public List<AmbientBrightnessDayStats> getAmbientBrightnessStats() {
654         try {
655             ParceledListSlice<AmbientBrightnessDayStats> stats = mDm.getAmbientBrightnessStats();
656             if (stats == null) {
657                 return Collections.emptyList();
658             }
659             return stats.getList();
660         } catch (RemoteException ex) {
661             throw ex.rethrowFromSystemServer();
662         }
663     }
664 
665     private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
666         @Override
onDisplayEvent(int displayId, int event)667         public void onDisplayEvent(int displayId, int event) {
668             if (DEBUG) {
669                 Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
670             }
671             handleDisplayEvent(displayId, event);
672         }
673     }
674 
675     private static final class DisplayListenerDelegate extends Handler {
676         public final DisplayListener mListener;
677 
DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper)678         DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper) {
679             super(looper, null, true /*async*/);
680             mListener = listener;
681         }
682 
sendDisplayEvent(int displayId, int event)683         public void sendDisplayEvent(int displayId, int event) {
684             Message msg = obtainMessage(event, displayId, 0);
685             sendMessage(msg);
686         }
687 
clearEvents()688         public void clearEvents() {
689             removeCallbacksAndMessages(null);
690         }
691 
692         @Override
handleMessage(Message msg)693         public void handleMessage(Message msg) {
694             switch (msg.what) {
695                 case EVENT_DISPLAY_ADDED:
696                     mListener.onDisplayAdded(msg.arg1);
697                     break;
698                 case EVENT_DISPLAY_CHANGED:
699                     mListener.onDisplayChanged(msg.arg1);
700                     break;
701                 case EVENT_DISPLAY_REMOVED:
702                     mListener.onDisplayRemoved(msg.arg1);
703                     break;
704             }
705         }
706     }
707 
708     private final static class VirtualDisplayCallback extends IVirtualDisplayCallback.Stub {
709         private VirtualDisplayCallbackDelegate mDelegate;
710 
VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler)711         public VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler) {
712             if (callback != null) {
713                 mDelegate = new VirtualDisplayCallbackDelegate(callback, handler);
714             }
715         }
716 
717         @Override // Binder call
onPaused()718         public void onPaused() {
719             if (mDelegate != null) {
720                 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_PAUSED);
721             }
722         }
723 
724         @Override // Binder call
onResumed()725         public void onResumed() {
726             if (mDelegate != null) {
727                 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_RESUMED);
728             }
729         }
730 
731         @Override // Binder call
onStopped()732         public void onStopped() {
733             if (mDelegate != null) {
734                 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_STOPPED);
735             }
736         }
737     }
738 
739     private final static class VirtualDisplayCallbackDelegate extends Handler {
740         public static final int MSG_DISPLAY_PAUSED = 0;
741         public static final int MSG_DISPLAY_RESUMED = 1;
742         public static final int MSG_DISPLAY_STOPPED = 2;
743 
744         private final VirtualDisplay.Callback mCallback;
745 
VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback, Handler handler)746         public VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback,
747                 Handler handler) {
748             super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
749             mCallback = callback;
750         }
751 
752         @Override
handleMessage(Message msg)753         public void handleMessage(Message msg) {
754             switch (msg.what) {
755                 case MSG_DISPLAY_PAUSED:
756                     mCallback.onPaused();
757                     break;
758                 case MSG_DISPLAY_RESUMED:
759                     mCallback.onResumed();
760                     break;
761                 case MSG_DISPLAY_STOPPED:
762                     mCallback.onStopped();
763                     break;
764             }
765         }
766     }
767 }
768