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.Handler;
22import android.os.IBinder;
23import android.os.Looper;
24import android.os.Message;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.util.Log;
28import android.util.SparseArray;
29import android.view.CompatibilityInfoHolder;
30import android.view.Display;
31import android.view.DisplayInfo;
32
33import java.util.ArrayList;
34
35/**
36 * Manager communication with the display manager service on behalf of
37 * an application process.  You're probably looking for {@link DisplayManager}.
38 *
39 * @hide
40 */
41public final class DisplayManagerGlobal {
42    private static final String TAG = "DisplayManager";
43    private static final boolean DEBUG = false;
44
45    // True if display info and display ids should be cached.
46    //
47    // FIXME: The cache is currently disabled because it's unclear whether we have the
48    // necessary guarantees that the caches will always be flushed before clients
49    // attempt to observe their new state.  For example, depending on the order
50    // in which the binder transactions take place, we might have a problem where
51    // an application could start processing a configuration change due to a display
52    // orientation change before the display info cache has actually been invalidated.
53    private static final boolean USE_CACHE = false;
54
55    public static final int EVENT_DISPLAY_ADDED = 1;
56    public static final int EVENT_DISPLAY_CHANGED = 2;
57    public static final int EVENT_DISPLAY_REMOVED = 3;
58
59    private static DisplayManagerGlobal sInstance;
60
61    private final Object mLock = new Object();
62
63    private final IDisplayManager mDm;
64
65    private DisplayManagerCallback mCallback;
66    private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
67            new ArrayList<DisplayListenerDelegate>();
68
69    private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
70    private int[] mDisplayIdCache;
71
72    private DisplayManagerGlobal(IDisplayManager dm) {
73        mDm = dm;
74    }
75
76    /**
77     * Gets an instance of the display manager global singleton.
78     *
79     * @return The display manager instance, may be null early in system startup
80     * before the display manager has been fully initialized.
81     */
82    public static DisplayManagerGlobal getInstance() {
83        synchronized (DisplayManagerGlobal.class) {
84            if (sInstance == null) {
85                IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
86                if (b != null) {
87                    sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
88                }
89            }
90            return sInstance;
91        }
92    }
93
94    /**
95     * Get information about a particular logical display.
96     *
97     * @param displayId The logical display id.
98     * @return Information about the specified display, or null if it does not exist.
99     * This object belongs to an internal cache and should be treated as if it were immutable.
100     */
101    public DisplayInfo getDisplayInfo(int displayId) {
102        try {
103            synchronized (mLock) {
104                DisplayInfo info;
105                if (USE_CACHE) {
106                    info = mDisplayInfoCache.get(displayId);
107                    if (info != null) {
108                        return info;
109                    }
110                }
111
112                info = mDm.getDisplayInfo(displayId);
113                if (info == null) {
114                    return null;
115                }
116
117                if (USE_CACHE) {
118                    mDisplayInfoCache.put(displayId, info);
119                }
120                registerCallbackIfNeededLocked();
121
122                if (DEBUG) {
123                    Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
124                }
125                return info;
126            }
127        } catch (RemoteException ex) {
128            Log.e(TAG, "Could not get display information from display manager.", ex);
129            return null;
130        }
131    }
132
133    /**
134     * Gets all currently valid logical display ids.
135     *
136     * @return An array containing all display ids.
137     */
138    public int[] getDisplayIds() {
139        try {
140            synchronized (mLock) {
141                if (USE_CACHE) {
142                    if (mDisplayIdCache != null) {
143                        return mDisplayIdCache;
144                    }
145                }
146
147                int[] displayIds = mDm.getDisplayIds();
148                if (USE_CACHE) {
149                    mDisplayIdCache = displayIds;
150                }
151                registerCallbackIfNeededLocked();
152                return displayIds;
153            }
154        } catch (RemoteException ex) {
155            Log.e(TAG, "Could not get display ids from display manager.", ex);
156            return new int[] { Display.DEFAULT_DISPLAY };
157        }
158    }
159
160    /**
161     * Gets information about a logical display.
162     *
163     * The display metrics may be adjusted to provide compatibility
164     * for legacy applications.
165     *
166     * @param displayId The logical display id.
167     * @param cih The compatibility info, or null if none is required.
168     * @return The display object, or null if there is no display with the given id.
169     */
170    public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
171        DisplayInfo displayInfo = getDisplayInfo(displayId);
172        if (displayInfo == null) {
173            return null;
174        }
175        return new Display(this, displayId, displayInfo, cih);
176    }
177
178    /**
179     * Gets information about a logical display without applying any compatibility metrics.
180     *
181     * @param displayId The logical display id.
182     * @return The display object, or null if there is no display with the given id.
183     */
184    public Display getRealDisplay(int displayId) {
185        return getCompatibleDisplay(displayId, null);
186    }
187
188    public void registerDisplayListener(DisplayListener listener, Handler handler) {
189        if (listener == null) {
190            throw new IllegalArgumentException("listener must not be null");
191        }
192
193        synchronized (mLock) {
194            int index = findDisplayListenerLocked(listener);
195            if (index < 0) {
196                mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
197                registerCallbackIfNeededLocked();
198            }
199        }
200    }
201
202    public void unregisterDisplayListener(DisplayListener listener) {
203        if (listener == null) {
204            throw new IllegalArgumentException("listener must not be null");
205        }
206
207        synchronized (mLock) {
208            int index = findDisplayListenerLocked(listener);
209            if (index >= 0) {
210                DisplayListenerDelegate d = mDisplayListeners.get(index);
211                d.clearEvents();
212                mDisplayListeners.remove(index);
213            }
214        }
215    }
216
217    private int findDisplayListenerLocked(DisplayListener listener) {
218        final int numListeners = mDisplayListeners.size();
219        for (int i = 0; i < numListeners; i++) {
220            if (mDisplayListeners.get(i).mListener == listener) {
221                return i;
222            }
223        }
224        return -1;
225    }
226
227    private void registerCallbackIfNeededLocked() {
228        if (mCallback == null) {
229            mCallback = new DisplayManagerCallback();
230            try {
231                mDm.registerCallback(mCallback);
232            } catch (RemoteException ex) {
233                Log.e(TAG, "Failed to register callback with display manager service.", ex);
234                mCallback = null;
235            }
236        }
237    }
238
239    private void handleDisplayEvent(int displayId, int event) {
240        synchronized (mLock) {
241            if (USE_CACHE) {
242                mDisplayInfoCache.remove(displayId);
243
244                if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
245                    mDisplayIdCache = null;
246                }
247            }
248
249            final int numListeners = mDisplayListeners.size();
250            for (int i = 0; i < numListeners; i++) {
251                mDisplayListeners.get(i).sendDisplayEvent(displayId, event);
252            }
253        }
254    }
255
256    public void scanWifiDisplays() {
257        try {
258            mDm.scanWifiDisplays();
259        } catch (RemoteException ex) {
260            Log.e(TAG, "Failed to scan for Wifi displays.", ex);
261        }
262    }
263
264    public void connectWifiDisplay(String deviceAddress) {
265        if (deviceAddress == null) {
266            throw new IllegalArgumentException("deviceAddress must not be null");
267        }
268
269        try {
270            mDm.connectWifiDisplay(deviceAddress);
271        } catch (RemoteException ex) {
272            Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex);
273        }
274    }
275
276    public void disconnectWifiDisplay() {
277        try {
278            mDm.disconnectWifiDisplay();
279        } catch (RemoteException ex) {
280            Log.e(TAG, "Failed to disconnect from Wifi display.", ex);
281        }
282    }
283
284    public void renameWifiDisplay(String deviceAddress, String alias) {
285        if (deviceAddress == null) {
286            throw new IllegalArgumentException("deviceAddress must not be null");
287        }
288
289        try {
290            mDm.renameWifiDisplay(deviceAddress, alias);
291        } catch (RemoteException ex) {
292            Log.e(TAG, "Failed to rename Wifi display " + deviceAddress
293                    + " with alias " + alias + ".", ex);
294        }
295    }
296
297    public void forgetWifiDisplay(String deviceAddress) {
298        if (deviceAddress == null) {
299            throw new IllegalArgumentException("deviceAddress must not be null");
300        }
301
302        try {
303            mDm.forgetWifiDisplay(deviceAddress);
304        } catch (RemoteException ex) {
305            Log.e(TAG, "Failed to forget Wifi display.", ex);
306        }
307    }
308
309    public WifiDisplayStatus getWifiDisplayStatus() {
310        try {
311            return mDm.getWifiDisplayStatus();
312        } catch (RemoteException ex) {
313            Log.e(TAG, "Failed to get Wifi display status.", ex);
314            return new WifiDisplayStatus();
315        }
316    }
317
318    private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
319        @Override
320        public void onDisplayEvent(int displayId, int event) {
321            if (DEBUG) {
322                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
323            }
324            handleDisplayEvent(displayId, event);
325        }
326    }
327
328    private static final class DisplayListenerDelegate extends Handler {
329        public final DisplayListener mListener;
330
331        public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
332            super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
333            mListener = listener;
334        }
335
336        public void sendDisplayEvent(int displayId, int event) {
337            Message msg = obtainMessage(event, displayId, 0);
338            sendMessage(msg);
339        }
340
341        public void clearEvents() {
342            removeCallbacksAndMessages(null);
343        }
344
345        @Override
346        public void handleMessage(Message msg) {
347            switch (msg.what) {
348                case EVENT_DISPLAY_ADDED:
349                    mListener.onDisplayAdded(msg.arg1);
350                    break;
351                case EVENT_DISPLAY_CHANGED:
352                    mListener.onDisplayChanged(msg.arg1);
353                    break;
354                case EVENT_DISPLAY_REMOVED:
355                    mListener.onDisplayRemoved(msg.arg1);
356                    break;
357            }
358        }
359    }
360}
361