blob: 185c778b519717773244be9ac5a9671ae6a66846 [file] [log] [blame]
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -08001/*
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
17package com.android.server.audio;
18
19import android.annotation.NonNull;
20import android.media.AudioAttributes;
21import android.media.AudioFormat;
22import android.media.AudioManager;
23import android.media.AudioPlaybackConfiguration;
24import android.media.AudioSystem;
25import android.media.IPlaybackConfigDispatcher;
26import android.media.MediaRecorder;
27import android.media.PlayerBase;
Jean-Michel Trividce82ab2017-02-07 16:02:33 -080028import android.media.VolumeShaper;
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -080029import android.os.Binder;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080030import android.os.IBinder;
31import android.os.RemoteException;
32import android.util.Log;
33
34import java.io.PrintWriter;
35import java.text.DateFormat;
36import java.util.ArrayList;
37import java.util.Date;
38import java.util.HashMap;
39import java.util.Iterator;
40import java.util.List;
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -080041import java.util.Set;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080042
43/**
44 * Class to receive and dispatch updates from AudioSystem about recording configurations.
45 */
Jean-Michel Trivi9dc22c22017-01-05 18:06:03 -080046public final class PlaybackActivityMonitor
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -080047 implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080048
49 public final static String TAG = "AudioService.PlaybackActivityMonitor";
Jean-Michel Trivi13d9ed62017-02-03 10:18:23 -080050 private final static boolean DEBUG = false;
Jean-Michel Trividce82ab2017-02-07 16:02:33 -080051 private final static int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080052
53 private ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
54 // a public client is one that needs an anonymized version of the playback configurations, we
55 // keep track of whether there is at least one to know when we need to create the list of
56 // playback configurations that do not contain uid/pid/package name information.
57 private boolean mHasPublicClients = false;
58
59 private final Object mPlayerLock = new Object();
60 private HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
61 new HashMap<Integer, AudioPlaybackConfiguration>();
62
63 PlaybackActivityMonitor() {
Jean-Michel Trivi9dc22c22017-01-05 18:06:03 -080064 PlayMonitorClient.sListenerDeathMonitor = this;
65 AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080066 }
67
68 //=================================================================
69 // Track players and their states
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -080070 // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080071 // into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
72 // all listeners as oneway calls.
73
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -080074 public int trackPlayer(PlayerBase.PlayerIdCard pic) {
75 final int newPiid = AudioSystem.newAudioPlayerId();
76 if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
77 final AudioPlaybackConfiguration apc =
78 new AudioPlaybackConfiguration(pic, newPiid,
79 Binder.getCallingUid(), Binder.getCallingPid());
Jean-Michel Trivi9dc22c22017-01-05 18:06:03 -080080 apc.init();
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080081 synchronized(mPlayerLock) {
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -080082 mPlayers.put(newPiid, apc);
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080083 }
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -080084 return newPiid;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080085 }
86
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -080087 public void playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid) {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080088 final boolean change;
89 synchronized(mPlayerLock) {
90 final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -080091 if (checkConfigurationCaller(piid, apc, binderUid)) {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080092 change = apc.handleAudioAttributesEvent(attr);
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -080093 } else {
94 Log.e(TAG, "Error updating audio attributes");
95 change = false;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -080096 }
97 }
98 if (change) {
99 dispatchPlaybackChange();
100 }
101 }
102
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800103 public void playerEvent(int piid, int event, int binderUid) {
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -0800104 if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800105 final boolean change;
106 synchronized(mPlayerLock) {
107 final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
Jean-Michel Trivif1d82762017-02-14 14:24:37 -0800108 // FIXME SoundPool not ready for state reporting
109 if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
110 return;
111 }
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800112 if (checkConfigurationCaller(piid, apc, binderUid)) {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800113 //TODO add generation counter to only update to the latest state
114 change = apc.handleStateEvent(event);
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800115 } else {
116 Log.e(TAG, "Error handling event " + event);
117 change = false;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800118 }
119 }
120 if (change) {
121 dispatchPlaybackChange();
122 }
123 }
124
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800125 public void releasePlayer(int piid, int binderUid) {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800126 if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
127 synchronized(mPlayerLock) {
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800128 final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
129 if (checkConfigurationCaller(piid, apc, binderUid)) {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800130 mPlayers.remove(new Integer(piid));
Jean-Michel Trividce82ab2017-02-07 16:02:33 -0800131 final VolumeShaper vs = mDuckVolumeShapers.get(new Integer(piid));
132 if (vs != null) {
133 vs.release();
134 mDuckVolumeShapers.remove(new Integer(piid));
135 }
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800136 } else {
137 Log.e(TAG, "Error releasing player " + piid);
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800138 }
139 }
140 }
141
Jean-Michel Trivi9dc22c22017-01-05 18:06:03 -0800142 // Implementation of AudioPlaybackConfiguration.PlayerDeathMonitor
143 @Override
144 public void playerDeath(int piid) {
145 releasePlayer(piid, 0);
146 }
147
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800148 protected void dump(PrintWriter pw) {
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800149 // players
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800150 pw.println("\nPlaybackActivityMonitor dump time: "
151 + DateFormat.getTimeInstance().format(new Date()));
152 synchronized(mPlayerLock) {
153 for (AudioPlaybackConfiguration conf : mPlayers.values()) {
154 conf.dump(pw);
155 }
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800156 // ducked players
157 pw.println("\n ducked player piids:");
158 for (int piid : mDuckedPlayers) {
159 pw.println(" " + piid);
160 }
Jean-Michel Trivi62b86342017-02-04 15:33:47 -0800161 // players muted due to the device ringing or being in a call
162 pw.println("\n muted player piids:");
163 for (int piid : mMutedPlayers) {
164 pw.println(" " + piid);
165 }
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800166 }
167 }
168
Jean-Michel Trivi46e310b2017-01-04 15:58:02 -0800169 /**
170 * Check that piid and uid are valid for the given configuration.
171 * @param piid the piid of the player.
172 * @param apc the configuration found for this piid.
173 * @param binderUid actual uid of client trying to signal a player state/event/attributes.
174 * @return true if the call is valid and the change should proceed, false otherwise.
175 */
176 private static boolean checkConfigurationCaller(int piid,
177 final AudioPlaybackConfiguration apc, int binderUid) {
178 if (apc == null) {
179 Log.e(TAG, "Invalid operation: unknown player " + piid);
180 return false;
181 } else if ((binderUid != 0) && (apc.getClientUid() != binderUid)) {
182 Log.e(TAG, "Forbidden operation from uid " + binderUid + " for player " + piid);
183 return false;
184 }
185 return true;
186 }
187
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800188 private void dispatchPlaybackChange() {
189 synchronized (mClients) {
190 // typical use case, nobody is listening, don't do any work
191 if (mClients.isEmpty()) {
192 return;
193 }
194 }
195 if (DEBUG) { Log.v(TAG, "dispatchPlaybackChange to " + mClients.size() + " clients"); }
196 final List<AudioPlaybackConfiguration> configsSystem;
197 // list of playback configurations for "public consumption". It is only computed if there
198 // are non-system playback activity listeners.
199 final List<AudioPlaybackConfiguration> configsPublic;
200 synchronized (mPlayerLock) {
201 if (mPlayers.isEmpty()) {
202 return;
203 }
204 configsSystem = new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
205 }
206 synchronized (mClients) {
207 // was done at beginning of method, but could have changed
208 if (mClients.isEmpty()) {
209 return;
210 }
211 configsPublic = mHasPublicClients ? anonymizeForPublicConsumption(configsSystem) : null;
212 final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
213 while (clientIterator.hasNext()) {
214 final PlayMonitorClient pmc = clientIterator.next();
215 try {
216 // do not spam the logs if there are problems communicating with this client
217 if (pmc.mErrorCount < PlayMonitorClient.MAX_ERRORS) {
218 if (pmc.mIsPrivileged) {
219 pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsSystem);
220 } else {
221 pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsPublic);
222 }
223 }
224 } catch (RemoteException e) {
225 pmc.mErrorCount++;
226 Log.e(TAG, "Error (" + pmc.mErrorCount +
227 ") trying to dispatch playback config change to " + pmc, e);
228 }
229 }
230 }
231 }
232
233 private ArrayList<AudioPlaybackConfiguration> anonymizeForPublicConsumption(
234 List<AudioPlaybackConfiguration> sysConfigs) {
235 ArrayList<AudioPlaybackConfiguration> publicConfigs =
236 new ArrayList<AudioPlaybackConfiguration>();
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800237 // only add active anonymized configurations,
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800238 for (AudioPlaybackConfiguration config : sysConfigs) {
239 if (config.isActive()) {
240 publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
241 }
242 }
243 return publicConfigs;
244 }
245
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800246
247 //=================================================================
248 // PlayerFocusEnforcer implementation
249 private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
Jean-Michel Trivi62b86342017-02-04 15:33:47 -0800250 private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800251
Jean-Michel Trividce82ab2017-02-07 16:02:33 -0800252 private final VolumeShaper.Configuration DUCK_VSHAPE =
253 new VolumeShaper.Configuration.Builder()
254 .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
255 .setCurve(new float[] { 0.f, 1.f } /* times */,
256 new float[] { 1.f, 0.2f } /* volumes */)
257 .setDurationMs(MediaFocusControl.getFocusRampTimeMs(
258 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
259 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
260 .build()))
261 .build();
262
263 private final HashMap<Integer, VolumeShaper> mDuckVolumeShapers =
264 new HashMap<Integer, VolumeShaper>();
265
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800266 @Override
267 public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
268 if (DEBUG) {
269 Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
270 winner.getClientUid(), loser.getClientUid())); }
271 synchronized (mPlayerLock) {
272 if (mPlayers.isEmpty()) {
273 return true;
274 }
275 final Set<Integer> piidSet = mPlayers.keySet();
276 final Iterator<Integer> piidIterator = piidSet.iterator();
277 // find which players to duck
278 while (piidIterator.hasNext()) {
279 final Integer piid = piidIterator.next();
280 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
281 if (!winner.hasSameUid(apc.getClientUid())
282 && loser.hasSameUid(apc.getClientUid())
283 && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
284 {
285 if (mDuckedPlayers.contains(piid)) {
286 if (DEBUG) { Log.v(TAG, "player " + piid + " already ducked"); }
287 } else if (apc.getAudioAttributes().getContentType() ==
288 AudioAttributes.CONTENT_TYPE_SPEECH) {
289 // the player is speaking, ducking will make the speech unintelligible
290 // so let the app handle it instead
291 return false;
Jean-Michel Trividce82ab2017-02-07 16:02:33 -0800292 } else if (apc.getPlayerType()
293 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
294 // TODO support ducking of SoundPool players
295 return false;
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800296 } else {
297 try {
298 if (DEBUG) { Log.v(TAG, "ducking player " + piid); }
Jean-Michel Trividce82ab2017-02-07 16:02:33 -0800299 final VolumeShaper ducker;
300 if (mDuckVolumeShapers.containsKey(new Integer(piid))) {
301 ducker = mDuckVolumeShapers.get(new Integer(piid));
302 } else {
303 ducker = new VolumeShaper(
304 DUCK_VSHAPE,
305 apc.getPlayerProxy(),
306 true /* keepReference */);
307 mDuckVolumeShapers.put(new Integer(piid), ducker);
308 }
309 ducker.apply(VolumeShaper.Operation.PLAY); // duck
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800310 mDuckedPlayers.add(piid);
311 } catch (Exception e) {
312 Log.e(TAG, "Error ducking player " + piid, e);
313 // something went wrong trying to duck, so let the app handle it
314 // instead, it may know things we don't
315 return false;
316 }
317 }
318 }
319 }
320 }
321 return true;
322 }
323
324 @Override
325 public void unduckPlayers(FocusRequester winner) {
326 if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
327 synchronized (mPlayerLock) {
328 if (mDuckedPlayers.isEmpty()) {
329 return;
330 }
331 for (int piid : mDuckedPlayers) {
332 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
333 if (apc != null
334 && winner.hasSameUid(apc.getClientUid())) {
335 try {
336 if (DEBUG) { Log.v(TAG, "unducking player" + piid); }
Jean-Michel Trivi62b86342017-02-04 15:33:47 -0800337 mDuckedPlayers.remove(new Integer(piid));
Jean-Michel Trividce82ab2017-02-07 16:02:33 -0800338 if (mDuckVolumeShapers.containsKey(new Integer(piid))) {
339 final VolumeShaper ducker = mDuckVolumeShapers.get(new Integer(piid));
340 ducker.apply(VolumeShaper.Operation.REVERSE); // unduck
341 }
Jean-Michel Trivi99489cc2017-01-25 19:08:49 -0800342 } catch (Exception e) {
343 Log.e(TAG, "Error unducking player " + piid, e);
344 }
345 } else {
346 Log.e(TAG, "Error unducking player " + piid + ", player not found");
347 }
348 }
349 }
350 }
351
Jean-Michel Trivi62b86342017-02-04 15:33:47 -0800352 @Override
353 public void mutePlayersForCall(int[] usagesToMute) {
354 if (DEBUG) {
355 String log = new String("mutePlayersForCall: usages=");
356 for (int usage : usagesToMute) { log += " " + usage; }
357 Log.v(TAG, log);
358 }
359 synchronized (mPlayerLock) {
360 final Set<Integer> piidSet = mPlayers.keySet();
361 final Iterator<Integer> piidIterator = piidSet.iterator();
362 // find which players to mute
363 while (piidIterator.hasNext()) {
364 final Integer piid = piidIterator.next();
365 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
366 final int playerUsage = apc.getAudioAttributes().getUsage();
367 boolean mute = false;
368 for (int usageToMute : usagesToMute) {
369 if (playerUsage == usageToMute) {
370 mute = true;
371 break;
372 }
373 }
374 if (mute) {
375 try {
376 if (DEBUG) { Log.v(TAG, "muting player" + piid); }
377 apc.getPlayerProxy().setVolume(0.0f);
378 mMutedPlayers.add(piid);
379 } catch (Exception e) {
380 Log.e(TAG, "Error muting player " + piid, e);
381 }
382 }
383 }
384 }
385 }
386
387 @Override
388 public void unmutePlayersForCall() {
389 if (DEBUG) {
390 Log.v(TAG, "unmutePlayersForCall()");
391 }
392 synchronized (mPlayerLock) {
393 if (mMutedPlayers.isEmpty()) {
394 return;
395 }
396 for (int piid : mMutedPlayers) {
397 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
398 if (apc != null) {
399 try {
400 if (DEBUG) { Log.v(TAG, "unmuting player" + piid); }
401 apc.getPlayerProxy().setVolume(1.0f);
Jean-Michel Trivi62b86342017-02-04 15:33:47 -0800402 } catch (Exception e) {
403 Log.e(TAG, "Error unmuting player " + piid, e);
404 }
405 }
406 }
Jean-Michel Trivi579c5112017-02-10 09:47:30 -0800407 mMutedPlayers.clear();
Jean-Michel Trivi62b86342017-02-04 15:33:47 -0800408 }
409 }
410
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800411 //=================================================================
412 // Track playback activity listeners
413
414 void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
415 if (pcdb == null) {
416 return;
417 }
418 synchronized(mClients) {
419 final PlayMonitorClient pmc = new PlayMonitorClient(pcdb, isPrivileged);
420 if (pmc.init()) {
421 if (!isPrivileged) {
422 mHasPublicClients = true;
423 }
424 mClients.add(pmc);
425 }
426 }
427 }
428
429 void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
430 if (pcdb == null) {
431 return;
432 }
433 synchronized(mClients) {
434 final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
435 boolean hasPublicClients = false;
436 // iterate over the clients to remove the dispatcher to remove, and reevaluate at
437 // the same time if we still have a public client.
438 while (clientIterator.hasNext()) {
439 PlayMonitorClient pmc = clientIterator.next();
440 if (pcdb.equals(pmc.mDispatcherCb)) {
441 pmc.release();
442 clientIterator.remove();
443 } else {
444 if (!pmc.mIsPrivileged) {
445 hasPublicClients = true;
446 }
447 }
448 }
449 mHasPublicClients = hasPublicClients;
450 }
451 }
452
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -0800453 List<AudioPlaybackConfiguration> getActivePlaybackConfigurations(boolean isPrivileged) {
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800454 synchronized(mPlayers) {
Jean-Michel Trivi44a8f532017-01-02 14:36:43 -0800455 if (isPrivileged) {
456 return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
457 } else {
458 final List<AudioPlaybackConfiguration> configsPublic;
459 synchronized (mPlayerLock) {
460 configsPublic = anonymizeForPublicConsumption(
461 new ArrayList<AudioPlaybackConfiguration>(mPlayers.values()));
462 }
463 return configsPublic;
464 }
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800465 }
466 }
467
468
469 /**
470 * Inner class to track clients that want to be notified of playback updates
471 */
472 private final static class PlayMonitorClient implements IBinder.DeathRecipient {
473
474 // can afford to be static because only one PlaybackActivityMonitor ever instantiated
Jean-Michel Trivi9dc22c22017-01-05 18:06:03 -0800475 static PlaybackActivityMonitor sListenerDeathMonitor;
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800476
477 final IPlaybackConfigDispatcher mDispatcherCb;
478 final boolean mIsPrivileged;
479
480 int mErrorCount = 0;
481 // number of errors after which we don't update this client anymore to not spam the logs
482 static final int MAX_ERRORS = 5;
483
484 PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
485 mDispatcherCb = pcdb;
486 mIsPrivileged = isPrivileged;
487 }
488
489 public void binderDied() {
490 Log.w(TAG, "client died");
Jean-Michel Trivi9dc22c22017-01-05 18:06:03 -0800491 sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
Jean-Michel Trivi292a6a42016-12-01 08:32:15 -0800492 }
493
494 boolean init() {
495 try {
496 mDispatcherCb.asBinder().linkToDeath(this, 0);
497 return true;
498 } catch (RemoteException e) {
499 Log.w(TAG, "Could not link to client death", e);
500 return false;
501 }
502 }
503
504 void release() {
505 mDispatcherCb.asBinder().unlinkToDeath(this, 0);
506 }
507 }
508}