1 /* 2 * Copyright (C) 2016 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.bluetooth; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.util.Objects; 28 29 /** 30 * Represents the codec configuration for a Bluetooth A2DP source device. 31 * 32 * {@see BluetoothA2dp} 33 * 34 * {@hide} 35 */ 36 public final class BluetoothCodecConfig implements Parcelable { 37 // Add an entry for each source codec here. 38 // NOTE: The values should be same as those listed in the following file: 39 // hardware/libhardware/include/hardware/bt_av.h 40 41 /** @hide */ 42 @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { 43 SOURCE_CODEC_TYPE_SBC, 44 SOURCE_CODEC_TYPE_AAC, 45 SOURCE_CODEC_TYPE_APTX, 46 SOURCE_CODEC_TYPE_APTX_HD, 47 SOURCE_CODEC_TYPE_LDAC, 48 SOURCE_CODEC_TYPE_MAX, 49 SOURCE_CODEC_TYPE_INVALID 50 }) 51 @Retention(RetentionPolicy.SOURCE) 52 public @interface SourceCodecType {} 53 54 public static final int SOURCE_CODEC_TYPE_SBC = 0; 55 56 public static final int SOURCE_CODEC_TYPE_AAC = 1; 57 58 public static final int SOURCE_CODEC_TYPE_APTX = 2; 59 60 public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; 61 62 public static final int SOURCE_CODEC_TYPE_LDAC = 4; 63 64 public static final int SOURCE_CODEC_TYPE_MAX = 5; 65 66 67 public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; 68 69 /** @hide */ 70 @IntDef(prefix = "CODEC_PRIORITY_", value = { 71 CODEC_PRIORITY_DISABLED, 72 CODEC_PRIORITY_DEFAULT, 73 CODEC_PRIORITY_HIGHEST 74 }) 75 @Retention(RetentionPolicy.SOURCE) 76 public @interface CodecPriority {} 77 78 public static final int CODEC_PRIORITY_DISABLED = -1; 79 80 public static final int CODEC_PRIORITY_DEFAULT = 0; 81 82 public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000; 83 84 85 /** @hide */ 86 @IntDef(prefix = "SAMPLE_RATE_", value = { 87 SAMPLE_RATE_NONE, 88 SAMPLE_RATE_44100, 89 SAMPLE_RATE_48000, 90 SAMPLE_RATE_88200, 91 SAMPLE_RATE_96000, 92 SAMPLE_RATE_176400, 93 SAMPLE_RATE_192000 94 }) 95 @Retention(RetentionPolicy.SOURCE) 96 public @interface SampleRate {} 97 98 public static final int SAMPLE_RATE_NONE = 0; 99 100 public static final int SAMPLE_RATE_44100 = 0x1 << 0; 101 102 public static final int SAMPLE_RATE_48000 = 0x1 << 1; 103 104 public static final int SAMPLE_RATE_88200 = 0x1 << 2; 105 106 public static final int SAMPLE_RATE_96000 = 0x1 << 3; 107 108 public static final int SAMPLE_RATE_176400 = 0x1 << 4; 109 110 public static final int SAMPLE_RATE_192000 = 0x1 << 5; 111 112 113 /** @hide */ 114 @IntDef(prefix = "BITS_PER_SAMPLE_", value = { 115 BITS_PER_SAMPLE_NONE, 116 BITS_PER_SAMPLE_16, 117 BITS_PER_SAMPLE_24, 118 BITS_PER_SAMPLE_32 119 }) 120 @Retention(RetentionPolicy.SOURCE) 121 public @interface BitsPerSample {} 122 123 public static final int BITS_PER_SAMPLE_NONE = 0; 124 125 public static final int BITS_PER_SAMPLE_16 = 0x1 << 0; 126 127 public static final int BITS_PER_SAMPLE_24 = 0x1 << 1; 128 129 public static final int BITS_PER_SAMPLE_32 = 0x1 << 2; 130 131 132 /** @hide */ 133 @IntDef(prefix = "CHANNEL_MODE_", value = { 134 CHANNEL_MODE_NONE, 135 CHANNEL_MODE_MONO, 136 CHANNEL_MODE_STEREO 137 }) 138 @Retention(RetentionPolicy.SOURCE) 139 public @interface ChannelMode {} 140 141 public static final int CHANNEL_MODE_NONE = 0; 142 143 public static final int CHANNEL_MODE_MONO = 0x1 << 0; 144 145 public static final int CHANNEL_MODE_STEREO = 0x1 << 1; 146 147 private final @SourceCodecType int mCodecType; 148 private @CodecPriority int mCodecPriority; 149 private final @SampleRate int mSampleRate; 150 private final @BitsPerSample int mBitsPerSample; 151 private final @ChannelMode int mChannelMode; 152 private final long mCodecSpecific1; 153 private final long mCodecSpecific2; 154 private final long mCodecSpecific3; 155 private final long mCodecSpecific4; 156 BluetoothCodecConfig(@ourceCodecType int codecType, @CodecPriority int codecPriority, @SampleRate int sampleRate, @BitsPerSample int bitsPerSample, @ChannelMode int channelMode, long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4)157 public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority, 158 @SampleRate int sampleRate, @BitsPerSample int bitsPerSample, 159 @ChannelMode int channelMode, long codecSpecific1, 160 long codecSpecific2, long codecSpecific3, 161 long codecSpecific4) { 162 mCodecType = codecType; 163 mCodecPriority = codecPriority; 164 mSampleRate = sampleRate; 165 mBitsPerSample = bitsPerSample; 166 mChannelMode = channelMode; 167 mCodecSpecific1 = codecSpecific1; 168 mCodecSpecific2 = codecSpecific2; 169 mCodecSpecific3 = codecSpecific3; 170 mCodecSpecific4 = codecSpecific4; 171 } 172 BluetoothCodecConfig(@ourceCodecType int codecType)173 public BluetoothCodecConfig(@SourceCodecType int codecType) { 174 mCodecType = codecType; 175 mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; 176 mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE; 177 mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE; 178 mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE; 179 mCodecSpecific1 = 0; 180 mCodecSpecific2 = 0; 181 mCodecSpecific3 = 0; 182 mCodecSpecific4 = 0; 183 } 184 185 @Override equals(Object o)186 public boolean equals(Object o) { 187 if (o instanceof BluetoothCodecConfig) { 188 BluetoothCodecConfig other = (BluetoothCodecConfig) o; 189 return (other.mCodecType == mCodecType 190 && other.mCodecPriority == mCodecPriority 191 && other.mSampleRate == mSampleRate 192 && other.mBitsPerSample == mBitsPerSample 193 && other.mChannelMode == mChannelMode 194 && other.mCodecSpecific1 == mCodecSpecific1 195 && other.mCodecSpecific2 == mCodecSpecific2 196 && other.mCodecSpecific3 == mCodecSpecific3 197 && other.mCodecSpecific4 == mCodecSpecific4); 198 } 199 return false; 200 } 201 202 /** 203 * Returns a hash based on the config values 204 * 205 * @return a hash based on the config values 206 * @hide 207 */ 208 @Override hashCode()209 public int hashCode() { 210 return Objects.hash(mCodecType, mCodecPriority, mSampleRate, 211 mBitsPerSample, mChannelMode, mCodecSpecific1, 212 mCodecSpecific2, mCodecSpecific3, mCodecSpecific4); 213 } 214 215 /** 216 * Checks whether the object contains valid codec configuration. 217 * 218 * @return true if the object contains valid codec configuration, otherwise false. 219 * @hide 220 */ isValid()221 public boolean isValid() { 222 return (mSampleRate != SAMPLE_RATE_NONE) 223 && (mBitsPerSample != BITS_PER_SAMPLE_NONE) 224 && (mChannelMode != CHANNEL_MODE_NONE); 225 } 226 227 /** 228 * Adds capability string to an existing string. 229 * 230 * @param prevStr the previous string with the capabilities. Can be a null pointer. 231 * @param capStr the capability string to append to prevStr argument. 232 * @return the result string in the form "prevStr|capStr". 233 */ appendCapabilityToString(String prevStr, String capStr)234 private static String appendCapabilityToString(String prevStr, 235 String capStr) { 236 if (prevStr == null) { 237 return capStr; 238 } 239 return prevStr + "|" + capStr; 240 } 241 242 @Override toString()243 public String toString() { 244 String sampleRateStr = null; 245 if (mSampleRate == SAMPLE_RATE_NONE) { 246 sampleRateStr = appendCapabilityToString(sampleRateStr, "NONE"); 247 } 248 if ((mSampleRate & SAMPLE_RATE_44100) != 0) { 249 sampleRateStr = appendCapabilityToString(sampleRateStr, "44100"); 250 } 251 if ((mSampleRate & SAMPLE_RATE_48000) != 0) { 252 sampleRateStr = appendCapabilityToString(sampleRateStr, "48000"); 253 } 254 if ((mSampleRate & SAMPLE_RATE_88200) != 0) { 255 sampleRateStr = appendCapabilityToString(sampleRateStr, "88200"); 256 } 257 if ((mSampleRate & SAMPLE_RATE_96000) != 0) { 258 sampleRateStr = appendCapabilityToString(sampleRateStr, "96000"); 259 } 260 if ((mSampleRate & SAMPLE_RATE_176400) != 0) { 261 sampleRateStr = appendCapabilityToString(sampleRateStr, "176400"); 262 } 263 if ((mSampleRate & SAMPLE_RATE_192000) != 0) { 264 sampleRateStr = appendCapabilityToString(sampleRateStr, "192000"); 265 } 266 267 String bitsPerSampleStr = null; 268 if (mBitsPerSample == BITS_PER_SAMPLE_NONE) { 269 bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "NONE"); 270 } 271 if ((mBitsPerSample & BITS_PER_SAMPLE_16) != 0) { 272 bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "16"); 273 } 274 if ((mBitsPerSample & BITS_PER_SAMPLE_24) != 0) { 275 bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "24"); 276 } 277 if ((mBitsPerSample & BITS_PER_SAMPLE_32) != 0) { 278 bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "32"); 279 } 280 281 String channelModeStr = null; 282 if (mChannelMode == CHANNEL_MODE_NONE) { 283 channelModeStr = appendCapabilityToString(channelModeStr, "NONE"); 284 } 285 if ((mChannelMode & CHANNEL_MODE_MONO) != 0) { 286 channelModeStr = appendCapabilityToString(channelModeStr, "MONO"); 287 } 288 if ((mChannelMode & CHANNEL_MODE_STEREO) != 0) { 289 channelModeStr = appendCapabilityToString(channelModeStr, "STEREO"); 290 } 291 292 return "{codecName:" + getCodecName() 293 + ",mCodecType:" + mCodecType 294 + ",mCodecPriority:" + mCodecPriority 295 + ",mSampleRate:" + String.format("0x%x", mSampleRate) 296 + "(" + sampleRateStr + ")" 297 + ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample) 298 + "(" + bitsPerSampleStr + ")" 299 + ",mChannelMode:" + String.format("0x%x", mChannelMode) 300 + "(" + channelModeStr + ")" 301 + ",mCodecSpecific1:" + mCodecSpecific1 302 + ",mCodecSpecific2:" + mCodecSpecific2 303 + ",mCodecSpecific3:" + mCodecSpecific3 304 + ",mCodecSpecific4:" + mCodecSpecific4 + "}"; 305 } 306 307 /** 308 * Always returns 0 309 * 310 * @return 0 311 * @hide 312 */ 313 @Override describeContents()314 public int describeContents() { 315 return 0; 316 } 317 318 public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecConfig> CREATOR = 319 new Parcelable.Creator<BluetoothCodecConfig>() { 320 public BluetoothCodecConfig createFromParcel(Parcel in) { 321 final int codecType = in.readInt(); 322 final int codecPriority = in.readInt(); 323 final int sampleRate = in.readInt(); 324 final int bitsPerSample = in.readInt(); 325 final int channelMode = in.readInt(); 326 final long codecSpecific1 = in.readLong(); 327 final long codecSpecific2 = in.readLong(); 328 final long codecSpecific3 = in.readLong(); 329 final long codecSpecific4 = in.readLong(); 330 return new BluetoothCodecConfig(codecType, codecPriority, 331 sampleRate, bitsPerSample, 332 channelMode, codecSpecific1, 333 codecSpecific2, codecSpecific3, 334 codecSpecific4); 335 } 336 337 public BluetoothCodecConfig[] newArray(int size) { 338 return new BluetoothCodecConfig[size]; 339 } 340 }; 341 342 /** 343 * Flattens the object to a parcel 344 * 345 * @param out The Parcel in which the object should be written. 346 * @param flags Additional flags about how the object should be written. 347 * 348 * @hide 349 */ 350 @Override writeToParcel(Parcel out, int flags)351 public void writeToParcel(Parcel out, int flags) { 352 out.writeInt(mCodecType); 353 out.writeInt(mCodecPriority); 354 out.writeInt(mSampleRate); 355 out.writeInt(mBitsPerSample); 356 out.writeInt(mChannelMode); 357 out.writeLong(mCodecSpecific1); 358 out.writeLong(mCodecSpecific2); 359 out.writeLong(mCodecSpecific3); 360 out.writeLong(mCodecSpecific4); 361 } 362 363 /** 364 * Gets the codec name. 365 * 366 * @return the codec name 367 */ getCodecName()368 public @NonNull String getCodecName() { 369 switch (mCodecType) { 370 case SOURCE_CODEC_TYPE_SBC: 371 return "SBC"; 372 case SOURCE_CODEC_TYPE_AAC: 373 return "AAC"; 374 case SOURCE_CODEC_TYPE_APTX: 375 return "aptX"; 376 case SOURCE_CODEC_TYPE_APTX_HD: 377 return "aptX HD"; 378 case SOURCE_CODEC_TYPE_LDAC: 379 return "LDAC"; 380 case SOURCE_CODEC_TYPE_INVALID: 381 return "INVALID CODEC"; 382 default: 383 break; 384 } 385 return "UNKNOWN CODEC(" + mCodecType + ")"; 386 } 387 388 /** 389 * Gets the codec type. 390 * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}. 391 * 392 * @return the codec type 393 */ getCodecType()394 public @SourceCodecType int getCodecType() { 395 return mCodecType; 396 } 397 398 /** 399 * Checks whether the codec is mandatory. 400 * 401 * @return true if the codec is mandatory, otherwise false. 402 */ isMandatoryCodec()403 public boolean isMandatoryCodec() { 404 return mCodecType == SOURCE_CODEC_TYPE_SBC; 405 } 406 407 /** 408 * Gets the codec selection priority. 409 * The codec selection priority is relative to other codecs: larger value 410 * means higher priority. If 0, reset to default. 411 * 412 * @return the codec priority 413 */ getCodecPriority()414 public @CodecPriority int getCodecPriority() { 415 return mCodecPriority; 416 } 417 418 /** 419 * Sets the codec selection priority. 420 * The codec selection priority is relative to other codecs: larger value 421 * means higher priority. If 0, reset to default. 422 * 423 * @param codecPriority the codec priority 424 * @hide 425 */ 426 @UnsupportedAppUsage setCodecPriority(@odecPriority int codecPriority)427 public void setCodecPriority(@CodecPriority int codecPriority) { 428 mCodecPriority = codecPriority; 429 } 430 431 /** 432 * Gets the codec sample rate. The value can be a bitmask with all 433 * supported sample rates: 434 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or 435 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or 436 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_48000} or 437 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_88200} or 438 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_96000} or 439 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_176400} or 440 * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_192000} 441 * 442 * @return the codec sample rate 443 */ getSampleRate()444 public @SampleRate int getSampleRate() { 445 return mSampleRate; 446 } 447 448 /** 449 * Gets the codec bits per sample. The value can be a bitmask with all 450 * bits per sample supported: 451 * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or 452 * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or 453 * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_24} or 454 * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_32} 455 * 456 * @return the codec bits per sample 457 */ getBitsPerSample()458 public @BitsPerSample int getBitsPerSample() { 459 return mBitsPerSample; 460 } 461 462 /** 463 * Gets the codec channel mode. The value can be a bitmask with all 464 * supported channel modes: 465 * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or 466 * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or 467 * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_STEREO} 468 * 469 * @return the codec channel mode 470 * @hide 471 */ 472 @UnsupportedAppUsage getChannelMode()473 public @ChannelMode int getChannelMode() { 474 return mChannelMode; 475 } 476 477 /** 478 * Gets a codec specific value1. 479 * 480 * @return a codec specific value1. 481 */ getCodecSpecific1()482 public long getCodecSpecific1() { 483 return mCodecSpecific1; 484 } 485 486 /** 487 * Gets a codec specific value2. 488 * 489 * @return a codec specific value2 490 * @hide 491 */ 492 @UnsupportedAppUsage getCodecSpecific2()493 public long getCodecSpecific2() { 494 return mCodecSpecific2; 495 } 496 497 /** 498 * Gets a codec specific value3. 499 * 500 * @return a codec specific value3 501 * @hide 502 */ 503 @UnsupportedAppUsage getCodecSpecific3()504 public long getCodecSpecific3() { 505 return mCodecSpecific3; 506 } 507 508 /** 509 * Gets a codec specific value4. 510 * 511 * @return a codec specific value4 512 * @hide 513 */ 514 @UnsupportedAppUsage getCodecSpecific4()515 public long getCodecSpecific4() { 516 return mCodecSpecific4; 517 } 518 519 /** 520 * Checks whether a value set presented by a bitmask has zero or single bit 521 * 522 * @param valueSet the value set presented by a bitmask 523 * @return true if the valueSet contains zero or single bit, otherwise false. 524 * @hide 525 */ hasSingleBit(int valueSet)526 private static boolean hasSingleBit(int valueSet) { 527 return (valueSet == 0 || (valueSet & (valueSet - 1)) == 0); 528 } 529 530 /** 531 * Checks whether the object contains none or single sample rate. 532 * 533 * @return true if the object contains none or single sample rate, otherwise false. 534 * @hide 535 */ hasSingleSampleRate()536 public boolean hasSingleSampleRate() { 537 return hasSingleBit(mSampleRate); 538 } 539 540 /** 541 * Checks whether the object contains none or single bits per sample. 542 * 543 * @return true if the object contains none or single bits per sample, otherwise false. 544 * @hide 545 */ hasSingleBitsPerSample()546 public boolean hasSingleBitsPerSample() { 547 return hasSingleBit(mBitsPerSample); 548 } 549 550 /** 551 * Checks whether the object contains none or single channel mode. 552 * 553 * @return true if the object contains none or single channel mode, otherwise false. 554 * @hide 555 */ hasSingleChannelMode()556 public boolean hasSingleChannelMode() { 557 return hasSingleBit(mChannelMode); 558 } 559 560 /** 561 * Checks whether the audio feeding parameters are same. 562 * 563 * @param other the codec config to compare against 564 * @return true if the audio feeding parameters are same, otherwise false 565 * @hide 566 */ sameAudioFeedingParameters(BluetoothCodecConfig other)567 public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) { 568 return (other != null && other.mSampleRate == mSampleRate 569 && other.mBitsPerSample == mBitsPerSample 570 && other.mChannelMode == mChannelMode); 571 } 572 573 /** 574 * Checks whether another codec config has the similar feeding parameters. 575 * Any parameters with NONE value will be considered to be a wildcard matching. 576 * 577 * @param other the codec config to compare against 578 * @return true if the audio feeding parameters are similar, otherwise false. 579 * @hide 580 */ similarCodecFeedingParameters(BluetoothCodecConfig other)581 public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) { 582 if (other == null || mCodecType != other.mCodecType) { 583 return false; 584 } 585 int sampleRate = other.mSampleRate; 586 if (mSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE 587 || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) { 588 sampleRate = mSampleRate; 589 } 590 int bitsPerSample = other.mBitsPerSample; 591 if (mBitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE 592 || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) { 593 bitsPerSample = mBitsPerSample; 594 } 595 int channelMode = other.mChannelMode; 596 if (mChannelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE 597 || channelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE) { 598 channelMode = mChannelMode; 599 } 600 return sameAudioFeedingParameters(new BluetoothCodecConfig( 601 mCodecType, /* priority */ 0, sampleRate, bitsPerSample, channelMode, 602 /* specific1 */ 0, /* specific2 */ 0, /* specific3 */ 0, 603 /* specific4 */ 0)); 604 } 605 606 /** 607 * Checks whether the codec specific parameters are the same. 608 * 609 * @param other the codec config to compare against 610 * @return true if the codec specific parameters are the same, otherwise false. 611 * @hide 612 */ sameCodecSpecificParameters(BluetoothCodecConfig other)613 public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) { 614 if (other == null && mCodecType != other.mCodecType) { 615 return false; 616 } 617 // Currently we only care about the LDAC Playback Quality at CodecSpecific1 618 switch (mCodecType) { 619 case SOURCE_CODEC_TYPE_LDAC: 620 if (mCodecSpecific1 != other.mCodecSpecific1) { 621 return false; 622 } 623 // fall through 624 default: 625 return true; 626 } 627 } 628 } 629