summaryrefslogtreecommitdiff
path: root/core/java/android/app/Presentation.java
blob: b3a39f5025c7e3cd0da72153a40406ffb341e8f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import static android.content.Context.DISPLAY_SERVICE;
import static android.content.Context.WINDOW_SERVICE;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;

import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerImpl;

/**
 * Base class for presentations.
 * <p>
 * A presentation is a special kind of dialog whose purpose is to present
 * content on a secondary display.  A {@link Presentation} is associated with
 * the target {@link Display} at creation time and configures its context and
 * resource configuration according to the display's metrics.
 * </p><p>
 * Notably, the {@link Context} of a presentation is different from the context
 * of its containing {@link Activity}.  It is important to inflate the layout
 * of a presentation and load other resources using the presentation's own context
 * to ensure that assets of the correct size and density for the target display
 * are loaded.
 * </p><p>
 * A presentation is automatically canceled (see {@link Dialog#cancel()}) when
 * the display to which it is attached is removed.  An activity should take
 * care of pausing and resuming whatever content is playing within the presentation
 * whenever the activity itself is paused or resumed.
 * </p>
 *
 * <h3>Choosing a presentation display</h3>
 * <p>
 * Before showing a {@link Presentation} it's important to choose the {@link Display}
 * on which it will appear.  Choosing a presentation display is sometimes difficult
 * because there may be multiple displays attached.  Rather than trying to guess
 * which display is best, an application should let the system choose a suitable
 * presentation display.
 * </p><p>
 * There are two main ways to choose a {@link Display}.
 * </p>
 *
 * <h4>Using the media router to choose a presentation display</h4>
 * <p>
 * The easiest way to choose a presentation display is to use the
 * {@link android.media.MediaRouter MediaRouter} API.  The media router service keeps
 * track of which audio and video routes are available on the system.
 * The media router sends notifications whenever routes are selected or unselected
 * or when the preferred presentation display of a route changes.
 * So an application can simply watch for these notifications and show or dismiss
 * a presentation on the preferred presentation display automatically.
 * </p><p>
 * The preferred presentation display is the display that the media router recommends
 * that the application should use if it wants to show content on the secondary display.
 * Sometimes there may not be a preferred presentation display in which
 * case the application should show its content locally without using a presentation.
 * </p><p>
 * Here's how to use the media router to create and show a presentation on the preferred
 * presentation display using {@link android.media.MediaRouter.RouteInfo#getPresentationDisplay()}.
 * </p>
 * <pre>
 * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
 * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
 * if (route != null) {
 *     Display presentationDisplay = route.getPresentationDisplay();
 *     if (presentationDisplay != null) {
 *         Presentation presentation = new MyPresentation(context, presentationDisplay);
 *         presentation.show();
 *     }
 * }</pre>
 * <p>
 * The following sample code from <code>ApiDemos</code> demonstrates how to use the media
 * router to automatically switch between showing content in the main activity and showing
 * the content in a presentation when a presentation display is available.
 * </p>
 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java
 *      activity}
 *
 * <h4>Using the display manager to choose a presentation display</h4>
 * <p>
 * Another way to choose a presentation display is to use the {@link DisplayManager} API
 * directly.  The display manager service provides functions to enumerate and describe all
 * displays that are attached to the system including displays that may be used
 * for presentations.
 * </p><p>
 * The display manager keeps track of all displays in the system.  However, not all
 * displays are appropriate for showing presentations.  For example, if an activity
 * attempted to show a presentation on the main display it might obscure its own content
 * (it's like opening a dialog on top of your activity).  Creating a presentation on the main
 * display will result in {@link android.view.WindowManager.InvalidDisplayException} being thrown
 * when invoking {@link #show()}.
 * </p><p>
 * Here's how to identify suitable displays for showing presentations using
 * {@link DisplayManager#getDisplays(String)} and the
 * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION} category.
 * </p>
 * <pre>
 * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
 * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
 * if (presentationDisplays.length > 0) {
 *     // If there is more than one suitable presentation display, then we could consider
 *     // giving the user a choice.  For this example, we simply choose the first display
 *     // which is the one the system recommends as the preferred presentation display.
 *     Display display = presentationDisplays[0];
 *     Presentation presentation = new MyPresentation(context, presentationDisplay);
 *     presentation.show();
 * }</pre>
 * <p>
 * The following sample code from <code>ApiDemos</code> demonstrates how to use the display
 * manager to enumerate displays and show content on multiple presentation displays
 * simultaneously.
 * </p>
 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
 *      activity}
 *
 * @see android.media.MediaRouter#ROUTE_TYPE_LIVE_VIDEO for information on about live
 * video routes and how to obtain the preferred presentation display for the
 * current media route.
 * @see DisplayManager for information on how to enumerate displays and receive
 * notifications when displays are added or removed.
 */
public class Presentation extends Dialog {
    private static final String TAG = "Presentation";

    private static final int MSG_CANCEL = 1;

    private final Display mDisplay;
    private final DisplayManager mDisplayManager;
    private final IBinder mToken = new Binder();

    /**
     * Creates a new presentation that is attached to the specified display
     * using the default theme.
     *
     * @param outerContext The context of the application that is showing the presentation.
     * The presentation will create its own context (see {@link #getContext()}) based
     * on this context and information about the associated display.
     * @param display The display to which the presentation should be attached.
     */
    public Presentation(Context outerContext, Display display) {
        this(outerContext, display, 0);
    }

