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 cb.setEnabled((display.getFlags() & Display.FLAG_PRESENTATION) != 0); 399 400 TextView tv = (TextView)v.findViewById(R.id.display_id); 401 tv.setText(v.getContext().getResources().getString( 402 R.string.presentation_display_id_text, displayId, display.getName())); 403 404 Button b = (Button)v.findViewById(R.id.info); 405 b.setTag(display); 406 b.setOnClickListener(PresentationActivity.this); 407 408 Spinner s = (Spinner)v.findViewById(R.id.modes); 409 Display.Mode[] modes = display.getSupportedModes(); 410 if (contents == null || modes.length == 1) { 411 s.setVisibility(View.GONE); 412 s.setAdapter(null); 413 } else { 414 ArrayAdapter<String> modeAdapter = new ArrayAdapter<String>(mContext, 415 android.R.layout.simple_list_item_1); 416 s.setVisibility(View.VISIBLE); 417 s.setAdapter(modeAdapter); 418 s.setTag(display); 419 s.setOnItemSelectedListener(PresentationActivity.this); 420 421 modeAdapter.add("<default mode>"); 422 423 for (Display.Mode mode : modes) { 424 modeAdapter.add(String.format("Mode %d: %dx%d/%.1ffps", 425 mode.getModeId(), 426 mode.getPhysicalWidth(), mode.getPhysicalHeight(), 427 mode.getRefreshRate())); 428 if (contents.displayModeId == mode.getModeId()) { 429 s.setSelection(modeAdapter.getCount() - 1); 430 } 431 } 432 } 433 434 return v; 435 } 436 437 /** 438 * Update the contents of the display list adapter to show 439 * information about all current displays. 440 */ updateContents()441 public void updateContents() { 442 clear(); 443 444 String displayCategory = getDisplayCategory(); 445 Display[] displays = mDisplayManager.getDisplays(displayCategory); 446 addAll(displays); 447 448 Log.d(TAG, "There are currently " + displays.length + " displays connected."); 449 for (Display display : displays) { 450 Log.d(TAG, " " + display); 451 } 452 } 453 getDisplayCategory()454 private String getDisplayCategory() { 455 return mShowAllDisplaysCheckbox.isChecked() ? null : 456 DisplayManager.DISPLAY_CATEGORY_PRESENTATION; 457 } 458 } 459 460 /** 461 * The presentation to show on the secondary display. 462 * 463 * Note that the presentation display may have different metrics from the display on which 464 * the main activity is showing so we must be careful to use the presentation's 465 * own {@link Context} whenever we load resources. 466 */ 467 private final class DemoPresentation extends Presentation { 468 469 final DemoPresentationContents mContents; 470 DemoPresentation(Context context, Display display, DemoPresentationContents contents)471 public DemoPresentation(Context context, Display display, 472 DemoPresentationContents contents) { 473 super(context, display); 474 mContents = contents; 475 } 476 477 /** 478 * Sets the preferred display mode id for the presentation. 479 */ setPreferredDisplayMode(int modeId)480 public void setPreferredDisplayMode(int modeId) { 481 mContents.displayModeId = modeId; 482 483 WindowManager.LayoutParams params = getWindow().getAttributes(); 484 params.preferredDisplayModeId = modeId; 485 getWindow().setAttributes(params); 486 } 487 488 @Override onCreate(Bundle savedInstanceState)489 protected void onCreate(Bundle savedInstanceState) { 490 // Be sure to call the super class. 491 super.onCreate(savedInstanceState); 492 493 // Get the resources for the context of the presentation. 494 // Notice that we are getting the resources from the context of the presentation. 495 Resources r = getContext().getResources(); 496 497 // Inflate the layout. 498 setContentView(R.layout.presentation_content); 499 500 final Display display = getDisplay(); 501 final int displayId = display.getDisplayId(); 502 final int photo = mContents.photo; 503 504 // Show a caption to describe what's going on. 505 TextView text = (TextView)findViewById(R.id.text); 506 text.setText(r.getString(R.string.presentation_photo_text, 507 photo, displayId, display.getName())); 508 509 // Show a n image for visual interest. 510 ImageView image = (ImageView)findViewById(R.id.image); 511 image.setImageDrawable(r.getDrawable(PHOTOS[photo])); 512 513 GradientDrawable drawable = new GradientDrawable(); 514 drawable.setShape(GradientDrawable.RECTANGLE); 515 drawable.setGradientType(GradientDrawable.RADIAL_GRADIENT); 516 517 // Set the background to a random gradient. 518 Point p = new Point(); 519 getDisplay().getSize(p); 520 drawable.setGradientRadius(Math.max(p.x, p.y) / 2); 521 drawable.setColors(mContents.colors); 522 findViewById(android.R.id.content).setBackground(drawable); 523 } 524 } 525 526 /** 527 * Information about the content we want to show in the presentation. 528 */ 529 private final static class DemoPresentationContents implements Parcelable { 530 final int photo; 531 final int[] colors; 532 int displayModeId; 533 534 public static final Creator<DemoPresentationContents> CREATOR = 535 new Creator<DemoPresentationContents>() { 536 @Override 537 public DemoPresentationContents createFromParcel(Parcel in) { 538 return new DemoPresentationContents(in); 539 } 540 541 @Override 542 public DemoPresentationContents[] newArray(int size) { 543 return new DemoPresentationContents[size]; 544 } 545 }; 546 DemoPresentationContents(int photo)547 public DemoPresentationContents(int photo) { 548 this.photo = photo; 549 colors = new int[] { 550 ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000, 551 ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000 }; 552 } 553 DemoPresentationContents(Parcel in)554 private DemoPresentationContents(Parcel in) { 555 photo = in.readInt(); 556 colors = new int[] { in.readInt(), in.readInt() }; 557 displayModeId = in.readInt(); 558 } 559 560 @Override describeContents()561 public int describeContents() { 562 return 0; 563 } 564 565 @Override writeToParcel(Parcel dest, int flags)566 public void writeToParcel(Parcel dest, int flags) { 567 dest.writeInt(photo); 568 dest.writeInt(colors[0]); 569 dest.writeInt(colors[1]); 570 dest.writeInt(displayModeId); 571 } 572 } 573 } 574 //END_INCLUDE(activity) 575