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.android.server.display; 18 19 import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; 20 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.graphics.SurfaceTexture; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.provider.Settings; 28 import android.text.TextUtils; 29 import android.util.DisplayMetrics; 30 import android.util.Slog; 31 import android.view.Display; 32 import android.view.Gravity; 33 import android.view.Surface; 34 import android.view.SurfaceControl; 35 36 import com.android.internal.util.DumpUtils; 37 import com.android.internal.util.IndentingPrintWriter; 38 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.List; 43 import java.util.regex.Matcher; 44 import java.util.regex.Pattern; 45 46 /** 47 * A display adapter that uses overlay windows to simulate secondary displays 48 * for development purposes. Use Development Settings to enable one or more 49 * overlay displays. 50 * <p> 51 * This object has two different handlers (which may be the same) which must not 52 * get confused. The main handler is used to posting messages to the display manager 53 * service as usual. The UI handler is only used by the {@link OverlayDisplayWindow}. 54 * </p><p> 55 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. 56 * </p><p> 57 * This adapter is configured via the 58 * {@link android.provider.Settings.Global#OVERLAY_DISPLAY_DEVICES} setting. This setting should be 59 * formatted as follows: 60 * <pre> 61 * [display1];[display2];... 62 * </pre> 63 * with each display specified as: 64 * <pre> 65 * [mode1]|[mode2]|...,[flag1],[flag2],... 66 * </pre> 67 * with each mode specified as: 68 * <pre> 69 * [width]x[height]/[densityDpi] 70 * </pre> 71 * Supported flags: 72 * <ul> 73 * <li><pre>secure</pre>: creates a secure display</li> 74 * <li><pre>own_content_only</pre>: only shows this display's own content</li> 75 * <li><pre>should_show_system_decorations</pre>: supports system decorations</li> 76 * </ul> 77 * </p><p> 78 * Example: 79 * <ul> 80 * <li><code>1280x720/213</code>: make one overlay that is 1280x720 at 213dpi.</li> 81 * <li><code>1920x1080/320,secure;1280x720/213</code>: make two overlays, the first at 1080p and 82 * secure; the second at 720p.</li> 83 * <li><code>1920x1080/320|3840x2160/640</code>: make one overlay that is 1920x1080 at 84 * 213dpi by default, but can also be upscaled to 3840x2160 at 640dpi by the system if the 85 * display device allows.</li> 86 * <li>If the value is empty, then no overlay display devices are created.</li> 87 * </ul></p> 88 */ 89 final class OverlayDisplayAdapter extends DisplayAdapter { 90 static final String TAG = "OverlayDisplayAdapter"; 91 static final boolean DEBUG = false; 92 93 /** 94 * When this flag is set, the overlay display is considered secure. 95 * @see DisplayDeviceInfo#FLAG_SECURE 96 */ 97 private static final String OVERLAY_DISPLAY_FLAG_SECURE = "secure"; 98 99 /** 100 * When this flag is set, only show this display's own content; do not mirror the content of 101 * another display. 102 * @see DisplayDeviceInfo#FLAG_OWN_CONTENT_ONLY 103 */ 104 private static final String OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY = "own_content_only"; 105 106 /** 107 * When this flag is set, the overlay display should support system decorations. 108 * @see DisplayDeviceInfo#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS 109 */ 110 private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 111 "should_show_system_decorations"; 112 113 private static final int MIN_WIDTH = 100; 114 private static final int MIN_HEIGHT = 100; 115 private static final int MAX_WIDTH = 4096; 116 private static final int MAX_HEIGHT = 4096; 117 118 private static final String DISPLAY_SPLITTER = ";"; 119 private static final String MODE_SPLITTER = "\\|"; 120 private static final String FLAG_SPLITTER = ","; 121 122 private static final Pattern DISPLAY_PATTERN = Pattern.compile("([^,]+)(,[,_a-z]+)*"); 123 private static final Pattern MODE_PATTERN = Pattern.compile("(\\d+)x(\\d+)/(\\d+)"); 124 125 // Unique id prefix for overlay displays. 126 private static final String UNIQUE_ID_PREFIX = "overlay:"; 127 128 private final Handler mUiHandler; 129 private final ArrayList<OverlayDisplayHandle> mOverlays = 130 new ArrayList<OverlayDisplayHandle>(); 131 private String mCurrentOverlaySetting = ""; 132 133 // Called with SyncRoot lock held. OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, Handler uiHandler)134 public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, 135 Context context, Handler handler, Listener listener, Handler uiHandler) { 136 super(syncRoot, context, handler, listener, TAG); 137 mUiHandler = uiHandler; 138 } 139 140 @Override dumpLocked(PrintWriter pw)141 public void dumpLocked(PrintWriter pw) { 142 super.dumpLocked(pw); 143 144 pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting); 145 pw.println("mOverlays: size=" + mOverlays.size()); 146 for (OverlayDisplayHandle overlay : mOverlays) { 147 overlay.dumpLocked(pw); 148 } 149 } 150 151 @Override registerLocked()152 public void registerLocked() { 153 super.registerLocked(); 154 155 getHandler().post(new Runnable() { 156 @Override 157 public void run() { 158 getContext().getContentResolver().registerContentObserver( 159 Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES), 160 true, new ContentObserver(getHandler()) { 161 @Override 162 public void onChange(boolean selfChange) { 163 updateOverlayDisplayDevices(); 164 } 165 }); 166 167 updateOverlayDisplayDevices(); 168 } 169 }); 170 } 171 updateOverlayDisplayDevices()172 private void updateOverlayDisplayDevices() { 173 synchronized (getSyncRoot()) { 174 updateOverlayDisplayDevicesLocked(); 175 } 176 } 177 updateOverlayDisplayDevicesLocked()178 private void updateOverlayDisplayDevicesLocked() { 179 String value = Settings.Global.getString(getContext().getContentResolver(), 180 Settings.Global.OVERLAY_DISPLAY_DEVICES); 181 if (value == null) { 182 value = ""; 183 } 184 185 if (value.equals(mCurrentOverlaySetting)) { 186 return; 187 } 188 mCurrentOverlaySetting = value; 189 190 if (!mOverlays.isEmpty()) { 191 Slog.i(TAG, "Dismissing all overlay display devices."); 192 for (OverlayDisplayHandle overlay : mOverlays) { 193 overlay.dismissLocked(); 194 } 195 mOverlays.clear(); 196 } 197 198 int count = 0; 199 for (String part : value.split(DISPLAY_SPLITTER)) { 200 Matcher displayMatcher = DISPLAY_PATTERN.matcher(part); 201 if (displayMatcher.matches()) { 202 if (count >= 4) { 203 Slog.w(TAG, "Too many overlay display devices specified: " + value); 204 break; 205 } 206 String modeString = displayMatcher.group(1); 207 String flagString = displayMatcher.group(2); 208 ArrayList<OverlayMode> modes = new ArrayList<>(); 209 for (String mode : modeString.split(MODE_SPLITTER)) { 210 Matcher modeMatcher = MODE_PATTERN.matcher(mode); 211 if (modeMatcher.matches()) { 212 try { 213 int width = Integer.parseInt(modeMatcher.group(1), 10); 214 int height = Integer.parseInt(modeMatcher.group(2), 10); 215 int densityDpi = Integer.parseInt(modeMatcher.group(3), 10); 216 if (width >= MIN_WIDTH && width <= MAX_WIDTH 217 && height >= MIN_HEIGHT && height <= MAX_HEIGHT 218 && densityDpi >= DisplayMetrics.DENSITY_LOW 219 && densityDpi <= DisplayMetrics.DENSITY_XXXHIGH) { 220 modes.add(new OverlayMode(width, height, densityDpi)); 221 continue; 222 } else { 223 Slog.w(TAG, "Ignoring out-of-range overlay display mode: " + mode); 224 } 225 } catch (NumberFormatException ex) { 226 } 227 } else if (mode.isEmpty()) { 228 continue; 229 } 230 } 231 if (!modes.isEmpty()) { 232 int number = ++count; 233 String name = getContext().getResources().getString( 234 com.android.internal.R.string.display_manager_overlay_display_name, 235 number); 236 int gravity = chooseOverlayGravity(number); 237 OverlayFlags flags = OverlayFlags.parseFlags(flagString); 238 239 Slog.i(TAG, "Showing overlay display device #" + number 240 + ": name=" + name + ", modes=" + Arrays.toString(modes.toArray()) 241 + ", flags=" + flags); 242 243 mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, flags, number)); 244 continue; 245 } 246 } 247 Slog.w(TAG, "Malformed overlay display devices setting: " + value); 248 } 249 } 250 chooseOverlayGravity(int overlayNumber)251 private static int chooseOverlayGravity(int overlayNumber) { 252 switch (overlayNumber) { 253 case 1: 254 return Gravity.TOP | Gravity.LEFT; 255 case 2: 256 return Gravity.BOTTOM | Gravity.RIGHT; 257 case 3: 258 return Gravity.TOP | Gravity.RIGHT; 259 case 4: 260 default: 261 return Gravity.BOTTOM | Gravity.LEFT; 262 } 263 } 264 265 private abstract class OverlayDisplayDevice extends DisplayDevice { 266 private final String mName; 267 private final float mRefreshRate; 268 private final long mDisplayPresentationDeadlineNanos; 269 private final OverlayFlags mFlags; 270 private final List<OverlayMode> mRawModes; 271 private final Display.Mode[] mModes; 272 private final int mDefaultMode; 273 274 private int mState; 275 private SurfaceTexture mSurfaceTexture; 276 private Surface mSurface; 277 private DisplayDeviceInfo mInfo; 278 private int mActiveMode; 279 OverlayDisplayDevice(IBinder displayToken, String name, List<OverlayMode> modes, int activeMode, int defaultMode, float refreshRate, long presentationDeadlineNanos, OverlayFlags flags, int state, SurfaceTexture surfaceTexture, int number)280 OverlayDisplayDevice(IBinder displayToken, String name, 281 List<OverlayMode> modes, int activeMode, int defaultMode, 282 float refreshRate, long presentationDeadlineNanos, 283 OverlayFlags flags, int state, SurfaceTexture surfaceTexture, int number) { 284 super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number, 285 getContext()); 286 mName = name; 287 mRefreshRate = refreshRate; 288 mDisplayPresentationDeadlineNanos = presentationDeadlineNanos; 289 mFlags = flags; 290 mState = state; 291 mSurfaceTexture = surfaceTexture; 292 mRawModes = modes; 293 mModes = new Display.Mode[modes.size()]; 294 for (int i = 0; i < modes.size(); i++) { 295 OverlayMode mode = modes.get(i); 296 mModes[i] = createMode(mode.mWidth, mode.mHeight, refreshRate); 297 } 298 mActiveMode = activeMode; 299 mDefaultMode = defaultMode; 300 } 301 destroyLocked()302 public void destroyLocked() { 303 mSurfaceTexture = null; 304 if (mSurface != null) { 305 mSurface.release(); 306 mSurface = null; 307 } 308 SurfaceControl.destroyDisplay(getDisplayTokenLocked()); 309 } 310 311 @Override hasStableUniqueId()312 public boolean hasStableUniqueId() { 313 return false; 314 } 315 316 @Override performTraversalLocked(SurfaceControl.Transaction t)317 public void performTraversalLocked(SurfaceControl.Transaction t) { 318 if (mSurfaceTexture != null) { 319 if (mSurface == null) { 320 mSurface = new Surface(mSurfaceTexture); 321 } 322 setSurfaceLocked(t, mSurface); 323 } 324 } 325 setStateLocked(int state)326 public void setStateLocked(int state) { 327 mState = state; 328 mInfo = null; 329 } 330 331 @Override getDisplayDeviceInfoLocked()332 public DisplayDeviceInfo getDisplayDeviceInfoLocked() { 333 if (mInfo == null) { 334 Display.Mode mode = mModes[mActiveMode]; 335 OverlayMode rawMode = mRawModes.get(mActiveMode); 336 mInfo = new DisplayDeviceInfo(); 337 mInfo.name = mName; 338 mInfo.uniqueId = getUniqueId(); 339 mInfo.width = mode.getPhysicalWidth(); 340 mInfo.height = mode.getPhysicalHeight(); 341 mInfo.modeId = mode.getModeId(); 342 mInfo.defaultModeId = mModes[0].getModeId(); 343 mInfo.supportedModes = mModes; 344 mInfo.densityDpi = rawMode.mDensityDpi; 345 mInfo.xDpi = rawMode.mDensityDpi; 346 mInfo.yDpi = rawMode.mDensityDpi; 347 mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos + 348 1000000000L / (int) mRefreshRate; // display's deadline + 1 frame 349 mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION; 350 if (mFlags.mSecure) { 351 mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE; 352 } 353 if (mFlags.mOwnContentOnly) { 354 mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY; 355 } 356 if (mFlags.mShouldShowSystemDecorations) { 357 mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; 358 } 359 mInfo.type = Display.TYPE_OVERLAY; 360 mInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL; 361 mInfo.state = mState; 362 // The display is trusted since it is created by system. 363 mInfo.flags |= FLAG_TRUSTED; 364 } 365 return mInfo; 366 } 367 368 @Override setDesiredDisplayModeSpecsLocked( DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs)369 public void setDesiredDisplayModeSpecsLocked( 370 DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) { 371 final int id = displayModeSpecs.baseModeId; 372 int index = -1; 373 if (id == 0) { 374 // Use the default. 375 index = 0; 376 } else { 377 for (int i = 0; i < mModes.length; i++) { 378 if (mModes[i].getModeId() == id) { 379 index = i; 380 break; 381 } 382 } 383 } 384 if (index == -1) { 385 Slog.w(TAG, "Unable to locate mode " + id + ", reverting to default."); 386 index = mDefaultMode; 387 } 388 if (mActiveMode == index) { 389 return; 390 } 391 mActiveMode = index; 392 mInfo = null; 393 sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED); 394 onModeChangedLocked(index); 395 } 396 397 /** 398 * Called when the device switched to a new mode. 399 * 400 * @param index index of the mode in the list of modes 401 */ onModeChangedLocked(int index)402 public abstract void onModeChangedLocked(int index); 403 } 404 405 /** 406 * Functions as a handle for overlay display devices which are created and 407 * destroyed asynchronously. 408 * 409 * Guarded by the {@link DisplayManagerService.SyncRoot} lock. 410 */ 411 private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener { 412 private static final int DEFAULT_MODE_INDEX = 0; 413 414 private final String mName; 415 private final List<OverlayMode> mModes; 416 private final int mGravity; 417 private final OverlayFlags mFlags; 418 private final int mNumber; 419 420 private OverlayDisplayWindow mWindow; 421 private OverlayDisplayDevice mDevice; 422 private int mActiveMode; 423 OverlayDisplayHandle( String name, List<OverlayMode> modes, int gravity, OverlayFlags flags, int number)424 OverlayDisplayHandle( 425 String name, 426 List<OverlayMode> modes, 427 int gravity, 428 OverlayFlags flags, 429 int number) { 430 mName = name; 431 mModes = modes; 432 mGravity = gravity; 433 mFlags = flags; 434 mNumber = number; 435 436 mActiveMode = 0; 437 438 showLocked(); 439 } 440 showLocked()441 private void showLocked() { 442 mUiHandler.post(mShowRunnable); 443 } 444 dismissLocked()445 public void dismissLocked() { 446 mUiHandler.removeCallbacks(mShowRunnable); 447 mUiHandler.post(mDismissRunnable); 448 } 449 onActiveModeChangedLocked(int index)450 private void onActiveModeChangedLocked(int index) { 451 mUiHandler.removeCallbacks(mResizeRunnable); 452 mActiveMode = index; 453 if (mWindow != null) { 454 mUiHandler.post(mResizeRunnable); 455 } 456 } 457 458 // Called on the UI thread. 459 @Override onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, long presentationDeadlineNanos, int state)460 public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, 461 long presentationDeadlineNanos, int state) { 462 synchronized (getSyncRoot()) { 463 IBinder displayToken = SurfaceControl.createDisplay(mName, mFlags.mSecure); 464 mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode, 465 DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos, 466 mFlags, state, surfaceTexture, mNumber) { 467 @Override 468 public void onModeChangedLocked(int index) { 469 onActiveModeChangedLocked(index); 470 } 471 }; 472 473 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED); 474 } 475 } 476 477 // Called on the UI thread. 478 @Override onWindowDestroyed()479 public void onWindowDestroyed() { 480 synchronized (getSyncRoot()) { 481 if (mDevice != null) { 482 mDevice.destroyLocked(); 483 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED); 484 } 485 } 486 } 487 488 // Called on the UI thread. 489 @Override onStateChanged(int state)490 public void onStateChanged(int state) { 491 synchronized (getSyncRoot()) { 492 if (mDevice != null) { 493 mDevice.setStateLocked(state); 494 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_CHANGED); 495 } 496 } 497 } 498 dumpLocked(PrintWriter pw)499 public void dumpLocked(PrintWriter pw) { 500 pw.println(" " + mName + ":"); 501 pw.println(" mModes=" + Arrays.toString(mModes.toArray())); 502 pw.println(" mActiveMode=" + mActiveMode); 503 pw.println(" mGravity=" + mGravity); 504 pw.println(" mFlags=" + mFlags); 505 pw.println(" mNumber=" + mNumber); 506 507 // Try to dump the window state. 508 if (mWindow != null) { 509 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 510 ipw.increaseIndent(); 511 DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200); 512 } 513 } 514 515 // Runs on the UI thread. 516 private final Runnable mShowRunnable = new Runnable() { 517 @Override 518 public void run() { 519 OverlayMode mode = mModes.get(mActiveMode); 520 OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(), 521 mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, 522 mFlags.mSecure, OverlayDisplayHandle.this); 523 window.show(); 524 525 synchronized (getSyncRoot()) { 526 mWindow = window; 527 } 528 } 529 }; 530 531 // Runs on the UI thread. 532 private final Runnable mDismissRunnable = new Runnable() { 533 @Override 534 public void run() { 535 OverlayDisplayWindow window; 536 synchronized (getSyncRoot()) { 537 window = mWindow; 538 mWindow = null; 539 } 540 541 if (window != null) { 542 window.dismiss(); 543 } 544 } 545 }; 546 547 // Runs on the UI thread. 548 private final Runnable mResizeRunnable = new Runnable() { 549 @Override 550 public void run() { 551 OverlayMode mode; 552 OverlayDisplayWindow window; 553 synchronized (getSyncRoot()) { 554 if (mWindow == null) { 555 return; 556 } 557 mode = mModes.get(mActiveMode); 558 window = mWindow; 559 } 560 window.resize(mode.mWidth, mode.mHeight, mode.mDensityDpi); 561 } 562 }; 563 } 564 565 /** 566 * A display mode for an overlay display. 567 */ 568 private static final class OverlayMode { 569 final int mWidth; 570 final int mHeight; 571 final int mDensityDpi; 572 OverlayMode(int width, int height, int densityDpi)573 OverlayMode(int width, int height, int densityDpi) { 574 mWidth = width; 575 mHeight = height; 576 mDensityDpi = densityDpi; 577 } 578 579 @Override toString()580 public String toString() { 581 return new StringBuilder("{") 582 .append("width=").append(mWidth) 583 .append(", height=").append(mHeight) 584 .append(", densityDpi=").append(mDensityDpi) 585 .append("}") 586 .toString(); 587 } 588 } 589 590 /** Represents the flags of the overlay display. */ 591 private static final class OverlayFlags { 592 /** See {@link #OVERLAY_DISPLAY_FLAG_SECURE}. */ 593 final boolean mSecure; 594 595 /** See {@link #OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY}. */ 596 final boolean mOwnContentOnly; 597 598 /** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */ 599 final boolean mShouldShowSystemDecorations; 600 OverlayFlags( boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations)601 OverlayFlags( 602 boolean secure, 603 boolean ownContentOnly, 604 boolean shouldShowSystemDecorations) { 605 mSecure = secure; 606 mOwnContentOnly = ownContentOnly; 607 mShouldShowSystemDecorations = shouldShowSystemDecorations; 608 } 609 parseFlags(@ullable String flagString)610 static OverlayFlags parseFlags(@Nullable String flagString) { 611 if (TextUtils.isEmpty(flagString)) { 612 return new OverlayFlags( 613 false /* secure */, 614 false /* ownContentOnly */, 615 false /* shouldShowSystemDecorations */); 616 } 617 618 boolean secure = false; 619 boolean ownContentOnly = false; 620 boolean shouldShowSystemDecorations = false; 621 for (String flag: flagString.split(FLAG_SPLITTER)) { 622 if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) { 623 secure = true; 624 } 625 if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) { 626 ownContentOnly = true; 627 } 628 if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) { 629 shouldShowSystemDecorations = true; 630 } 631 } 632 return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations); 633 } 634 635 @Override toString()636 public String toString() { 637 return new StringBuilder("{") 638 .append("secure=").append(mSecure) 639 .append(", ownContentOnly=").append(mOwnContentOnly) 640 .append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations) 641 .append("}") 642 .toString(); 643 } 644 } 645 } 646