Audio recording notification API
AudioService can register a recording callback with AudioSystem. Callbacks are handled in a new class, RecordingActivityMonitor. This class notifies registered AudioManager instances of updates, only if this AudioManager has client callbacks. Each AudioManager dispatches updates to its registered callbacks. Bug 22876530 Change-Id: I95c6e5ec0631e53af53a490bcecca96089490089
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
18
media/java/android/media/AudioRecordConfiguration.aidl
Normal file
18
media/java/android/media/AudioRecordConfiguration.aidl
Normal 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;
|
||||
102
media/java/android/media/AudioRecordConfiguration.java
Normal file
102
media/java/android/media/AudioRecordConfiguration.java
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
28
media/java/android/media/IRecordingConfigDispatcher.aidl
Normal file
28
media/java/android/media/IRecordingConfigDispatcher.aidl
Normal 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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
//======================
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user