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