1 /* 2 * Copyright (C) 2015 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 package android.car.media; 17 18 import android.annotation.NonNull; 19 import android.annotation.RequiresPermission; 20 import android.annotation.SystemApi; 21 import android.annotation.TestApi; 22 import android.car.Car; 23 import android.car.CarLibLog; 24 import android.car.CarManagerBase; 25 import android.media.AudioAttributes; 26 import android.os.Bundle; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.Log; 30 import android.view.Display; 31 import android.view.DisplayAddress; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * APIs for handling audio in a car. 38 * 39 * In a car environment, we introduced the support to turn audio dynamic routing on /off by 40 * setting the "audioUseDynamicRouting" attribute in config.xml 41 * 42 * When audio dynamic routing is enabled: 43 * - Audio devices are grouped into zones 44 * - There is at least one primary zone, and extra secondary zones such as RSE 45 * (Reat Seat Entertainment) 46 * - Within each zone, audio devices are grouped into volume groups for volume control 47 * - Audio is assigned to an audio device based on its AudioAttributes usage 48 * 49 * When audio dynamic routing is disabled: 50 * - There is exactly one audio zone, which is the primary zone 51 * - Each volume group represents a controllable STREAM_TYPE, same as AudioManager 52 */ 53 public final class CarAudioManager extends CarManagerBase { 54 55 /** 56 * Zone id of the primary audio zone. 57 * @hide 58 */ 59 @SystemApi 60 public static final int PRIMARY_AUDIO_ZONE = 0x0; 61 62 /** 63 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 64 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events, 65 * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. 66 * The requester must hold {@link Car#PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS}; otherwise, 67 * this extra is ignored. 68 * 69 * @hide 70 */ 71 @SystemApi 72 public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = 73 "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS"; 74 75 /** 76 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 77 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus for the 78 * the zone. If the zone id is not defined: the audio focus request will default to the 79 * currently mapped zone for the requesting uid or {@link CarAudioManager.PRIMARY_AUDIO_ZONE} 80 * if no uid mapping currently exist. 81 * 82 * @hide 83 */ 84 public static final String AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID = 85 "android.car.media.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID"; 86 87 private final ICarAudio mService; 88 private final List<CarVolumeCallback> mCarVolumeCallbacks; 89 90 private final ICarVolumeCallback mCarVolumeCallbackImpl = new ICarVolumeCallback.Stub() { 91 @Override 92 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 93 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 94 callback.onGroupVolumeChanged(zoneId, groupId, flags); 95 } 96 } 97 98 @Override 99 public void onMasterMuteChanged(int zoneId, int flags) { 100 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 101 callback.onMasterMuteChanged(zoneId, flags); 102 } 103 } 104 }; 105 106 /** 107 * @return Whether dynamic routing is enabled or not. 108 * @hide 109 */ 110 @TestApi isDynamicRoutingEnabled()111 public boolean isDynamicRoutingEnabled() { 112 try { 113 return mService.isDynamicRoutingEnabled(); 114 } catch (RemoteException e) { 115 return handleRemoteExceptionFromCarService(e, false); 116 } 117 } 118 119 /** 120 * Sets the volume index for a volume group in primary zone. 121 * 122 * @see {@link #setGroupVolume(int, int, int, int)} 123 * @hide 124 */ 125 @SystemApi 126 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int groupId, int index, int flags)127 public void setGroupVolume(int groupId, int index, int flags) { 128 setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags); 129 } 130 131 /** 132 * Sets the volume index for a volume group. 133 * 134 * @param zoneId The zone id whose volume group is affected. 135 * @param groupId The volume group id whose volume index should be set. 136 * @param index The volume index to set. See 137 * {@link #getGroupMaxVolume(int, int)} for the largest valid value. 138 * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI}, 139 * {@link android.media.AudioManager#FLAG_PLAY_SOUND}) 140 * @hide 141 */ 142 @SystemApi 143 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int zoneId, int groupId, int index, int flags)144 public void setGroupVolume(int zoneId, int groupId, int index, int flags) { 145 try { 146 mService.setGroupVolume(zoneId, groupId, index, flags); 147 } catch (RemoteException e) { 148 handleRemoteExceptionFromCarService(e); 149 } 150 } 151 152 /** 153 * Returns the maximum volume index for a volume group in primary zone. 154 * 155 * @see {@link #getGroupMaxVolume(int, int)} 156 * @hide 157 */ 158 @SystemApi 159 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int groupId)160 public int getGroupMaxVolume(int groupId) { 161 return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId); 162 } 163 164 /** 165 * Returns the maximum volume index for a volume group. 166 * 167 * @param zoneId The zone id whose volume group is queried. 168 * @param groupId The volume group id whose maximum volume index is returned. 169 * @return The maximum valid volume index for the given group. 170 * @hide 171 */ 172 @SystemApi 173 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int zoneId, int groupId)174 public int getGroupMaxVolume(int zoneId, int groupId) { 175 try { 176 return mService.getGroupMaxVolume(zoneId, groupId); 177 } catch (RemoteException e) { 178 return handleRemoteExceptionFromCarService(e, 0); 179 } 180 } 181 182 /** 183 * Returns the minimum volume index for a volume group in primary zone. 184 * 185 * @see {@link #getGroupMinVolume(int, int)} 186 * @hide 187 */ 188 @SystemApi 189 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int groupId)190 public int getGroupMinVolume(int groupId) { 191 return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId); 192 } 193 194 /** 195 * Returns the minimum volume index for a volume group. 196 * 197 * @param zoneId The zone id whose volume group is queried. 198 * @param groupId The volume group id whose minimum volume index is returned. 199 * @return The minimum valid volume index for the given group, non-negative 200 * @hide 201 */ 202 @SystemApi 203 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int zoneId, int groupId)204 public int getGroupMinVolume(int zoneId, int groupId) { 205 try { 206 return mService.getGroupMinVolume(zoneId, groupId); 207 } catch (RemoteException e) { 208 return handleRemoteExceptionFromCarService(e, 0); 209 } 210 } 211 212 /** 213 * Returns the current volume index for a volume group in primary zone. 214 * 215 * @see {@link #getGroupVolume(int, int)} 216 * @hide 217 */ 218 @SystemApi 219 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int groupId)220 public int getGroupVolume(int groupId) { 221 return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId); 222 } 223 224 /** 225 * Returns the current volume index for a volume group. 226 * 227 * @param zoneId The zone id whose volume groups is queried. 228 * @param groupId The volume group id whose volume index is returned. 229 * @return The current volume index for the given group. 230 * 231 * @see #getGroupMaxVolume(int, int) 232 * @see #setGroupVolume(int, int, int, int) 233 * @hide 234 */ 235 @SystemApi 236 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int zoneId, int groupId)237 public int getGroupVolume(int zoneId, int groupId) { 238 try { 239 return mService.getGroupVolume(zoneId, groupId); 240 } catch (RemoteException e) { 241 return handleRemoteExceptionFromCarService(e, 0); 242 } 243 } 244 245 /** 246 * Adjust the relative volume in the front vs back of the vehicle cabin. 247 * 248 * @param value in the range -1.0 to 1.0 for fully toward the back through 249 * fully toward the front. 0.0 means evenly balanced. 250 * 251 * @see #setBalanceTowardRight(float) 252 * @hide 253 */ 254 @SystemApi 255 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setFadeTowardFront(float value)256 public void setFadeTowardFront(float value) { 257 try { 258 mService.setFadeTowardFront(value); 259 } catch (RemoteException e) { 260 handleRemoteExceptionFromCarService(e); 261 } 262 } 263 264 /** 265 * Adjust the relative volume on the left vs right side of the vehicle cabin. 266 * 267 * @param value in the range -1.0 to 1.0 for fully toward the left through 268 * fully toward the right. 0.0 means evenly balanced. 269 * 270 * @see #setFadeTowardFront(float) 271 * @hide 272 */ 273 @SystemApi 274 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setBalanceTowardRight(float value)275 public void setBalanceTowardRight(float value) { 276 try { 277 mService.setBalanceTowardRight(value); 278 } catch (RemoteException e) { 279 handleRemoteExceptionFromCarService(e); 280 } 281 } 282 283 /** 284 * Queries the system configuration in order to report the available, non-microphone audio 285 * input devices. 286 * 287 * @return An array of strings representing the available input ports. 288 * Each port is identified by it's "address" tag in the audioPolicyConfiguration xml file. 289 * Empty array if we find nothing. 290 * 291 * @see #createAudioPatch(String, int, int) 292 * @see #releaseAudioPatch(CarAudioPatchHandle) 293 * @hide 294 */ 295 @SystemApi 296 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getExternalSources()297 public @NonNull String[] getExternalSources() { 298 try { 299 return mService.getExternalSources(); 300 } catch (RemoteException e) { 301 handleRemoteExceptionFromCarService(e); 302 return new String[0]; 303 } 304 } 305 306 /** 307 * Given an input port identified by getExternalSources(), request that it's audio signal 308 * be routed below the HAL to the output port associated with the given usage. For example, 309 * The output of a tuner might be routed directly to the output buss associated with 310 * AudioAttributes.USAGE_MEDIA while the tuner is playing. 311 * 312 * @param sourceAddress the input port name obtained from getExternalSources(). 313 * @param usage the type of audio represented by this source (usually USAGE_MEDIA). 314 * @param gainInMillibels How many steps above the minimum value defined for the source port to 315 * set the gain when creating the patch. 316 * This may be used for source balancing without affecting the user 317 * controlled volumes applied to the destination ports. A value of 318 * 0 indicates no gain change is requested. 319 * @return A handle for the created patch which can be used to later remove it. 320 * 321 * @see #getExternalSources() 322 * @see #releaseAudioPatch(CarAudioPatchHandle) 323 * @hide 324 */ 325 @SystemApi 326 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)327 public CarAudioPatchHandle createAudioPatch(String sourceAddress, 328 @AudioAttributes.AttributeUsage int usage, int gainInMillibels) { 329 try { 330 return mService.createAudioPatch(sourceAddress, usage, gainInMillibels); 331 } catch (RemoteException e) { 332 return handleRemoteExceptionFromCarService(e, null); 333 } 334 } 335 336 /** 337 * Removes the association between an input port and an output port identified by the provided 338 * handle. 339 * 340 * @param patch CarAudioPatchHandle returned from createAudioPatch(). 341 * 342 * @see #getExternalSources() 343 * @see #createAudioPatch(String, int, int) 344 * @hide 345 */ 346 @SystemApi 347 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) releaseAudioPatch(CarAudioPatchHandle patch)348 public void releaseAudioPatch(CarAudioPatchHandle patch) { 349 try { 350 mService.releaseAudioPatch(patch); 351 } catch (RemoteException e) { 352 handleRemoteExceptionFromCarService(e); 353 } 354 } 355 356 /** 357 * Gets the count of available volume groups in primary zone. 358 * 359 * @see {@link #getVolumeGroupCount(int)} 360 * @hide 361 */ 362 @SystemApi 363 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount()364 public int getVolumeGroupCount() { 365 return getVolumeGroupCount(PRIMARY_AUDIO_ZONE); 366 } 367 368 /** 369 * Gets the count of available volume groups in the system. 370 * 371 * @param zoneId The zone id whois count of volume groups is queried. 372 * @return Count of volume groups 373 * @hide 374 */ 375 @SystemApi 376 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount(int zoneId)377 public int getVolumeGroupCount(int zoneId) { 378 try { 379 return mService.getVolumeGroupCount(zoneId); 380 } catch (RemoteException e) { 381 return handleRemoteExceptionFromCarService(e, 0); 382 } 383 } 384 385 /** 386 * Gets the volume group id for a given {@link AudioAttributes} usage in primary zone. 387 * 388 * @see {@link #getVolumeGroupIdForUsage(int, int)} 389 * @hide 390 */ 391 @SystemApi 392 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(@udioAttributes.AttributeUsage int usage)393 public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) { 394 return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage); 395 } 396 397 /** 398 * Gets the volume group id for a given {@link AudioAttributes} usage. 399 * 400 * @param zoneId The zone id whose volume group is queried. 401 * @param usage The {@link AudioAttributes} usage to get a volume group from. 402 * @return The volume group id where the usage belongs to 403 * @hide 404 */ 405 @SystemApi 406 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)407 public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) { 408 try { 409 return mService.getVolumeGroupIdForUsage(zoneId, usage); 410 } catch (RemoteException e) { 411 return handleRemoteExceptionFromCarService(e, 0); 412 } 413 } 414 415 /** 416 * Gets array of {@link AudioAttributes} usages for a volume group in primary zone. 417 * 418 * @see {@link #getUsagesForVolumeGroupId(int, int)} 419 * @hide 420 */ 421 @SystemApi 422 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int groupId)423 public @NonNull int[] getUsagesForVolumeGroupId(int groupId) { 424 return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId); 425 } 426 427 /** 428 * Gets the audio zones currently available 429 * 430 * @return audio zone ids 431 * @hide 432 */ 433 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getAudioZoneIds()434 public @NonNull int[] getAudioZoneIds() { 435 try { 436 return mService.getAudioZoneIds(); 437 } catch (RemoteException e) { 438 return handleRemoteExceptionFromCarService(e, new int[0]); 439 } 440 } 441 442 /** 443 * Gets the audio zone id currently mapped to uId, 444 * defaults to PRIMARY_AUDIO_ZONE if no mapping exist 445 * 446 * @param uid The uid to map 447 * @return zone id mapped to uid 448 * @hide 449 */ 450 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getZoneIdForUid(int uid)451 public int getZoneIdForUid(int uid) { 452 try { 453 return mService.getZoneIdForUid(uid); 454 } catch (RemoteException e) { 455 return handleRemoteExceptionFromCarService(e, 0); 456 } 457 } 458 459 /** 460 * Maps the audio zone id to uid 461 * 462 * @param zoneId The audio zone id 463 * @param uid The uid to map 464 * @return true if the uid is successfully mapped 465 * @hide 466 */ 467 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) setZoneIdForUid(int zoneId, int uid)468 public boolean setZoneIdForUid(int zoneId, int uid) { 469 try { 470 return mService.setZoneIdForUid(zoneId, uid); 471 } catch (RemoteException e) { 472 return handleRemoteExceptionFromCarService(e, false); 473 } 474 } 475 476 /** 477 * Clears the current zone mapping of the uid 478 * 479 * @param uid The uid to clear 480 * @return true if the zone was successfully cleared 481 * @hide 482 */ 483 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) clearZoneIdForUid(int uid)484 public boolean clearZoneIdForUid(int uid) { 485 try { 486 return mService.clearZoneIdForUid(uid); 487 } catch (RemoteException e) { 488 return handleRemoteExceptionFromCarService(e, false); 489 } 490 } 491 492 /** 493 * Get the zone id for the display 494 * 495 * @param display display to query 496 * @return zone id for display or 497 * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found. 498 * @hide 499 */ 500 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getZoneIdForDisplay(Display display)501 public int getZoneIdForDisplay(Display display) { 502 DisplayAddress address = display.getAddress(); 503 if (address instanceof DisplayAddress.Physical) { 504 DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address; 505 if (physicalAddress != null) { 506 return getZoneIdForDisplayPortId(physicalAddress.getPort()); 507 } 508 } 509 return PRIMARY_AUDIO_ZONE; 510 } 511 512 /** 513 * Get the zone id for the display port id passed in 514 * 515 * @param displayPortId display port id to query 516 * @return zone id for display port id or 517 * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found. 518 * @hide 519 */ 520 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getZoneIdForDisplayPortId(byte displayPortId)521 public int getZoneIdForDisplayPortId(byte displayPortId) { 522 try { 523 return mService.getZoneIdForDisplayPortId(displayPortId); 524 } catch (RemoteException e) { 525 return handleRemoteExceptionFromCarService(e, 0); 526 } 527 } 528 529 /** 530 * Gets array of {@link AudioAttributes} usages for a volume group in a zone. 531 * 532 * @param zoneId The zone id whose volume group is queried. 533 * @param groupId The volume group id whose associated audio usages is returned. 534 * @return Array of {@link AudioAttributes} usages for a given volume group id 535 * @hide 536 */ 537 @SystemApi 538 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int zoneId, int groupId)539 public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) { 540 try { 541 return mService.getUsagesForVolumeGroupId(zoneId, groupId); 542 } catch (RemoteException e) { 543 return handleRemoteExceptionFromCarService(e, new int[0]); 544 } 545 } 546 547 /** @hide */ 548 @Override onCarDisconnected()549 public void onCarDisconnected() { 550 if (mService != null) { 551 try { 552 mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 553 } catch (RemoteException e) { 554 handleRemoteExceptionFromCarService(e); 555 } 556 } 557 } 558 559 /** @hide */ CarAudioManager(Car car, IBinder service)560 public CarAudioManager(Car car, IBinder service) { 561 super(car); 562 mService = ICarAudio.Stub.asInterface(service); 563 mCarVolumeCallbacks = new ArrayList<>(); 564 try { 565 mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 566 } catch (RemoteException e) { 567 Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e); 568 } 569 } 570 571 /** 572 * Registers a {@link CarVolumeCallback} to receive volume change callbacks 573 * @param callback {@link CarVolumeCallback} instance, can not be null 574 */ registerCarVolumeCallback(@onNull CarVolumeCallback callback)575 public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) { 576 mCarVolumeCallbacks.add(callback); 577 } 578 579 /** 580 * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks 581 * @param callback {@link CarVolumeCallback} instance previously registered, can not be null 582 */ unregisterCarVolumeCallback(@onNull CarVolumeCallback callback)583 public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) { 584 mCarVolumeCallbacks.remove(callback); 585 } 586 587 /** 588 * Callback interface to receive volume change events in a car. 589 * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)} 590 * and unregister it via {@link #unregisterCarVolumeCallback(CarVolumeCallback)} 591 */ 592 public abstract static class CarVolumeCallback { 593 /** 594 * This is called whenever a group volume is changed. 595 * The changed-to volume index is not included, the caller is encouraged to 596 * get the current group volume index via CarAudioManager. 597 * 598 * @param zoneId Id of the audio zone that volume change happens 599 * @param groupId Id of the volume group that volume is changed 600 * @param flags see {@link android.media.AudioManager} for flag definitions 601 */ onGroupVolumeChanged(int zoneId, int groupId, int flags)602 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {} 603 604 /** 605 * This is called whenever the global mute state is changed. 606 * The changed-to global mute state is not included, the caller is encouraged to 607 * get the current global mute state via AudioManager. 608 * 609 * @param zoneId Id of the audio zone that global mute state change happens 610 * @param flags see {@link android.media.AudioManager} for flag definitions 611 */ onMasterMuteChanged(int zoneId, int flags)612 public void onMasterMuteChanged(int zoneId, int flags) {} 613 } 614 } 615