Merge changes from topic 'recordingcallback'

* changes:
  Audio recording notification API
  AudioManager event dispatcher: make more generic
This commit is contained in:
Jean-Michel Trivi
2016-01-13 00:20:56 +00:00
committed by Android (Google) Code Review
10 changed files with 589 additions and 22 deletions

View File

@@ -345,6 +345,7 @@ LOCAL_SRC_FILES += \
media/java/android/media/IMediaRouterService.aidl \
media/java/android/media/IMediaScannerListener.aidl \
media/java/android/media/IMediaScannerService.aidl \
media/java/android/media/IRecordingConfigDispatcher.aidl \
media/java/android/media/IRemoteDisplayCallback.aidl \
media/java/android/media/IRemoteDisplayProvider.aidl \
media/java/android/media/IRemoteVolumeController.aidl \

View File

@@ -152,7 +152,8 @@ static struct {
static struct {
jmethodID postDynPolicyEventFromNative;
} gDynPolicyEventHandlerMethods;
jmethodID postRecordConfigEventFromNative;
} gAudioPolicyEventHandlerMethods;
static Mutex gLock;
@@ -378,12 +379,26 @@ android_media_AudioSystem_dyn_policy_callback(int event, String8 regId, int val)
const char* zechars = regId.string();
jstring zestring = env->NewStringUTF(zechars);
env->CallStaticVoidMethod(clazz, gDynPolicyEventHandlerMethods.postDynPolicyEventFromNative,
env->CallStaticVoidMethod(clazz, gAudioPolicyEventHandlerMethods.postDynPolicyEventFromNative,
event, zestring, val);
env->ReleaseStringUTFChars(zestring, zechars);
env->DeleteLocalRef(clazz);
}
static void
android_media_AudioSystem_recording_callback(int event, int session, int source)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (env == NULL) {
return;
}
jclass clazz = env->FindClass(kClassPathName);
env->CallStaticVoidMethod(clazz,
gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative,
event, session, source);
env->DeleteLocalRef(clazz);
}
static jint
@@ -1503,6 +1518,12 @@ android_media_AudioSystem_registerDynPolicyCallback(JNIEnv *env, jobject thiz)
AudioSystem::setDynPolicyCallback(android_media_AudioSystem_dyn_policy_callback);
}
static void
android_media_AudioSystem_registerRecordingCallback(JNIEnv *env, jobject thiz)
{
AudioSystem::setRecordConfigCallback(android_media_AudioSystem_recording_callback);
}
static jint convertAudioMixToNative(JNIEnv *env,
AudioMix *nAudioMix,
@@ -1677,6 +1698,8 @@ static const JNINativeMethod gMethods[] = {
(void *)android_media_AudioSystem_registerPolicyMixes},
{"native_register_dynamic_policy_callback", "()V",
(void *)android_media_AudioSystem_registerDynPolicyCallback},
{"native_register_recording_callback", "()V",
(void *)android_media_AudioSystem_registerRecordingCallback},
{"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
};
@@ -1780,9 +1803,12 @@ int register_android_media_AudioSystem(JNIEnv *env)
gEventHandlerFields.mJniCallback = GetFieldIDOrDie(env,
eventHandlerClass, "mJniCallback", "J");
gDynPolicyEventHandlerMethods.postDynPolicyEventFromNative =
gAudioPolicyEventHandlerMethods.postDynPolicyEventFromNative =
GetStaticMethodIDOrDie(env, env->FindClass(kClassPathName),
"dynamicPolicyCallbackFromNative", "(ILjava/lang/String;I)V");
gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative =
GetStaticMethodIDOrDie(env, env->FindClass(kClassPathName),
"recordingCallbackFromNative", "(III)V");
jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);

View File

