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 com.example.android.apis.app;
18 
19 // Need the following import to get access to the app resources, since this
20 // class is in a sub-package.
21 import com.example.android.apis.R;
22 
23 import android.app.Activity;
24 import android.app.AlertDialog;
25 import android.app.Presentation;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.res.Resources;
29 import android.graphics.Point;
30 import android.graphics.drawable.GradientDrawable;
31 import android.hardware.display.DisplayManager;
32 import android.os.Bundle;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.os.Parcelable.Creator;
36 import android.util.Log;
37 import android.util.SparseArray;
38 import android.view.Display;
39 import android.view.View;
40 import android.view.View.OnClickListener;
41 import android.view.ViewGroup;
42 import android.view.WindowManager;
43 import android.widget.CheckBox;
44 import android.widget.CompoundButton;
45 import android.widget.CompoundButton.OnCheckedChangeListener;
46 import android.widget.AdapterView;
47 import android.widget.AdapterView.OnItemSelectedListener;
48 import android.widget.ArrayAdapter;
49 import android.widget.Button;
50 import android.widget.ImageView;
51 import android.widget.ListView;
52 import android.widget.Spinner;
53 import android.widget.TextView;
54 
55 //BEGIN_INCLUDE(activity)
56 /**
57  * <h3>Presentation Activity</h3>
58  *
59  * <p>
60  * This demonstrates how to create an activity that shows some content
61  * on a secondary display using a {@link Presentation}.
62  * </p><p>
63  * The activity uses the {@link DisplayManager} API to enumerate displays.
64  * When the user selects a display, the activity opens a {@link Presentation}
65  * on that display.  We show a different photograph in each presentation
66  * on a unique background along with a label describing the display.
67  * We also write information about displays and display-related events to
68  * the Android log which you can read using <code>adb logcat</code>.
69  * </p><p>
70  * You can try this out using an HDMI or Wifi display or by using the
71  * "Simulate secondary displays" feature in Development Settings to create a few
72  * simulated secondary displays.  Each display will appear in the list along with a
73  * checkbox to show a presentation on that display.
74  * </p><p>
75  * See also the {@link PresentationWithMediaRouterActivity} sample which
76  * uses the media router to automatically select a secondary display
77  * on which to show content based on the currently selected route.
78  * </p>
79  */
80 public class PresentationActivity extends Activity
81         implements OnCheckedChangeListener, OnClickListener, OnItemSelectedListener {
82     private final String TAG = "PresentationActivity";
83 
84     // Key for storing saved instance state.
85     private static final String PRESENTATION_KEY = "presentation";
86 
87     // The content that we want to show on the presentation.
88     private static final int[] PHOTOS = new int[] {
89         R.drawable.frantic,
90         R.drawable.photo1, R.drawable.photo2, R.drawable.photo3,
91         R.drawable.photo4, R.drawable.photo5, R.drawable.photo6,
92         R.drawable.sample_4,
93     };
94 
95     private DisplayManager mDisplayManager;
96     private DisplayListAdapter mDisplayListAdapter;
97     private CheckBox mShowAllDisplaysCheckbox;
98     private ListView mListView;
99     private int mNextImageNumber;
100 
101     // List of presentation contents indexed by displayId.
102     // This state persists so that we can restore the old presentation
103     // contents when the activity is paused or resumed.
104     private SparseArray<DemoPresentationContents> mSavedPresentationContents;
105 
106     // List of all currently visible presentations indexed by display id.
107     private final SparseArray<DemoPresentation> mActivePresentations =
108             new SparseArray<DemoPresentation>();
109 
110     /**
111      * Initialization of the Activity after it is first created.  Must at least
112      * call {@link android.app.Activity#setContentView setContentView()} to
113      * describe what is to be displayed in the screen.
114      */
115     @Override
onCreate(Bundle savedInstanceState)116     protected void onCreate(Bundle savedInstanceState) {
117         // Be sure to call the super class.
118         super.onCreate(savedInstanceState);
119 
120         // Restore saved instance state.
121         if (savedInstanceState != null) {
122             mSavedPresentationContents =
123                     savedInstanceState.getSparseParcelableArray(PRESENTATION_KEY);
124         } else {
125             mSavedPresentationContents = new SparseArray<DemoPresentationContents>();
126         }
127 
128         // Get the display manager service.
129         mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
130 
131         // See assets/res/any/layout/presentation_activity.xml for this
132         // view layout definition, which is being set here as
133         // the content of our screen.
134         setContentView(R.layout.presentation_activity);
135 
136         // Set up checkbox to toggle between showing all displays or only presentation displays.
137         mShowAllDisplaysCheckbox = (CheckBox)findViewById(R.id.show_all_displays);
138         mShowAllDisplaysCheckbox.setOnCheckedChangeListener(this);
139 
140         // Set up the list of displays.
141         mDisplayListAdapter = new DisplayListAdapter(this);
142         mListView = (ListView)findViewById(R.id.display_list);
143         mListView.setAdapter(mDisplayListAdapter);
144     }
145 
146     @Override
onResume()147     protected void onResume() {
148         // Be sure to call the super class.
149         super.onResume();
150 
151         // Update our list of displays on resume.
152         mDisplayListAdapter.updateContents();
153 
154         // Restore presentations from before the activity was paused.
155         final int numDisplays = mDisplayListAdapter.getCount();
156         for (int i = 0; i < numDisplays; i++) {
157             final Display display = mDisplayListAdapter.getItem(i);
158             final DemoPresentationContents contents =
159                     mSavedPresentationContents.get(display.getDisplayId());
160             if (contents != null) {
161                 showPresentation(display, contents);
162             }
163         }
164         mSavedPresentationContents.clear();
165 
166         // Register to receive events from the display manager.
167         mDisplayManager.registerDisplayListener(mDisplayListener, null);
168     }
169 
170     @Override
onPause()171     protected void onPause() {
172         // Be sure to call the super class.
173         super.onPause();
174 
175         // Unregister from the display manager.
176         mDisplayManager.unregisterDisplayListener(mDisplayListener);
177 
178         // Dismiss all of our presentations but remember their contents.
179         Log.d(TAG, "Activity is being paused.  Dismissing all active presentation.");
180         for (int i = 0; i < mActivePresentations.size(); i++) {
181             DemoPresentation presentation = mActivePresentations.valueAt(i);
182             int displayId = mActivePresentations.keyAt(i);
183             mSavedPresentationContents.put(displayId, presentation.mContents);
184             presentation.dismiss();
185         }
186         mActivePresentations.clear();
187     }
188 
189     @Override
onSaveInstanceState(Bundle outState)190     protected void onSaveInstanceState(Bundle outState) {
191         // Be sure to call the super class.
192         super.onSaveInstanceState(outState);
193         outState.putSparseParcelableArray(PRESENTATION_KEY, mSavedPresentationContents);
194     }
195 
196     /**
197      * Shows a {@link Presentation} on the specified display.
198      */
showPresentation(Display display, DemoPresentationContents contents)199     private void showPresentation(Display display, DemoPresentationContents contents) {
200         final int displayId = display.getDisplayId();
201         if (mActivePresentations.get(displayId) != null) {
202             return;
203         }
204 
205         Log.d(TAG, "Showing presentation photo #" + contents.photo
206                 + " on display #" + displayId + ".");
207 
208         DemoPresentation presentation = new DemoPresentation(this, display, contents);
209         presentation.show();
210         presentation.setOnDismissListener(mOnDismissListener);
211         mActivePresentations.put(displayId, presentation);
212     }
213 
214     /**
215      * Hides a {@link Presentation} on the specified display.
216      */
hidePresentation(Display display)217     private void hidePresentation(Display display) {
218         final int displayId = display.getDisplayId();
219         DemoPresentation presentation = mActivePresentations.get(displayId);
220         if (presentation == null) {
221             return;
222         }
223 
224         Log.d(TAG, "Dismissing presentation on display #" + displayId + ".");
225 
226         presentation.dismiss();
227         mActivePresentations.delete(displayId);
228     }
229 
230     /**
231      * Sets the display mode of the {@link Presentation} on the specified display
232      * if it is already shown.
233      */
setPresentationDisplayMode(Display display, int displayModeId)234     private void setPresentationDisplayMode(Display display, int displayModeId) {
235         final int displayId = display.getDisplayId();
236         DemoPresentation presentation = mActivePresentations.get(displayId);
237         if (presentation == null) {
238             return;
239         }
240 
241         presentation.setPreferredDisplayMode(displayModeId);
242     }
243 
getNextPhoto()244     private int getNextPhoto() {
245         final int photo = mNextImageNumber;
246         mNextImageNumber = (mNextImageNumber + 1) % PHOTOS.length;
247         return photo;
248     }
249 
250     /**
251      * Called when the show all displays checkbox is toggled or when
252      * an item in the list of displays is checked or unchecked.
253      */
254     @Override
onCheckedChanged(CompoundButton buttonView, boolean isChecked)255     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
256         if (buttonView == mShowAllDisplaysCheckbox) {
257             // Show all displays checkbox was toggled.
258             mDisplayListAdapter.updateContents();
259         } else {
260             // Display item checkbox was toggled.
261             final Display display = (Display)buttonView.getTag();
262             if (isChecked) {
263                 DemoPresentationContents contents = new DemoPresentationContents(getNextPhoto());
264                 showPresentation(display, contents);
265             } else {
266                 hidePresentation(display);
267             }
268             mDisplayListAdapter.updateContents();
269         }
270     }
271 
272     /**
273      * Called when the Info button next to a display is clicked to show information
274      * about the display.
275      */
276     @Override
onClick(View v)277     public void onClick(View v) {
278         Context context = v.getContext();
279         AlertDialog.Builder builder = new AlertDialog.Builder(context);
280         final Display display = (Display)v.getTag();
281         Resources r = context.getResources();
282         AlertDialog alert = builder
283                 .setTitle(r.getString(
284                         R.string.presentation_alert_info_text, display.getDisplayId()))
285                 .setMessage(display.toString())
286                 .setNeutralButton(R.string.presentation_alert_dismiss_text,
287                         new DialogInterface.OnClickListener() {
288                             @Override
289                             public void onClick(DialogInterface dialog, int which) {
290                                 dialog.dismiss();
291                             }
292                     })
293                 .create();
294         alert.show();
295     }
296 
297     /**
298      * Called when a display mode has been selected.
299      */
300     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)301     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
302         final Display display = (Display)parent.getTag();
303         final Display.Mode[] modes = display.getSupportedModes();
304         setPresentationDisplayMode(display, position >= 1 && position <= modes.length ?
305                 modes[position - 1].getModeId() : 0);
306     }
307 
308     /**
309      * Called when a display mode has been unselected.
310      */
311     @Override
onNothingSelected(AdapterView<?> parent)312     public void onNothingSelected(AdapterView<?> parent) {
313         final Display display = (Display)parent.getTag();
314         setPresentationDisplayMode(display, 0);
315     }
316 
317     /**
318      * Listens for displays to be added, changed or removed.
319      * We use it to update the list and show a new {@link Presentation} when a
320      * display is connected.
321      *
322      * Note that we don't bother dismissing the {@link Presentation} when a
323      * display is removed, although we could.  The presentation API takes care
324      * of doing that automatically for us.
325      */
326     private final DisplayManager.DisplayListener mDisplayListener =
327             new DisplayManager.DisplayListener() {
328         @Override
329         public void onDisplayAdded(int displayId) {
330             Log.d(TAG, "Display #" + displayId + " added.");
331             mDisplayListAdapter.updateContents();
332         }
333 
334         @Override
335         public void onDisplayChanged(int displayId) {
336             Log.d(TAG, "Display #" + displayId + " changed.");
337             mDisplayListAdapter.updateContents();
338         }
339 
340         @Override
341         public void onDisplayRemoved(int displayId) {
342             Log.d(TAG, "Display #" + displayId + " removed.");
343             mDisplayListAdapter.updateContents();
344         }
345     };
346 
347     /**
348      * Listens for when presentations are dismissed.
349      */
350     private final DialogInterface.OnDismissListener mOnDismissListener =
351             new DialogInterface.OnDismissListener() {
352         @Override
353         public void onDismiss(DialogInterface dialog) {
354             DemoPresentation presentation = (DemoPresentation)dialog;
355             int displayId = presentation.getDisplay().getDisplayId();
356             Log.d(TAG, "Presentation on display #" + displayId + " was dismissed.");
357             mActivePresentations.delete(displayId);
358             mDisplayListAdapter.notifyDataSetChanged();
359         }
360     };
361 
362     /**
363      * List adapter.
364      * Shows information about all displays.
365      */
366     private final class DisplayListAdapter extends ArrayAdapter<Display> {
367         final Context mContext;
368 
DisplayListAdapter(Context context)369         public DisplayListAdapter(Context context) {
370             super(context, R.layout.presentation_list_item);
371             mContext = context;
372         }
373 
374         @Override
getView(int position, View convertView, ViewGroup parent)375         public View getView(int position, View convertView, ViewGroup parent) {
376             final View v;
377             if (convertView == null) {
378                 v = ((Activity) mContext).getLayoutInflater().inflate(
379                         R.layout.presentation_list_item, null);
380             } else {
381                 v = convertView;
382             }
383 
384             final Display display = getItem(position);
385             final int displayId = display.getDisplayId();
386 
387             DemoPresentation presentation = mActivePresentations.get(displayId);
388             DemoPresentationContents contents = presentation != null ?
389                     presentation.mContents : null;
390             if (contents == null) {
391                 contents = mSavedPresentationContents.get(displayId);
392             }
393 
394             CheckBox cb = (CheckBox)v.findViewById(R.id.checkbox_presentation);
395             cb.setTag(display);
396             cb.setOnCheckedChangeListener(PresentationActivity.this);
397             cb.setChecked(contents != null);
398 
399             TextView tv = (TextView)v.findViewById(R.id.display_id);
400             tv.setText(v.getContext().getResources().getString(
401                     R.string.presentation_display_id_text, displayId, display.getName()));
402 
403             Button b = (Button)v.findViewById(R.id.info);
404             b.setTag(display);
405             b.setOnClickListener(PresentationActivity.this);
406 
407             Spinner s = (Spinner)v.findViewById(R.id.modes);
408             Display.Mode[] modes = display.getSupportedModes();
409             if (contents == null || modes.length == 1) {
410                 s.setVisibility(View.GONE);
411                 s.setAdapter(null);
412             } else {
413                 ArrayAdapter<String> modeAdapter = new ArrayAdapter<String>(mContext,
414                         android.R.layout.simple_list_item_1);
415                 s.setVisibility(View.VISIBLE);
416                 s.setAdapter(modeAdapter);
417                 s.setTag(display);
418                 s.setOnItemSelectedListener(PresentationActivity.this);
419 
420                 modeAdapter.add("<default mode>");
421 
422                 for (Display.Mode mode : modes) {
423                     modeAdapter.add(String.format("Mode %d: %dx%d/%.1ffps",
424                             mode.getModeId(),
425                             mode.getPhysicalWidth(), mode.getPhysicalHeight(),
426                             mode.getRefreshRate()));
427                     if (contents.displayModeId == mode.getModeId()) {
428                         s.setSelection(modeAdapter.getCount() - 1);
429                     }
430                 }
431             }
432 
433             return v;
434         }
435 
436         /**
437          * Update the contents of the display list adapter to show
438          * information about all current displays.
439          */
updateContents()440         public void updateContents() {
441             clear();
442 
443             String displayCategory = getDisplayCategory();
444             Display[] displays = mDisplayManager.getDisplays(displayCategory);
445             addAll(displays);
446 
447             Log.d(TAG, "There are currently " + displays.length + " displays connected.");
448             for (Display display : displays) {
449                 Log.d(TAG, "  " + display);
450             }
451         }
452 
getDisplayCategory()453         private String getDisplayCategory() {
454             return mShowAllDisplaysCheckbox.isChecked() ? null :
455                 DisplayManager.DISPLAY_CATEGORY_PRESENTATION;
456         }
457     }
458 
459     /**
460      * The presentation to show on the secondary display.
461      *
462      * Note that the presentation display may have different metrics from the display on which
463      * the main activity is showing so we must be careful to use the presentation's
464      * own {@link Context} whenever we load resources.
465      */
466     private final class DemoPresentation extends Presentation {
467 
468         final DemoPresentationContents mContents;
469 
DemoPresentation(Context context, Display display, DemoPresentationContents contents)470         public DemoPresentation(Context context, Display display,
471                 DemoPresentationContents contents) {
472             super(context, display);
473             mContents = contents;
474         }
475 
476         /**
477          * Sets the preferred display mode id for the presentation.
478          */
setPreferredDisplayMode(int modeId)479         public void setPreferredDisplayMode(int modeId) {
480             mContents.displayModeId = modeId;
481 
482             WindowManager.LayoutParams params = getWindow().getAttributes();
483             params.preferredDisplayModeId = modeId;
484             getWindow().setAttributes(params);
485         }
486 
487         @Override
onCreate(Bundle savedInstanceState)488         protected void onCreate(Bundle savedInstanceState) {
489             // Be sure to call the super class.
490             super.onCreate(savedInstanceState);
491 
492             // Get the resources for the context of the presentation.
493             // Notice that we are getting the resources from the context of the presentation.
494             Resources r = getContext().getResources();
495 
496             // Inflate the layout.
497             setContentView(R.layout.presentation_content);
498 
499             final Display display = getDisplay();
500             final int displayId = display.getDisplayId();
501             final int photo = mContents.photo;
502 
503             // Show a caption to describe what's going on.
504             TextView text = (TextView)findViewById(R.id.text);
505             text.setText(r.getString(R.string.presentation_photo_text,
506                     photo, displayId, display.getName()));
507 
508             // Show a n image for visual interest.
509             ImageView image = (ImageView)findViewById(R.id.image);
510             image.setImageDrawable(r.getDrawable(PHOTOS[photo]));
511 
512             GradientDrawable drawable = new GradientDrawable();
513             drawable.setShape(GradientDrawable.RECTANGLE);
514             drawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
515 
516             // Set the background to a random gradient.
517             Point p = new Point();
518             getDisplay().getSize(p);
519             drawable.setGradientRadius(Math.max(p.x, p.y) / 2);
520             drawable.setColors(mContents.colors);
521             findViewById(android.R.id.content).setBackground(drawable);
522         }
523     }
524 
525     /**
526      * Information about the content we want to show in the presentation.
527      */
528     private final static class DemoPresentationContents implements Parcelable {
529         final int photo;
530         final int[] colors;
531         int displayModeId;
532 
533         public static final Creator<DemoPresentationContents> CREATOR =
534                 new Creator<DemoPresentationContents>() {
535             @Override
536             public DemoPresentationContents createFromParcel(Parcel in) {
537                 return new DemoPresentationContents(in);
538             }
539 
540             @Override
541             public DemoPresentationContents[] newArray(int size) {
542                 return new DemoPresentationContents[size];
543             }
544         };
545 
DemoPresentationContents(int photo)546         public DemoPresentationContents(int photo) {
547             this.photo = photo;
548             colors = new int[] {
549                     ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000,
550                     ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000 };
551         }
552 
DemoPresentationContents(Parcel in)553         private DemoPresentationContents(Parcel in) {
554             photo = in.readInt();
555             colors = new int[] { in.readInt(), in.readInt() };
556             displayModeId = in.readInt();
557         }
558 
559         @Override
describeContents()560         public int describeContents() {
561             return 0;
562         }
563 
564         @Override
writeToParcel(Parcel dest, int flags)565         public void writeToParcel(Parcel dest, int flags) {
566             dest.writeInt(photo);
567             dest.writeInt(colors[0]);
568             dest.writeInt(colors[1]);
569             dest.writeInt(displayModeId);
570         }
571     }
572 }
573 //END_INCLUDE(activity)
574