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