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.os.Handler;
21import android.util.SparseArray;
22import android.view.Display;
23
24import java.util.ArrayList;
25
26/**
27 * Manages the properties of attached displays.
28 * <p>
29 * Get an instance of this class by calling
30 * {@link android.content.Context#getSystemService(java.lang.String)
31 * Context.getSystemService()} with the argument
32 * {@link android.content.Context#DISPLAY_SERVICE}.
33 * </p>
34 */
35public final class DisplayManager {
36    private static final String TAG = "DisplayManager";
37    private static final boolean DEBUG = false;
38
39    private final Context mContext;
40    private final DisplayManagerGlobal mGlobal;
41
42    private final Object mLock = new Object();
43    private final SparseArray<Display> mDisplays = new SparseArray<Display>();
44
45    private final ArrayList<Display> mTempDisplays = new ArrayList<Display>();
46
47    /**
48     * Broadcast receiver that indicates when the Wifi display status changes.
49     * <p>
50     * The status is provided as a {@link WifiDisplayStatus} object in the
51     * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra.
52     * </p><p>
53     * This broadcast is only sent to registered receivers and can only be sent by the system.
54     * </p>
55     * @hide
56     */
57    public static final String ACTION_WIFI_DISPLAY_STATUS_CHANGED =
58            "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED";
59
60    /**
61     * Contains a {@link WifiDisplayStatus} object.
62     * @hide
63     */
64    public static final String EXTRA_WIFI_DISPLAY_STATUS =
65            "android.hardware.display.extra.WIFI_DISPLAY_STATUS";
66
67    /**
68     * Display category: Presentation displays.
69     * <p>
70     * This category can be used to identify secondary displays that are suitable for
71     * use as presentation displays.
72     * </p>
73     *
74     * @see android.app.Presentation for information about presenting content
75     * on secondary displays.
76     * @see #getDisplays(String)
77     */
78    public static final String DISPLAY_CATEGORY_PRESENTATION =
79            "android.hardware.display.category.PRESENTATION";
80
81    /** @hide */
82    public DisplayManager(Context context) {
83        mContext = context;
84        mGlobal = DisplayManagerGlobal.getInstance();
85    }
86
87    /**
88     * Gets information about a logical display.
89     *
90     * The display metrics may be adjusted to provide compatibility
91     * for legacy applications.
92     *
93     * @param displayId The logical display id.
94     * @return The display object, or null if there is no valid display with the given id.
95     */
96    public Display getDisplay(int displayId) {
97        synchronized (mLock) {
98            return getOrCreateDisplayLocked(displayId, false /*assumeValid*/);
99        }
100    }
101
102    /**
103     * Gets all currently valid logical displays.
104     *
105     * @return An array containing all displays.
106     */
107    public Display[] getDisplays() {
108        return getDisplays(null);
109    }
110
111    /**
112     * Gets all currently valid logical displays of the specified category.
113     * <p>
114     * When there are multiple displays in a category the returned displays are sorted
115     * of preference.  For example, if the requested category is
116     * {@link #DISPLAY_CATEGORY_PRESENTATION} and there are multiple presentation displays
117     * then the displays are sorted so that the first display in the returned array
118     * is the most preferred presentation display.  The application may simply
119     * use the first display or allow the user to choose.
120     * </p>
121     *
122     * @param category The requested display category or null to return all displays.
123     * @return An array containing all displays sorted by order of preference.
124     *
125     * @see #DISPLAY_CATEGORY_PRESENTATION
126     */
127    public Display[] getDisplays(String category) {
128        final int[] displayIds = mGlobal.getDisplayIds();
129        synchronized (mLock) {
130            try {
131                if (category == null) {
132                    addMatchingDisplaysLocked(mTempDisplays, displayIds, -1);
133                } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
134                    addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
135                    addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI);
136                    addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
137                }
138                return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
139            } finally {
140                mTempDisplays.clear();
141            }
142        }
143    }
144
145    private void addMatchingDisplaysLocked(
146            ArrayList<Display> displays, int[] displayIds, int matchType) {
147        for (int i = 0; i < displayIds.length; i++) {
148            Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
149            if (display != null
150                    && (matchType < 0 || display.getType() == matchType)) {
151                displays.add(display);
152            }
153        }
154    }
155
156    private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) {
157        Display display = mDisplays.get(displayId);
158        if (display == null) {
159            display = mGlobal.getCompatibleDisplay(displayId,
160                    mContext.getCompatibilityInfo(displayId));
161            if (display != null) {
162                mDisplays.put(displayId, display);
163            }
164        } else if (!assumeValid && !display.isValid()) {
165            display = null;
166        }
167        return display;
168    }
169
170    /**
171     * Registers an display listener to receive notifications about when
172     * displays are added, removed or changed.
173     *
174     * @param listener The listener to register.
175     * @param handler The handler on which the listener should be invoked, or null
176     * if the listener should be invoked on the calling thread's looper.
177     *
178     * @see #unregisterDisplayListener
179     */
180    public void registerDisplayListener(DisplayListener listener, Handler handler) {
181        mGlobal.registerDisplayListener(listener, handler);
182    }
183
184    /**
185     * Unregisters an input device listener.
186     *
187     * @param listener The listener to unregister.
188     *
189     * @see #registerDisplayListener
190     */
191    public void unregisterDisplayListener(DisplayListener listener) {
192        mGlobal.unregisterDisplayListener(listener);
193    }
194
195    /**
196     * Initiates a fresh scan of availble Wifi displays.
197     * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
198     * @hide
199     */
200    public void scanWifiDisplays() {
201        mGlobal.scanWifiDisplays();
202    }
203
204    /**
205     * Connects to a Wifi display.
206     * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
207     * <p>
208     * Automatically remembers the display after a successful connection, if not
209     * already remembered.
210     * </p><p>
211     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} to connect
212     * to unknown displays.  No permissions are required to connect to already known displays.
213     * </p>
214     *
215     * @param deviceAddress The MAC address of the device to which we should connect.
216     * @hide
217     */
218    public void connectWifiDisplay(String deviceAddress) {
219        mGlobal.connectWifiDisplay(deviceAddress);
220    }
221
222    /**
223     * Disconnects from the current Wifi display.
224     * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
225     * @hide
226     */
227    public void disconnectWifiDisplay() {
228        mGlobal.disconnectWifiDisplay();
229    }
230
231    /**
232     * Renames a Wifi display.
233     * <p>
234     * The display must already be remembered for this call to succeed.  In other words,
235     * we must already have successfully connected to the display at least once and then
236     * not forgotten it.
237     * </p><p>
238     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
239     * </p>
240     *
241     * @param deviceAddress The MAC address of the device to rename.
242     * @param alias The alias name by which to remember the device, or null
243     * or empty if no alias should be used.
244     * @hide
245     */
246    public void renameWifiDisplay(String deviceAddress, String alias) {
247        mGlobal.renameWifiDisplay(deviceAddress, alias);
248    }
249
250    /**
251     * Forgets a previously remembered Wifi display.
252     * <p>
253     * Automatically disconnects from the display if currently connected to it.
254     * </p><p>
255     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
256     * </p>
257     *
258     * @param deviceAddress The MAC address of the device to forget.
259     * @hide
260     */
261    public void forgetWifiDisplay(String deviceAddress) {
262        mGlobal.forgetWifiDisplay(deviceAddress);
263    }
264
265    /**
266     * Gets the current Wifi display status.
267     * Watch for changes in the status by registering a broadcast receiver for
268     * {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED}.
269     *
270     * @return The current Wifi display status.
271     * @hide
272     */
273    public WifiDisplayStatus getWifiDisplayStatus() {
274        return mGlobal.getWifiDisplayStatus();
275    }
276
277    /**
278     * Listens for changes in available display devices.
279     */
280    public interface DisplayListener {
281        /**
282         * Called whenever a logical display has been added to the system.
283         * Use {@link DisplayManager#getDisplay} to get more information about
284         * the display.
285         *
286         * @param displayId The id of the logical display that was added.
287         */
288        void onDisplayAdded(int displayId);
289
290        /**
291         * Called whenever a logical display has been removed from the system.
292         *
293         * @param displayId The id of the logical display that was removed.
294         */
295        void onDisplayRemoved(int displayId);
296
297        /**
298         * Called whenever the properties of a logical display have changed.
299         *
300         * @param displayId The id of the logical display that changed.
301         */
302        void onDisplayChanged(int displayId);
303    }
304}
305