@@ -51,6 +51,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* AudioManager provides access to volume and ringer mode control.
@@ -2158,36 +2159,73 @@ public class AudioManager {
}
/**
* Handler for audio focus events coming from the audio service.
* Handler for events (audio focus change, recording config change) coming from the
* audio service.
*/
private final FocusEventHandlerDelegate mAudioFocusEventHandlerDelegate =
new FocusEventHandlerDelegate();
private final ServiceEventHandlerDelegate mServiceEventHandlerDelegate =
new ServiceEventHandlerDelegate();
/**
* Helper class to handle the forwarding of audio focus events to the appropriate listener
* Event types
*/
private class FocusEventHandlerDelegate {
private final static int MSSG_FOCUS_CHANGE = 0;
private final static int MSSG_RECORDING_CONFIG_CHANGE = 1;
/**
* Helper class to handle the forwarding of audio service events to the appropriate listener
*/
private class ServiceEventHandlerDelegate {
private final Handler mHandler;
FocusEventHandlerDelegate() {
ServiceEventHandlerDelegate() {
Looper looper;
if ((looper = Looper.myLooper()) == null) {
looper = Looper.getMainLooper();
}
if (looper != null) {
// implement the event handler delegate to receive audio focus events
// implement the event handler delegate to receive events from audio service
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
OnAudioFocusChangeListener listener = null;
synchronized(mFocusListenerLock) {
listener = findFocusListener((String)msg.obj);
}
if (listener != null) {
Log.d(TAG, "AudioManager dispatching onAudioFocusChange("
+ msg.what + ") for " + msg.obj);
listener.onAudioFocusChange(msg.what);
switch (msg.what) {
case MSSG_FOCUS_CHANGE:
OnAudioFocusChangeListener listener = null;
synchronized(mFocusListenerLock) {
listener = findFocusListener((String)msg.obj);
}
if (listener != null) {
Log.d(TAG, "AudioManager dispatching onAudioFocusChange("
+ msg.what + ") for " + msg.obj);
listener.onAudioFocusChange(msg.arg1);
}
break;
case MSSG_RECORDING_CONFIG_CHANGE:
// optimizing for the case of a single callback
AudioRecordingCallback singleCallback = null;
ArrayList<AudioRecordingCallback> multipleCallbacks = null;
synchronized(mRecordCallbackLock) {
if ((mRecordCallbackList != null)
&& (mRecordCallbackList.size() != 0)) {
if (mRecordCallbackList.size() == 1) {
singleCallback = mRecordCallbackList.get(0);
} else {
multipleCallbacks =
new ArrayList<AudioRecordingCallback>(
mRecordCallbackList);
}
}
}
if (singleCallback != null) {
singleCallback.onRecordConfigChanged();
} else if (multipleCallbacks != null) {
for (int i=0 ; i < multipleCallbacks.size() ; i++) {
multipleCallbacks.get(i).onRecordConfigChanged();
}
}
break;
default:
Log.e(TAG, "Unknown event " + msg.what);
}
}
};
@@ -2204,8 +2242,9 @@ public class AudioManager {
private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
public void dispatchAudioFocusChange(int focusChange, String id) {
Message m = mAudioFocusEventHandlerDelegate.getHandler().obtainMessage(focusChange, id);
mAudioFocusEventHandlerDelegate.getHandler().sendMessage(m);
final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/);
mServiceEventHandlerDelegate.getHandler().sendMessage(m);
}
};
@@ -2702,6 +2741,8 @@ public class AudioManager {
}
//====================================================================
// Audio policy
/**
* @hide
* Register the given {@link AudioPolicy}.
@@ -2754,6 +2795,131 @@ public class AudioManager {
}
//====================================================================
// Recording configuration
/**
* @hide
* candidate for public API
*/
public static abstract class AudioRecordingCallback {
/**
* @hide
* candidate for public API
*/
public void onRecordConfigChanged() {}
}
/**
* @hide
* candidate for public API
* @param non-null callback
*/
public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
if (cb == null) {
throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument");
}
synchronized(mRecordCallbackLock) {
// lazy initialization of the list of recording callbacks
if (mRecordCallbackList == null) {
mRecordCallbackList = new ArrayList<AudioRecordingCallback>();
}
final int oldCbCount = mRecordCallbackList.size();
if (!mRecordCallbackList.contains(cb)) {
mRecordCallbackList.add(cb);
final int newCbCount = mRecordCallbackList.size();
if ((oldCbCount == 0) && (newCbCount > 0)) {
// register binder for callbacks
final IAudioService service = getService();
try {
service.registerRecordingCallback(mRecCb);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerRecordingCallback", e);
}
}
} else {
Log.w(TAG, "attempt to call registerAudioRecordingCallback() on a previously"
+ "registered callback");
}
}
}
/**
* @hide
* candidate for public API
* @param non-null callback
*/
public void unregisterAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
if (cb == null) {
throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument");
}
synchronized(mRecordCallbackLock) {
if (mRecordCallbackList == null) {
return;
}
final int oldCbCount = mRecordCallbackList.size();
if (mRecordCallbackList.remove(cb)) {
final int newCbCount = mRecordCallbackList.size();
if ((oldCbCount > 0) && (newCbCount == 0)) {
// unregister binder for callbacks
final IAudioService service = getService();
try {
service.unregisterRecordingCallback(mRecCb);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterRecordingCallback", e);
}
}
} else {
Log.w(TAG, "attempt to call unregisterAudioRecordingCallback() on a callback"
+ " already unregistered or never registered");
}
}
}
/**
* @hide
* candidate for public API
* @return a non-null array of recording configurations. An array of length 0 indicates there is
* no recording active when queried.
*/
public @NonNull AudioRecordConfiguration[] getActiveRecordConfigurations() {
final IAudioService service = getService();
try {
return service.getActiveRecordConfigurations();
} catch (RemoteException e) {
Log.e(TAG, "Unable to retrieve active record configurations", e);
return null;
}
}
/**
* constants for the recording events, to keep in sync
* with frameworks/av/include/media/AudioPolicy.h
*/
/** @hide */
public final static int RECORD_CONFIG_EVENT_START = 1;
/** @hide */
public final static int RECORD_CONFIG_EVENT_STOP = 0;
/**
* All operations on this list are sync'd on mRecordCallbackLock.
* List is lazy-initialized in {@link #registerAudioRecordingCallback(AudioRecordingCallback)}.
* List can be null.
*/
private List<AudioRecordingCallback> mRecordCallbackList;
private final Object mRecordCallbackLock = new Object();
private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() {
public void dispatchRecordingConfigChange() {
final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
MSSG_RECORDING_CONFIG_CHANGE/*what*/);
mServiceEventHandlerDelegate.getHandler().sendMessage(m);
}
};
//=====================================================================
/**
* @hide
* Reload audio settings. This method is called by Settings backup

View File

@@ -0,0 +1,18 @@
/* Copyright 2016, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.media;
parcelable AudioRecordConfiguration;

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* @hide
* Candidate for public API, see AudioManager.getActiveRecordConfiguration()
*
*/
public class AudioRecordConfiguration implements Parcelable {
private final int mSessionId;
private final int mClientSource;
/**
* @hide
*/
public AudioRecordConfiguration(int session, int source) {
mSessionId = session;
mClientSource = source;
}
/**
* @return one of AudioSource.MIC, AudioSource.VOICE_UPLINK,
* AudioSource.VOICE_DOWNLINK, AudioSource.VOICE_CALL,
* AudioSource.CAMCORDER, AudioSource.VOICE_RECOGNITION,
* AudioSource.VOICE_COMMUNICATION.
*/
public int getClientAudioSource() { return mClientSource; }
/**
* @return the session number of the recorder.
*/
public int getAudioSessionId() { return mSessionId; }
public static final Parcelable.Creator<AudioRecordConfiguration> CREATOR
= new Parcelable.Creator<AudioRecordConfiguration>() {
/**
* Rebuilds an AudioRecordConfiguration previously stored with writeToParcel().
* @param p Parcel object to read the AudioRecordConfiguration from
* @return a new AudioRecordConfiguration created from the data in the parcel
*/
public AudioRecordConfiguration createFromParcel(Parcel p) {
return new AudioRecordConfiguration(p);
}
public AudioRecordConfiguration[] newArray(int size) {
return new AudioRecordConfiguration[size];
}
};
@Override
public int hashCode() {
return Objects.hash(mSessionId, mClientSource);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSessionId);
dest.writeInt(mClientSource);
}
private AudioRecordConfiguration(Parcel in) {
mSessionId = in.readInt();
mClientSource = in.readInt();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof AudioRecordConfiguration)) return false;
final AudioRecordConfiguration that = (AudioRecordConfiguration) o;
return ((mSessionId == that.mSessionId)
&& (mClientSource == that.mClientSource));
}
}