    /**
     * Creates a new presentation that is attached to the specified display
     * using the optionally specified theme.
     *
     * @param outerContext The context of the application that is showing the presentation.
     * The presentation will create its own context (see {@link #getContext()}) based
     * on this context and information about the associated display.
     * @param display The display to which the presentation should be attached.
     * @param theme A style resource describing the theme to use for the window.
     * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
     * Style and Theme Resources</a> for more information about defining and using
     * styles.  This theme is applied on top of the current theme in
     * <var>outerContext</var>.  If 0, the default presentation theme will be used.
     */
    public Presentation(Context outerContext, Display display, int theme) {
        super(createPresentationContext(outerContext, display, theme), theme, false);

        mDisplay = display;
        mDisplayManager = (DisplayManager)getContext().getSystemService(DISPLAY_SERVICE);

        final Window w = getWindow();
        final WindowManager.LayoutParams attr = w.getAttributes();
        attr.token = mToken;
        w.setAttributes(attr);
        w.setGravity(Gravity.FILL);
        w.setType(TYPE_PRESENTATION);
        setCanceledOnTouchOutside(false);
    }

    /**
     * Gets the {@link Display} that this presentation appears on.
     *
     * @return The display.
     */
    public Display getDisplay() {
        return mDisplay;
    }

    /**
     * Gets the {@link Resources} that should be used to inflate the layout of this presentation.
     * This resources object has been configured according to the metrics of the
     * display that the presentation appears on.
     *
     * @return The presentation resources object.
     */
    public Resources getResources() {
        return getContext().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

        // Since we were not watching for display changes until just now, there is a
        // chance that the display metrics have changed.  If so, we will need to
        // dismiss the presentation immediately.  This case is expected
        // to be rare but surprising, so we'll write a log message about it.
        if (!isConfigurationStillValid()) {
            Log.i(TAG, "Presentation is being dismissed because the "
                    + "display metrics have changed since it was created.");
            mHandler.sendEmptyMessage(MSG_CANCEL);
        }
    }

    @Override
    protected void onStop() {
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        super.onStop();
    }

    /**
     * Inherited from {@link Dialog#show}. Will throw
     * {@link android.view.WindowManager.InvalidDisplayException} if the specified secondary
     * {@link Display} can't be found or if it does not have {@link Display#FLAG_PRESENTATION} set.
     */
    @Override
    public void show() {
        super.show();
    }

    /**
     * Called by the system when the {@link Display} to which the presentation
     * is attached has been removed.
     *
     * The system automatically calls {@link #cancel} to dismiss the presentation
     * after sending this event.
     *
     * @see #getDisplay
     */
    public void onDisplayRemoved() {
    }

    /**
     * Called by the system when the properties of the {@link Display} to which
     * the presentation is attached have changed.
     *
     * If the display metrics have changed (for example, if the display has been
     * resized or rotated), then the system automatically calls
     * {@link #cancel} to dismiss the presentation.
     *
     * @see #getDisplay
     */
    public void onDisplayChanged() {
    }

    private void handleDisplayRemoved() {
        onDisplayRemoved();
        cancel();
    }

    private void handleDisplayChanged() {
        onDisplayChanged();

        // We currently do not support configuration changes for presentations
        // (although we could add that feature with a bit more work).
        // If the display metrics have changed in any way then the current configuration
        // is invalid and the application must recreate the presentation to get
        // a new context.
        if (!isConfigurationStillValid()) {
            Log.i(TAG, "Presentation is being dismissed because the "
                    + "display metrics have changed since it was created.");
            cancel();
        }
    }

    private boolean isConfigurationStillValid() {
        DisplayMetrics dm = new DisplayMetrics();
        mDisplay.getMetrics(dm);
        return dm.equalsPhysical(getResources().getDisplayMetrics());
    }

    @UnsupportedAppUsage
    private static Context createPresentationContext(
            Context outerContext, Display display, int theme) {
        if (outerContext == null) {
            throw new IllegalArgumentException("outerContext must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }

        Context displayContext = outerContext.createDisplayContext(display);
        if (theme == 0) {
            TypedValue outValue = new TypedValue();
            displayContext.getTheme().resolveAttribute(
                    com.android.internal.R.attr.presentationTheme, outValue, true);
            theme = outValue.resourceId;
        }

        // Derive the display's window manager from the outer window manager.
        // We do this because the outer window manager have some extra information
        // such as the parent window, which is important if the presentation uses
        // an application window type.
        final WindowManagerImpl outerWindowManager =
                (WindowManagerImpl)outerContext.getSystemService(WINDOW_SERVICE);
        final WindowManagerImpl displayWindowManager =
                outerWindowManager.createPresentationWindowManager(displayContext);
        return new ContextThemeWrapper(displayContext, theme) {
            @Override
            public Object getSystemService(String name) {
                if (WINDOW_SERVICE.equals(name)) {
                    return displayWindowManager;
                }
                return super.getSystemService(name);
            }
        };
    }

    private final DisplayListener mDisplayListener = new DisplayListener() {
        @Override
        public void onDisplayAdded(int displayId) {
        }

        @Override
        public void onDisplayRemoved(int displayId) {
            if (displayId == mDisplay.getDisplayId()) {
                handleDisplayRemoved();
            }
        }

        @Override
        public void onDisplayChanged(int displayId) {
            if (displayId == mDisplay.getDisplayId()) {
                handleDisplayChanged();
            }
        }
    };

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_CANCEL:
                    cancel();
                    break;
            }
        }
    };
}