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
17package android.hardware.display;
18
19import android.content.Context;
20import android.hardware.display.DisplayManager.DisplayListener;
21import android.os.Binder;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.Looper;
25import android.os.Message;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.text.TextUtils;
29import android.util.Log;
30import android.util.SparseArray;
31import android.view.DisplayAdjustments;
32import android.view.Display;
33import android.view.DisplayInfo;
34import android.view.Surface;
35
36import java.util.ArrayList;
37
38/**
39 * Manager communication with the display manager service on behalf of
40 * an application process.  You're probably looking for {@link DisplayManager}.
41 *
42 * @hide
43 */
44public final class DisplayManagerGlobal {
45    private static final String TAG = "DisplayManager";
46    private static final boolean DEBUG = false;
47
48    // True if display info and display ids should be cached.
49    //
50    // FIXME: The cache is currently disabled because it's unclear whether we have the
51    // necessary guarantees that the caches will always be flushed before clients
52    // attempt to observe their new state.  For example, depending on the order
53    // in which the binder transactions take place, we might have a problem where
54    // an application could start processing a configuration change due to a display
55    // orientation change before the display info cache has actually been invalidated.
56    private static final boolean USE_CACHE = false;
57
58    public static final int EVENT_DISPLAY_ADDED = 1;
59    public static final int EVENT_DISPLAY_CHANGED = 2;
60    public static final int EVENT_DISPLAY_REMOVED = 3;
61
62    private static DisplayManagerGlobal sInstance;
63
64    private final Object mLock = new Object();
65
66    private final IDisplayManager mDm;
67
68    private DisplayManagerCallback mCallback;
69    private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
70            new ArrayList<DisplayListenerDelegate>();
71
72    private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
73    private int[] mDisplayIdCache;
74
75    private int mWifiDisplayScanNestCount;
76
77    private DisplayManagerGlobal(IDisplayManager dm) {
78        mDm = dm;
79    }
80
81    /**
82     * Gets an instance of the display manager global singleton.
83     *
84     * @return The display manager instance, may be null early in system startup
85     * before the display manager has been fully initialized.
86     */
87    public static DisplayManagerGlobal getInstance() {
88        synchronized (DisplayManagerGlobal.class) {
89            if (sInstance == null) {
90                IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
91                if (b != null) {
92                    sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
93                }
94            }
95            return sInstance;
96        }
97    }
98
99    /**
100     * Get information about a particular logical display.
101     *
102     * @param displayId The logical display id.
103     * @return Information about the specified display, or null if it does not exist.
104     * This object belongs to an internal cache and should be treated as if it were immutable.
105     */
106    public DisplayInfo getDisplayInfo(int displayId) {
107        try {
108            synchronized (mLock) {
109                DisplayInfo info;
110                if (USE_CACHE) {
111                    info = mDisplayInfoCache.get(displayId);
112                    if (info != null) {
113                        return info;
114                    }
115                }
116
117                info = mDm.getDisplayInfo(displayId);
118                if (info == null) {
119                    return null;
120                }
121
122                if (USE_CACHE) {
123                    mDisplayInfoCache.put(displayId, info);
124                }
125                registerCallbackIfNeededLocked();
126
127                if (DEBUG) {
128                    Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
129                }
130                return info;
131            }
132        } catch (RemoteException ex) {
133            Log.e(TAG, "Could not get display information from display manager.", ex);
134            return null;
135        }
136    }
137
138    /**
139     * Gets all currently valid logical display ids.
140     *
141     * @return An array containing all display ids.
142     */
143    public int[] getDisplayIds() {
144        try {
145            synchronized (mLock) {
146                if (USE_CACHE) {
147                    if (mDisplayIdCache != null) {
148                        return mDisplayIdCache;
149                    }
150                }
151
152                int[] displayIds = mDm.getDisplayIds();
153                if (USE_CACHE) {
154                    mDisplayIdCache = displayIds;
155                }
156                registerCallbackIfNeededLocked();
157                return displayIds;
158            }
159        } catch (RemoteException ex) {
160            Log.e(TAG, "Could not get display ids from display manager.", ex);
161            return new int[] { Display.DEFAULT_DISPLAY };
162        }
163    }
164
165    /**
166     * Gets information about a logical display.
167     *
168     * The display metrics may be adjusted to provide compatibility
169     * for legacy applications or limited screen areas.
170     *
171     * @param displayId The logical display id.
172     * @param daj The compatibility info and activityToken.
173     * @return The display object, or null if there is no display with the given id.
174     */
175    public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) {
176        DisplayInfo displayInfo = getDisplayInfo(displayId);
177        if (displayInfo == null) {
178            return null;
179        }
180        return new Display(this, displayId, displayInfo, daj);
181    }
182
183    /**
184     * Gets information about a logical display without applying any compatibility metrics.
185     *
186     * @param displayId The logical display id.
187     * @return The display object, or null if there is no display with the given id.
188     */
189    public Display getRealDisplay(int displayId) {
190        return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
191    }
192
193    /**
194     * Gets information about a logical display without applying any compatibility metrics.
195     *
196     * @param displayId The logical display id.
197     * @param IBinder the activity token for this display.
198     * @return The display object, or null if there is no display with the given id.
199     */
200    public Display getRealDisplay(int displayId, IBinder token) {
201        return getCompatibleDisplay(displayId, new DisplayAdjustments(token));
202    }
203
204    public void registerDisplayListener(DisplayListener listener, Handler handler) {
205        if (listener == null) {
206            throw new IllegalArgumentException("listener must not be null");
207        }
208
209        synchronized (mLock) {
210            int index = findDisplayListenerLocked(listener);
211            if (index < 0) {
212                mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
213                registerCallbackIfNeededLocked();
214            }
215        }
216    }
217
218    public void unregisterDisplayListener(DisplayListener listener) {
219        if (listener == null) {
220            throw new IllegalArgumentException("listener must not be null");
221        }
222
223        synchronized (mLock) {
224            int index = findDisplayListenerLocked(listener);
225            if (index >= 0) {
226                DisplayListenerDelegate d = mDisplayListeners.get(index);
227                d.clearEvents();
228                mDisplayListeners.remove(index);
229            }
230        }
231    }
232
233    private int findDisplayListenerLocked(DisplayListener listener) {
234        final int numListeners = mDisplayListeners.size();
235        for (int i = 0; i < numListeners; i++) {
236            if (mDisplayListeners.get(i).mListener == listener) {
237                return i;
238            }
239        }
240        return -1;
241    }
242
243    private void registerCallbackIfNeededLocked() {
244        if (mCallback == null) {
245            mCallback = new DisplayManagerCallback();
246            try {
247                mDm.registerCallback(mCallback);
248            } catch (RemoteException ex) {
249                Log.e(TAG, "Failed to register callback with display manager service.", ex);
250                mCallback = null;
251            }
252        }
253    }
254
255    private void handleDisplayEvent(int displayId, int event) {
256        synchronized (mLock) {
257            if (USE_CACHE) {
258                mDisplayInfoCache.remove(displayId);
259
260                if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
261                    mDisplayIdCache = null;
262                }
263            }
264
265            final int numListeners = mDisplayListeners.size();
266            for (int i = 0; i < numListeners; i++) {
267                mDisplayListeners.get(i).sendDisplayEvent(displayId, event);
268            }
269        }
270    }
271
272    public void startWifiDisplayScan() {
273        synchronized (mLock) {
274            if (mWifiDisplayScanNestCount++ == 0) {
275                registerCallbackIfNeededLocked();
276                try {
277                    mDm.startWifiDisplayScan();
278                } catch (RemoteException ex) {
279                    Log.e(TAG, "Failed to scan for Wifi displays.", ex);
280                }
281            }
282        }
283    }
284
285    public void stopWifiDisplayScan() {
286        synchronized (mLock) {
287            if (--mWifiDisplayScanNestCount == 0) {
288                try {
289                    mDm.stopWifiDisplayScan();
290                } catch (RemoteException ex) {
291                    Log.e(TAG, "Failed to scan for Wifi displays.", ex);
292                }
293            } else if (mWifiDisplayScanNestCount < 0) {
294                Log.wtf(TAG, "Wifi display scan nest count became negative: "
295                        + mWifiDisplayScanNestCount);
296                mWifiDisplayScanNestCount = 0;
297            }
298        }
299    }
300
301    public void connectWifiDisplay(String deviceAddress) {
302        if (deviceAddress == null) {
303            throw new IllegalArgumentException("deviceAddress must not be null");
304        }
305
306        try {
307            mDm.connectWifiDisplay(deviceAddress);
308        } catch (RemoteException ex) {
309            Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex);
310        }
311    }
312
313    public void pauseWifiDisplay() {
314        try {
315            mDm.pauseWifiDisplay();
316        } catch (RemoteException ex) {
317            Log.e(TAG, "Failed to pause Wifi display.", ex);
318        }
319    }
320
321    public void resumeWifiDisplay() {
322        try {
323            mDm.resumeWifiDisplay();
324        } catch (RemoteException ex) {
325            Log.e(TAG, "Failed to resume Wifi display.", ex);
326        }
327    }
328
329    public void disconnectWifiDisplay() {
330        try {
331            mDm.disconnectWifiDisplay();
332        } catch (RemoteException ex) {
333            Log.e(TAG, "Failed to disconnect from Wifi display.", ex);
334        }
335    }
336
337    public void renameWifiDisplay(String deviceAddress, String alias) {
338        if (deviceAddress == null) {
339            throw new IllegalArgumentException("deviceAddress must not be null");
340        }
341
342        try {
343            mDm.renameWifiDisplay(deviceAddress, alias);
344        } catch (RemoteException ex) {
345            Log.e(TAG, "Failed to rename Wifi display " + deviceAddress
346                    + " with alias " + alias + ".", ex);
347        }
348    }
349
350    public void forgetWifiDisplay(String deviceAddress) {
351        if (deviceAddress == null) {
352            throw new IllegalArgumentException("deviceAddress must not be null");
353        }
354
355        try {
356            mDm.forgetWifiDisplay(deviceAddress);
357        } catch (RemoteException ex) {
358            Log.e(TAG, "Failed to forget Wifi display.", ex);
359        }
360    }
361
362    public WifiDisplayStatus getWifiDisplayStatus() {
363        try {
364            return mDm.getWifiDisplayStatus();
365        } catch (RemoteException ex) {
366            Log.e(TAG, "Failed to get Wifi display status.", ex);
367            return new WifiDisplayStatus();
368        }
369    }
370
371    public VirtualDisplay createVirtualDisplay(Context context, String name,
372            int width, int height, int densityDpi, Surface surface, int flags) {
373        if (TextUtils.isEmpty(name)) {
374            throw new IllegalArgumentException("name must be non-null and non-empty");
375        }
376        if (width <= 0 || height <= 0 || densityDpi <= 0) {
377            throw new IllegalArgumentException("width, height, and densityDpi must be "
378                    + "greater than 0");
379        }
380        if (surface == null) {
381            throw new IllegalArgumentException("surface must not be null");
382        }
383
384        Binder token = new Binder();
385        int displayId;
386        try {
387            displayId = mDm.createVirtualDisplay(token, context.getPackageName(),
388                    name, width, height, densityDpi, surface, flags);
389        } catch (RemoteException ex) {
390            Log.e(TAG, "Could not create virtual display: " + name, ex);
391            return null;
392        }
393        if (displayId < 0) {
394            Log.e(TAG, "Could not create virtual display: " + name);
395            return null;
396        }
397        Display display = getRealDisplay(displayId);
398        if (display == null) {
399            Log.wtf(TAG, "Could not obtain display info for newly created "
400                    + "virtual display: " + name);
401            try {
402                mDm.releaseVirtualDisplay(token);
403            } catch (RemoteException ex) {
404            }
405            return null;
406        }
407        return new VirtualDisplay(this, display, token);
408    }
409
410    public void releaseVirtualDisplay(IBinder token) {
411        try {
412            mDm.releaseVirtualDisplay(token);
413        } catch (RemoteException ex) {
414            Log.w(TAG, "Failed to release virtual display.", ex);
415        }
416    }
417
418    private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
419        @Override
420        public void onDisplayEvent(int displayId, int event) {
421            if (DEBUG) {
422                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
423            }
424            handleDisplayEvent(displayId, event);
425        }
426    }
427
428    private static final class DisplayListenerDelegate extends Handler {
429        public final DisplayListener mListener;
430
431        public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
432            super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
433            mListener = listener;
434        }
435
436        public void sendDisplayEvent(int displayId, int event) {
437            Message msg = obtainMessage(event, displayId, 0);
438            sendMessage(msg);
439        }
440
441        public void clearEvents() {
442            removeCallbacksAndMessages(null);
443        }
444
445        @Override
446        public void handleMessage(Message msg) {
447            switch (msg.what) {
448                case EVENT_DISPLAY_ADDED:
449                    mListener.onDisplayAdded(msg.arg1);
450                    break;
451                case EVENT_DISPLAY_CHANGED:
452                    mListener.onDisplayChanged(msg.arg1);
453                    break;
454                case EVENT_DISPLAY_REMOVED:
455                    mListener.onDisplayRemoved(msg.arg1);
456                    break;
457            }
458        }
459    }
460}
461