View File

@@ -227,7 +227,7 @@ public class AudioSystem
}
/**
* Handles events for the audio policy manager about dynamic audio policies
* Handles events from the audio policy manager about dynamic audio policies
* @see android.media.audiopolicy.AudioPolicy
*/
public interface DynamicPolicyCallback
@@ -267,6 +267,33 @@ public class AudioSystem
}
}
/**
* Handles events from the audio policy manager about recording events
* @see android.media.AudioManager.AudioRecordingCallback
*/
public interface AudioRecordingCallback
{
void onRecordingConfigurationChanged(int event, int session, int source);
}
private static AudioRecordingCallback sRecordingCallback;
public static void setRecordingCallback(AudioRecordingCallback cb) {
synchronized (AudioSystem.class) {
sRecordingCallback = cb;
native_register_recording_callback();
}
}
private static void recordingCallbackFromNative(int event, int session, int source) {
AudioRecordingCallback cb = null;
synchronized (AudioSystem.class) {
cb = sRecordingCallback;
}
if (cb != null) {
cb.onRecordingConfigurationChanged(event, session, source);
}
}
/*
* Error codes used by public APIs (AudioTrack, AudioRecord, AudioManager ...)
@@ -646,6 +673,8 @@ public class AudioSystem
// declare this instance as having a dynamic policy callback handler
private static native final void native_register_dynamic_policy_callback();
// declare this instance as having a recording configuration update callback handler
private static native final void native_register_recording_callback();
// must be kept in sync with value in include/system/audio.h
public static final int AUDIO_HW_SYNC_INVALID = 0;

View File

@@ -20,9 +20,11 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.media.AudioAttributes;
import android.media.AudioRecordConfiguration;
import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.Rating;
@@ -161,4 +163,10 @@ interface IAudioService {
int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
void setVolumePolicy(in VolumePolicy policy);
void registerRecordingCallback(in IRecordingConfigDispatcher rcdb);
oneway void unregisterRecordingCallback(in IRecordingConfigDispatcher rcdb);
AudioRecordConfiguration[] getActiveRecordConfigurations();
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
/**
* AIDL for the RecordingActivity monitor in AudioService to signal audio recording updates.
*
* {@hide}
*/
oneway interface IRecordingConfigDispatcher {
void dispatchRecordingConfigChange();
}

