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 android.view; 18 19 import android.content.res.CompatibilityInfo; 20 import android.content.res.Configuration; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.ArraySet; 24 import android.util.DisplayMetrics; 25 26 import libcore.util.Objects; 27 28 import java.util.Arrays; 29 30 /** 31 * Describes the characteristics of a particular logical display. 32 * @hide 33 */ 34 public final class DisplayInfo implements Parcelable { 35 /** 36 * The surface flinger layer stack associated with this logical display. 37 */ 38 public int layerStack; 39 40 /** 41 * Display flags. 42 */ 43 public int flags; 44 45 /** 46 * Display type. 47 */ 48 public int type; 49 50 /** 51 * Display address, or null if none. 52 * Interpretation varies by display type. 53 */ 54 public String address; 55 56 /** 57 * The human-readable name of the display. 58 */ 59 public String name; 60 61 /** 62 * Unique identifier for the display. Shouldn't be displayed to the user. 63 */ 64 public String uniqueId; 65 66 /** 67 * The width of the portion of the display that is available to applications, in pixels. 68 * Represents the size of the display minus any system decorations. 69 */ 70 public int appWidth; 71 72 /** 73 * The height of the portion of the display that is available to applications, in pixels. 74 * Represents the size of the display minus any system decorations. 75 */ 76 public int appHeight; 77 78 /** 79 * The smallest value of {@link #appWidth} that an application is likely to encounter, 80 * in pixels, excepting cases where the width may be even smaller due to the presence 81 * of a soft keyboard, for example. 82 */ 83 public int smallestNominalAppWidth; 84 85 /** 86 * The smallest value of {@link #appHeight} that an application is likely to encounter, 87 * in pixels, excepting cases where the height may be even smaller due to the presence 88 * of a soft keyboard, for example. 89 */ 90 public int smallestNominalAppHeight; 91 92 /** 93 * The largest value of {@link #appWidth} that an application is likely to encounter, 94 * in pixels, excepting cases where the width may be even larger due to system decorations 95 * such as the status bar being hidden, for example. 96 */ 97 public int largestNominalAppWidth; 98 99 /** 100 * The largest value of {@link #appHeight} that an application is likely to encounter, 101 * in pixels, excepting cases where the height may be even larger due to system decorations 102 * such as the status bar being hidden, for example. 103 */ 104 public int largestNominalAppHeight; 105 106 /** 107 * The logical width of the display, in pixels. 108 * Represents the usable size of the display which may be smaller than the 109 * physical size when the system is emulating a smaller display. 110 */ 111 public int logicalWidth; 112 113 /** 114 * The logical height of the display, in pixels. 115 * Represents the usable size of the display which may be smaller than the 116 * physical size when the system is emulating a smaller display. 117 */ 118 public int logicalHeight; 119 120 /** 121 * @hide 122 * Number of overscan pixels on the left side of the display. 123 */ 124 public int overscanLeft; 125 126 /** 127 * @hide 128 * Number of overscan pixels on the top side of the display. 129 */ 130 public int overscanTop; 131 132 /** 133 * @hide 134 * Number of overscan pixels on the right side of the display. 135 */ 136 public int overscanRight; 137 138 /** 139 * @hide 140 * Number of overscan pixels on the bottom side of the display. 141 */ 142 public int overscanBottom; 143 144 /** 145 * The rotation of the display relative to its natural orientation. 146 * May be one of {@link android.view.Surface#ROTATION_0}, 147 * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180}, 148 * {@link android.view.Surface#ROTATION_270}. 149 * <p> 150 * The value of this field is indeterminate if the logical display is presented on 151 * more than one physical display. 152 * </p> 153 */ 154 @Surface.Rotation 155 public int rotation; 156 157 /** 158 * The active display mode. 159 */ 160 public int modeId; 161 162 /** 163 * The default display mode. 164 */ 165 public int defaultModeId; 166 167 /** 168 * The supported modes of this display. 169 */ 170 public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY; 171 172 /** The active color mode. */ 173 public int colorMode; 174 175 /** The list of supported color modes */ 176 public int[] supportedColorModes = { Display.COLOR_MODE_DEFAULT }; 177 178 /** The display's HDR capabilities */ 179 public Display.HdrCapabilities hdrCapabilities; 180 181 /** 182 * The logical display density which is the basis for density-independent 183 * pixels. 184 */ 185 public int logicalDensityDpi; 186 187 /** 188 * The exact physical pixels per inch of the screen in the X dimension. 189 * <p> 190 * The value of this field is indeterminate if the logical display is presented on 191 * more than one physical display. 192 * </p> 193 */ 194 public float physicalXDpi; 195 196 /** 197 * The exact physical pixels per inch of the screen in the Y dimension. 198 * <p> 199 * The value of this field is indeterminate if the logical display is presented on 200 * more than one physical display. 201 * </p> 202 */ 203 public float physicalYDpi; 204 205 /** 206 * This is a positive value indicating the phase offset of the VSYNC events provided by 207 * Choreographer relative to the display refresh. For example, if Choreographer reports 208 * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos). 209 */ 210 public long appVsyncOffsetNanos; 211 212 /** 213 * This is how far in advance a buffer must be queued for presentation at 214 * a given time. If you want a buffer to appear on the screen at 215 * time N, you must submit the buffer before (N - bufferDeadlineNanos). 216 */ 217 public long presentationDeadlineNanos; 218 219 /** 220 * The state of the display, such as {@link android.view.Display#STATE_ON}. 221 */ 222 public int state; 223 224 /** 225 * The UID of the application that owns this display, or zero if it is owned by the system. 226 * <p> 227 * If the display is private, then only the owner can use it. 228 * </p> 229 */ 230 public int ownerUid; 231 232 /** 233 * The package name of the application that owns this display, or null if it is 234 * owned by the system. 235 * <p> 236 * If the display is private, then only the owner can use it. 237 * </p> 238 */ 239 public String ownerPackageName; 240 241 /** 242 * @hide 243 * Get current remove mode of the display - what actions should be performed with the display's 244 * content when it is removed. 245 * 246 * @see Display#getRemoveMode() 247 */ 248 public int removeMode = Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY; 249 250 public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { 251 @Override 252 public DisplayInfo createFromParcel(Parcel source) { 253 return new DisplayInfo(source); 254 } 255 256 @Override 257 public DisplayInfo[] newArray(int size) { 258 return new DisplayInfo[size]; 259 } 260 }; 261 DisplayInfo()262 public DisplayInfo() { 263 } 264 DisplayInfo(DisplayInfo other)265 public DisplayInfo(DisplayInfo other) { 266 copyFrom(other); 267 } 268 DisplayInfo(Parcel source)269 private DisplayInfo(Parcel source) { 270 readFromParcel(source); 271 } 272 273 @Override equals(Object o)274 public boolean equals(Object o) { 275 return o instanceof DisplayInfo && equals((DisplayInfo)o); 276 } 277 equals(DisplayInfo other)278 public boolean equals(DisplayInfo other) { 279 return other != null 280 && layerStack == other.layerStack 281 && flags == other.flags 282 && type == other.type 283 && Objects.equal(address, other.address) 284 && Objects.equal(uniqueId, other.uniqueId) 285 && appWidth == other.appWidth 286 && appHeight == other.appHeight 287 && smallestNominalAppWidth == other.smallestNominalAppWidth 288 && smallestNominalAppHeight == other.smallestNominalAppHeight 289 && largestNominalAppWidth == other.largestNominalAppWidth 290 && largestNominalAppHeight == other.largestNominalAppHeight 291 && logicalWidth == other.logicalWidth 292 && logicalHeight == other.logicalHeight 293 && overscanLeft == other.overscanLeft 294 && overscanTop == other.overscanTop 295 && overscanRight == other.overscanRight 296 && overscanBottom == other.overscanBottom 297 && rotation == other.rotation 298 && modeId == other.modeId 299 && defaultModeId == other.defaultModeId 300 && colorMode == other.colorMode 301 && Arrays.equals(supportedColorModes, other.supportedColorModes) 302 && Objects.equal(hdrCapabilities, other.hdrCapabilities) 303 && logicalDensityDpi == other.logicalDensityDpi 304 && physicalXDpi == other.physicalXDpi 305 && physicalYDpi == other.physicalYDpi 306 && appVsyncOffsetNanos == other.appVsyncOffsetNanos 307 && presentationDeadlineNanos == other.presentationDeadlineNanos 308 && state == other.state 309 && ownerUid == other.ownerUid 310 && Objects.equal(ownerPackageName, other.ownerPackageName) 311 && removeMode == other.removeMode; 312 } 313 314 @Override hashCode()315 public int hashCode() { 316 return 0; // don't care 317 } 318 copyFrom(DisplayInfo other)319 public void copyFrom(DisplayInfo other) { 320 layerStack = other.layerStack; 321 flags = other.flags; 322 type = other.type; 323 address = other.address; 324 name = other.name; 325 uniqueId = other.uniqueId; 326 appWidth = other.appWidth; 327 appHeight = other.appHeight; 328 smallestNominalAppWidth = other.smallestNominalAppWidth; 329 smallestNominalAppHeight = other.smallestNominalAppHeight; 330 largestNominalAppWidth = other.largestNominalAppWidth; 331 largestNominalAppHeight = other.largestNominalAppHeight; 332 logicalWidth = other.logicalWidth; 333 logicalHeight = other.logicalHeight; 334 overscanLeft = other.overscanLeft; 335 overscanTop = other.overscanTop; 336 overscanRight = other.overscanRight; 337 overscanBottom = other.overscanBottom; 338 rotation = other.rotation; 339 modeId = other.modeId; 340 defaultModeId = other.defaultModeId; 341 supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length); 342 colorMode = other.colorMode; 343 supportedColorModes = Arrays.copyOf( 344 other.supportedColorModes, other.supportedColorModes.length); 345 hdrCapabilities = other.hdrCapabilities; 346 logicalDensityDpi = other.logicalDensityDpi; 347 physicalXDpi = other.physicalXDpi; 348 physicalYDpi = other.physicalYDpi; 349 appVsyncOffsetNanos = other.appVsyncOffsetNanos; 350 presentationDeadlineNanos = other.presentationDeadlineNanos; 351 state = other.state; 352 ownerUid = other.ownerUid; 353 ownerPackageName = other.ownerPackageName; 354 removeMode = other.removeMode; 355 } 356 readFromParcel(Parcel source)357 public void readFromParcel(Parcel source) { 358 layerStack = source.readInt(); 359 flags = source.readInt(); 360 type = source.readInt(); 361 address = source.readString(); 362 name = source.readString(); 363 appWidth = source.readInt(); 364 appHeight = source.readInt(); 365 smallestNominalAppWidth = source.readInt(); 366 smallestNominalAppHeight = source.readInt(); 367 largestNominalAppWidth = source.readInt(); 368 largestNominalAppHeight = source.readInt(); 369 logicalWidth = source.readInt(); 370 logicalHeight = source.readInt(); 371 overscanLeft = source.readInt(); 372 overscanTop = source.readInt(); 373 overscanRight = source.readInt(); 374 overscanBottom = source.readInt(); 375 rotation = source.readInt(); 376 modeId = source.readInt(); 377 defaultModeId = source.readInt(); 378 int nModes = source.readInt(); 379 supportedModes = new Display.Mode[nModes]; 380 for (int i = 0; i < nModes; i++) { 381 supportedModes[i] = Display.Mode.CREATOR.createFromParcel(source); 382 } 383 colorMode = source.readInt(); 384 int nColorModes = source.readInt(); 385 supportedColorModes = new int[nColorModes]; 386 for (int i = 0; i < nColorModes; i++) { 387 supportedColorModes[i] = source.readInt(); 388 } 389 hdrCapabilities = source.readParcelable(null); 390 logicalDensityDpi = source.readInt(); 391 physicalXDpi = source.readFloat(); 392 physicalYDpi = source.readFloat(); 393 appVsyncOffsetNanos = source.readLong(); 394 presentationDeadlineNanos = source.readLong(); 395 state = source.readInt(); 396 ownerUid = source.readInt(); 397 ownerPackageName = source.readString(); 398 uniqueId = source.readString(); 399 removeMode = source.readInt(); 400 } 401 402 @Override writeToParcel(Parcel dest, int flags)403 public void writeToParcel(Parcel dest, int flags) { 404 dest.writeInt(layerStack); 405 dest.writeInt(this.flags); 406 dest.writeInt(type); 407 dest.writeString(address); 408 dest.writeString(name); 409 dest.writeInt(appWidth); 410 dest.writeInt(appHeight); 411 dest.writeInt(smallestNominalAppWidth); 412 dest.writeInt(smallestNominalAppHeight); 413 dest.writeInt(largestNominalAppWidth); 414 dest.writeInt(largestNominalAppHeight); 415 dest.writeInt(logicalWidth); 416 dest.writeInt(logicalHeight); 417 dest.writeInt(overscanLeft); 418 dest.writeInt(overscanTop); 419 dest.writeInt(overscanRight); 420 dest.writeInt(overscanBottom); 421 dest.writeInt(rotation); 422 dest.writeInt(modeId); 423 dest.writeInt(defaultModeId); 424 dest.writeInt(supportedModes.length); 425 for (int i = 0; i < supportedModes.length; i++) { 426 supportedModes[i].writeToParcel(dest, flags); 427 } 428 dest.writeInt(colorMode); 429 dest.writeInt(supportedColorModes.length); 430 for (int i = 0; i < supportedColorModes.length; i++) { 431 dest.writeInt(supportedColorModes[i]); 432 } 433 dest.writeParcelable(hdrCapabilities, flags); 434 dest.writeInt(logicalDensityDpi); 435 dest.writeFloat(physicalXDpi); 436 dest.writeFloat(physicalYDpi); 437 dest.writeLong(appVsyncOffsetNanos); 438 dest.writeLong(presentationDeadlineNanos); 439 dest.writeInt(state); 440 dest.writeInt(ownerUid); 441 dest.writeString(ownerPackageName); 442 dest.writeString(uniqueId); 443 dest.writeInt(removeMode); 444 } 445 446 @Override describeContents()447 public int describeContents() { 448 return 0; 449 } 450 getMode()451 public Display.Mode getMode() { 452 return findMode(modeId); 453 } 454 getDefaultMode()455 public Display.Mode getDefaultMode() { 456 return findMode(defaultModeId); 457 } 458 findMode(int id)459 private Display.Mode findMode(int id) { 460 for (int i = 0; i < supportedModes.length; i++) { 461 if (supportedModes[i].getModeId() == id) { 462 return supportedModes[i]; 463 } 464 } 465 throw new IllegalStateException("Unable to locate mode " + id); 466 } 467 468 /** 469 * Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable 470 * mode could be found. 471 */ findDefaultModeByRefreshRate(float refreshRate)472 public int findDefaultModeByRefreshRate(float refreshRate) { 473 Display.Mode[] modes = supportedModes; 474 Display.Mode defaultMode = getDefaultMode(); 475 for (int i = 0; i < modes.length; i++) { 476 if (modes[i].matches( 477 defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) { 478 return modes[i].getModeId(); 479 } 480 } 481 return 0; 482 } 483 484 /** 485 * Returns the list of supported refresh rates in the default mode. 486 */ getDefaultRefreshRates()487 public float[] getDefaultRefreshRates() { 488 Display.Mode[] modes = supportedModes; 489 ArraySet<Float> rates = new ArraySet<>(); 490 Display.Mode defaultMode = getDefaultMode(); 491 for (int i = 0; i < modes.length; i++) { 492 Display.Mode mode = modes[i]; 493 if (mode.getPhysicalWidth() == defaultMode.getPhysicalWidth() 494 && mode.getPhysicalHeight() == defaultMode.getPhysicalHeight()) { 495 rates.add(mode.getRefreshRate()); 496 } 497 } 498 float[] result = new float[rates.size()]; 499 int i = 0; 500 for (Float rate : rates) { 501 result[i++] = rate; 502 } 503 return result; 504 } 505 getAppMetrics(DisplayMetrics outMetrics)506 public void getAppMetrics(DisplayMetrics outMetrics) { 507 getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); 508 } 509 getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments)510 public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) { 511 getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(), 512 displayAdjustments.getConfiguration(), appWidth, appHeight); 513 } 514 getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci, Configuration configuration)515 public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci, 516 Configuration configuration) { 517 getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight); 518 } 519 getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, Configuration configuration)520 public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, 521 Configuration configuration) { 522 getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight); 523 } 524 getNaturalWidth()525 public int getNaturalWidth() { 526 return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? 527 logicalWidth : logicalHeight; 528 } 529 getNaturalHeight()530 public int getNaturalHeight() { 531 return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? 532 logicalHeight : logicalWidth; 533 } 534 isHdr()535 public boolean isHdr() { 536 int[] types = hdrCapabilities != null ? hdrCapabilities.getSupportedHdrTypes() : null; 537 return types != null && types.length > 0; 538 } 539 isWideColorGamut()540 public boolean isWideColorGamut() { 541 for (int colorMode : supportedColorModes) { 542 if (colorMode == Display.COLOR_MODE_DCI_P3 || colorMode > Display.COLOR_MODE_SRGB) { 543 return true; 544 } 545 } 546 return false; 547 } 548 549 /** 550 * Returns true if the specified UID has access to this display. 551 */ hasAccess(int uid)552 public boolean hasAccess(int uid) { 553 return Display.hasAccess(uid, flags, ownerUid); 554 } 555 getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, Configuration configuration, int width, int height)556 private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, 557 Configuration configuration, int width, int height) { 558 outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi; 559 outMetrics.density = outMetrics.noncompatDensity = 560 logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; 561 outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density; 562 outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi; 563 outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi; 564 565 width = configuration != null && configuration.appBounds != null 566 ? configuration.appBounds.width() : width; 567 height = configuration != null && configuration.appBounds != null 568 ? configuration.appBounds.height() : height; 569 570 outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width; 571 outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height; 572 573 if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) { 574 compatInfo.applyToDisplayMetrics(outMetrics); 575 } 576 } 577 578 // For debugging purposes 579 @Override toString()580 public String toString() { 581 StringBuilder sb = new StringBuilder(); 582 sb.append("DisplayInfo{\""); 583 sb.append(name); 584 sb.append("\", uniqueId \""); 585 sb.append(uniqueId); 586 sb.append("\", app "); 587 sb.append(appWidth); 588 sb.append(" x "); 589 sb.append(appHeight); 590 sb.append(", real "); 591 sb.append(logicalWidth); 592 sb.append(" x "); 593 sb.append(logicalHeight); 594 if (overscanLeft != 0 || overscanTop != 0 || overscanRight != 0 || overscanBottom != 0) { 595 sb.append(", overscan ("); 596 sb.append(overscanLeft); 597 sb.append(","); 598 sb.append(overscanTop); 599 sb.append(","); 600 sb.append(overscanRight); 601 sb.append(","); 602 sb.append(overscanBottom); 603 sb.append(")"); 604 } 605 sb.append(", largest app "); 606 sb.append(largestNominalAppWidth); 607 sb.append(" x "); 608 sb.append(largestNominalAppHeight); 609 sb.append(", smallest app "); 610 sb.append(smallestNominalAppWidth); 611 sb.append(" x "); 612 sb.append(smallestNominalAppHeight); 613 sb.append(", mode "); 614 sb.append(modeId); 615 sb.append(", defaultMode "); 616 sb.append(defaultModeId); 617 sb.append(", modes "); 618 sb.append(Arrays.toString(supportedModes)); 619 sb.append(", colorMode "); 620 sb.append(colorMode); 621 sb.append(", supportedColorModes "); 622 sb.append(Arrays.toString(supportedColorModes)); 623 sb.append(", hdrCapabilities "); 624 sb.append(hdrCapabilities); 625 sb.append(", rotation "); 626 sb.append(rotation); 627 sb.append(", density "); 628 sb.append(logicalDensityDpi); 629 sb.append(" ("); 630 sb.append(physicalXDpi); 631 sb.append(" x "); 632 sb.append(physicalYDpi); 633 sb.append(") dpi, layerStack "); 634 sb.append(layerStack); 635 sb.append(", appVsyncOff "); 636 sb.append(appVsyncOffsetNanos); 637 sb.append(", presDeadline "); 638 sb.append(presentationDeadlineNanos); 639 sb.append(", type "); 640 sb.append(Display.typeToString(type)); 641 if (address != null) { 642 sb.append(", address ").append(address); 643 } 644 sb.append(", state "); 645 sb.append(Display.stateToString(state)); 646 if (ownerUid != 0 || ownerPackageName != null) { 647 sb.append(", owner ").append(ownerPackageName); 648 sb.append(" (uid ").append(ownerUid).append(")"); 649 } 650 sb.append(flagsToString(flags)); 651 sb.append(", removeMode "); 652 sb.append(removeMode); 653 sb.append("}"); 654 return sb.toString(); 655 } 656 flagsToString(int flags)657 private static String flagsToString(int flags) { 658 StringBuilder result = new StringBuilder(); 659 if ((flags & Display.FLAG_SECURE) != 0) { 660 result.append(", FLAG_SECURE"); 661 } 662 if ((flags & Display.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) { 663 result.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS"); 664 } 665 if ((flags & Display.FLAG_PRIVATE) != 0) { 666 result.append(", FLAG_PRIVATE"); 667 } 668 if ((flags & Display.FLAG_PRESENTATION) != 0) { 669 result.append(", FLAG_PRESENTATION"); 670 } 671 if ((flags & Display.FLAG_SCALING_DISABLED) != 0) { 672 result.append(", FLAG_SCALING_DISABLED"); 673 } 674 if ((flags & Display.FLAG_ROUND) != 0) { 675 result.append(", FLAG_ROUND"); 676 } 677 return result.toString(); 678 } 679 } 680