diff options
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | core/jni/android_media_AudioSystem.cpp | 32 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 153 | ||||
| -rw-r--r-- | media/java/android/media/AudioRecordConfiguration.aidl | 18 | ||||
| -rw-r--r-- | media/java/android/media/AudioRecordConfiguration.java | 102 | ||||
| -rw-r--r-- | media/java/android/media/AudioSystem.java | 31 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 8 | ||||
| -rw-r--r-- | media/java/android/media/IRecordingConfigDispatcher.aidl | 28 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/AudioService.java | 22 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/RecordingActivityMonitor.java | 169 |
10 files changed, 557 insertions, 7 deletions
diff --git a/Android.mk b/Android.mk index c1c74ea04e55..b95ab2e9d0dd 100644 --- a/Android.mk +++ b/Android.mk @@ -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 \ diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 6d3c7d7363c6..dd19b6e2da74 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -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 @@ -1487,6 +1502,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, @@ -1659,6 +1680,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}, }; @@ -1762,9 +1785,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); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 60456a779525..2e2e4ec3618a 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -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. @@ -2157,7 +2158,28 @@ public class AudioManager { } break; case MSSG_RECORDING_CONFIG_CHANGE: - // cool stuff happening here in a bit, patience + // 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); @@ -2177,7 +2199,7 @@ public class AudioManager { private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() { public void dispatchAudioFocusChange(int focusChange, String id) { - Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage( + final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage( MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/); mServiceEventHandlerDelegate.getHandler().sendMessage(m); } @@ -2676,6 +2698,8 @@ public class AudioManager { } + //==================================================================== + // Audio policy /** * @hide * Register the given {@link AudioPolicy}. @@ -2728,6 +2752,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 diff --git a/media/java/android/media/AudioRecordConfiguration.aidl b/media/java/android/media/AudioRecordConfiguration.aidl new file mode 100644 index 000000000000..afe912b10b8f --- /dev/null +++ b/media/java/android/media/AudioRecordConfiguration.aidl @@ -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; diff --git a/media/java/android/media/AudioRecordConfiguration.java b/media/java/android/media/AudioRecordConfiguration.java new file mode 100644 index 000000000000..aefe692de651 --- /dev/null +++ b/media/java/android/media/AudioRecordConfiguration.java @@ -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)); + } +}
\ No newline at end of file diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index c59d1c7b9999..3d92a2a4bd9e 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -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 ...) @@ -641,6 +668,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; diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 693a519c234f..987a8b6a48ee 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -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; @@ -157,4 +159,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(); } diff --git a/media/java/android/media/IRecordingConfigDispatcher.aidl b/media/java/android/media/IRecordingConfigDispatcher.aidl new file mode 100644 index 000000000000..a5eb8b9fa787 --- /dev/null +++ b/media/java/android/media/IRecordingConfigDispatcher.aidl @@ -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(); + +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 40d01e7359d5..a66dd35c89e5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -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; @@ -705,6 +707,8 @@ public class AudioService extends IAudioService.Stub { LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); + + mRecordMonitor.initMonitor(); } public void systemReady() { @@ -6097,7 +6101,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() { @@ -6126,7 +6130,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(); } //====================== diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java new file mode 100644 index 000000000000..5806f3fb5b70 --- /dev/null +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -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); + } + } +} |