View File

@@ -58,10 +58,12 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPort;
import android.media.AudioRecordConfiguration;
import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioService;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.MediaPlayer;
@@ -706,6 +708,8 @@ public class AudioService extends IAudioService.Stub {
LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
mRecordMonitor.initMonitor();
}
public void systemReady() {
@@ -6165,7 +6169,7 @@ public class AudioService extends IAudioService.Stub {
}
//======================
// Audio policy callback from AudioSystem
// Audio policy callbacks from AudioSystem for dynamic policies
//======================
private final AudioSystem.DynamicPolicyCallback mDynPolicyCallback =
new AudioSystem.DynamicPolicyCallback() {
@@ -6194,7 +6198,23 @@ public class AudioService extends IAudioService.Stub {
}
}
}
}
//======================
// Audio policy callbacks from AudioSystem for recording configuration updates
//======================
private final RecordingActivityMonitor mRecordMonitor = new RecordingActivityMonitor();
public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
mRecordMonitor.registerRecordingCallback(rcdb);
}
public void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
mRecordMonitor.unregisterRecordingCallback(rcdb);
}
public AudioRecordConfiguration[] getActiveRecordConfigurations() {
return mRecordMonitor.getActiveRecordConfigurations();
}
//======================

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.audio;
import android.media.AudioManager;
import android.media.AudioRecordConfiguration;
import android.media.AudioSystem;
import android.media.IRecordingConfigDispatcher;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
*/
public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
public final static String TAG = "AudioService.RecordingActivityMonitor";
private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
private HashMap<Integer, AudioRecordConfiguration> mRecordConfigs =
new HashMap<Integer, AudioRecordConfiguration>();
RecordingActivityMonitor() {
RecMonitorClient.sMonitor = this;
}
/**
* Implementation of android.media.AudioSystem.AudioRecordingCallback
*/
public void onRecordingConfigurationChanged(int event, int session, int source) {
if (updateSnapshot(event, session, source)) {
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
synchronized(mClients) {
while (clientIterator.hasNext()) {
try {
clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange();
} catch (RemoteException e) {
Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
}
}
}
}
}
void initMonitor() {
AudioSystem.setRecordingCallback(this);
}
void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
if (rcdb == null) {
return;
}
synchronized(mClients) {
final RecMonitorClient rmc = new RecMonitorClient(rcdb);
if (rmc.init()) {
mClients.add(rmc);
}
}
}
void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
if (rcdb == null) {
return;
}
synchronized(mClients) {
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
while (clientIterator.hasNext()) {
RecMonitorClient rmc = clientIterator.next();
if (rcdb.equals(rmc.mDispatcherCb)) {
rmc.release();
clientIterator.remove();
break;
}
}
}
}
AudioRecordConfiguration[] getActiveRecordConfigurations() {
synchronized(mRecordConfigs) {
return mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]);
}
}
/**
* Update the internal "view" of the active recording sessions
* @param event
* @param session
* @param source
* @return true if the list of active recording sessions has been modified, false otherwise.
*/
private boolean updateSnapshot(int event, int session, int source) {
synchronized(mRecordConfigs) {
switch (event) {
case AudioManager.RECORD_CONFIG_EVENT_STOP:
// return failure if an unknown recording session stopped
return (mRecordConfigs.remove(new Integer(session)) != null);
case AudioManager.RECORD_CONFIG_EVENT_START:
if (mRecordConfigs.containsKey(new Integer(session))) {
// start of session that's already tracked, not worth an update
// TO DO in the future when tracking record format: there might be a record
// format change during a recording that requires reporting
return false;
} else {
mRecordConfigs.put(new Integer(session),
new AudioRecordConfiguration(session, source));
return true;
}
default:
Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
event, session, source));
return false;
}
}
}
/**
* Inner class to track clients that want to be notified of recording updates
*/
private final static class RecMonitorClient implements IBinder.DeathRecipient {
// can afford to be static because only one RecordingActivityMonitor ever instantiated
static RecordingActivityMonitor sMonitor;
final IRecordingConfigDispatcher mDispatcherCb;
RecMonitorClient(IRecordingConfigDispatcher rcdb) {
mDispatcherCb = rcdb;
}
public void binderDied() {
Log.w(TAG, "client died");
sMonitor.unregisterRecordingCallback(mDispatcherCb);
}
boolean init() {
try {
mDispatcherCb.asBinder().linkToDeath(this, 0);
return true;
} catch (RemoteException e) {
Log.w(TAG, "Could not link to client death", e);
return false;
}
}
void release() {
mDispatcherCb.asBinder().unlinkToDeath(this, 0);
}
}
}