Loading media/java/android/media/AudioRecord.java +1 −0 Original line number Diff line number Diff line Loading @@ -1219,6 +1219,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, AudioManager.unregisterAudioPolicyAsyncStatic(mAudioCapturePolicy); mAudioCapturePolicy = null; } mRecordingInfoImpl.endRecordingCallbackHandling(); native_release(); mState = STATE_UNINITIALIZED; } Loading media/java/android/media/AudioRecordingMonitorImpl.java +53 −112 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.media.AudioManager.AudioRecordingCallback; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; Loading @@ -46,9 +47,27 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { private static final String TAG = "android.media.AudioRecordingMonitor"; private static IAudioService sService; //lazy initialization, use getService() private final AudioRecordingMonitorClient mClient; private final Object mRecordCallbackLock = new Object(); @GuardedBy("mRecordCallbackLock") private List<AudioRecordingCallbackInfo> mRecordCallbackList = new ArrayList<AudioRecordingCallbackInfo>(); private final IRecordingConfigDispatcher mRecordingCallback = new IRecordingConfigDispatcher.Stub() { @Override public void dispatchRecordingConfigChange( List<AudioRecordingConfiguration> configs) { AudioRecordingConfiguration config = getMyConfig(configs); if (config != null) { configs.removeIf(c -> c != config); handleCallback(configs); } } }; AudioRecordingMonitorImpl(@NonNull AudioRecordingMonitorClient client) { mClient = client; } Loading @@ -62,7 +81,7 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { * @param cb non-null callback to register */ public void registerAudioRecordingCallback(@NonNull @CallbackExecutor Executor executor, @NonNull AudioManager.AudioRecordingCallback cb) { @NonNull AudioRecordingCallback cb) { if (cb == null) { throw new IllegalArgumentException("Illegal null AudioRecordingCallback"); } Loading @@ -72,13 +91,19 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { synchronized (mRecordCallbackLock) { // check if eventCallback already in list for (AudioRecordingCallbackInfo arci : mRecordCallbackList) { if (arci.mCb == cb) { if (arci.cb == cb) { throw new IllegalArgumentException( "AudioRecordingCallback already registered"); } } beginRecordingCallbackHandling(); mRecordCallbackList.add(new AudioRecordingCallbackInfo(executor, cb)); if (mRecordCallbackList.isEmpty()) { try { getService().registerRecordingCallback(mRecordingCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb, executor)); } } Loading @@ -87,25 +112,20 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { * {@link #registerAudioRecordingCallback(Executor, AudioManager.AudioRecordingCallback)}. * @param cb non-null callback to unregister */ public void unregisterAudioRecordingCallback(@NonNull AudioManager.AudioRecordingCallback cb) { public void unregisterAudioRecordingCallback(@NonNull AudioRecordingCallback cb) { if (cb == null) { throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument"); } synchronized (mRecordCallbackLock) { for (AudioRecordingCallbackInfo arci : mRecordCallbackList) { if (arci.mCb == cb) { // ok to remove while iterating over list as we exit iteration mRecordCallbackList.remove(arci); if (mRecordCallbackList.removeIf((arci) -> arci.cb == cb)) { if (mRecordCallbackList.size() == 0) { endRecordingCallbackHandling(); } return; } } } else { throw new IllegalArgumentException("AudioRecordingCallback was not registered"); } } } /** * Returns the current active audio recording for this audio recorder. Loading @@ -123,110 +143,31 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { } } private static class AudioRecordingCallbackInfo { final AudioManager.AudioRecordingCallback mCb; final Executor mExecutor; AudioRecordingCallbackInfo(Executor e, AudioManager.AudioRecordingCallback cb) { mExecutor = e; mCb = cb; private static record AudioRecordingCallbackInfo(AudioRecordingCallback cb, Executor executor) { void dispatch(List<AudioRecordingConfiguration> configs) { executor.execute(() -> cb.onRecordingConfigChanged(configs)); } } private static final int MSG_RECORDING_CONFIG_CHANGE = 1; private final Object mRecordCallbackLock = new Object(); @GuardedBy("mRecordCallbackLock") @NonNull private LinkedList<AudioRecordingCallbackInfo> mRecordCallbackList = new LinkedList<AudioRecordingCallbackInfo>(); @GuardedBy("mRecordCallbackLock") private @Nullable HandlerThread mRecordingCallbackHandlerThread; @GuardedBy("mRecordCallbackLock") private @Nullable volatile Handler mRecordingCallbackHandler; @GuardedBy("mRecordCallbackLock") private final IRecordingConfigDispatcher mRecordingCallback = new IRecordingConfigDispatcher.Stub() { @Override public void dispatchRecordingConfigChange(List<AudioRecordingConfiguration> configs) { AudioRecordingConfiguration config = getMyConfig(configs); if (config != null) { private void handleCallback(List<AudioRecordingConfiguration> configs) { List<AudioRecordingCallbackInfo> cbInfoList; synchronized (mRecordCallbackLock) { if (mRecordingCallbackHandler != null) { final Message m = mRecordingCallbackHandler.obtainMessage( MSG_RECORDING_CONFIG_CHANGE/*what*/, config /*obj*/); mRecordingCallbackHandler.sendMessage(m); } } } } }; @GuardedBy("mRecordCallbackLock") private void beginRecordingCallbackHandling() { if (mRecordingCallbackHandlerThread == null) { mRecordingCallbackHandlerThread = new HandlerThread(TAG + ".RecordingCallback"); mRecordingCallbackHandlerThread.start(); final Looper looper = mRecordingCallbackHandlerThread.getLooper(); if (looper != null) { mRecordingCallbackHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_RECORDING_CONFIG_CHANGE: { if (msg.obj == null) { if (mRecordCallbackList.isEmpty()) { return; } ArrayList<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>(); configs.add((AudioRecordingConfiguration) msg.obj); final LinkedList<AudioRecordingCallbackInfo> cbInfoList; synchronized (mRecordCallbackLock) { if (mRecordCallbackList.size() == 0) { return; cbInfoList = new ArrayList<AudioRecordingCallbackInfo>(mRecordCallbackList); } cbInfoList = new LinkedList<AudioRecordingCallbackInfo>( mRecordCallbackList); Binder.withCleanCallingIdentity(() -> cbInfoList.forEach(cbi -> cbi.dispatch(configs))); } final long identity = Binder.clearCallingIdentity(); // Package private for cleanup from AudioRecord#release // AudioService tolerates double unregister calls void endRecordingCallbackHandling() { try { for (AudioRecordingCallbackInfo cbi : cbInfoList) { cbi.mExecutor.execute(() -> cbi.mCb.onRecordingConfigChanged(configs)); } } finally { Binder.restoreCallingIdentity(identity); } } break; default: Log.e(TAG, "Unknown event " + msg.what); break; } } }; final IAudioService service = getService(); try { service.registerRecordingCallback(mRecordingCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } } @GuardedBy("mRecordCallbackLock") private void endRecordingCallbackHandling() { if (mRecordingCallbackHandlerThread != null) { final IAudioService service = getService(); try { service.unregisterRecordingCallback(mRecordingCallback); getService().unregisterRecordingCallback(mRecordingCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mRecordingCallbackHandlerThread.quit(); mRecordingCallbackHandlerThread = null; } } AudioRecordingConfiguration getMyConfig(List<AudioRecordingConfiguration> configs) { Loading media/java/android/media/MediaRecorder.java +6 −1 Original line number Diff line number Diff line Loading @@ -1975,7 +1975,12 @@ public class MediaRecorder implements AudioRouting, * may be expected when unnecessary multiple instances are used * at the same time. */ public native void release(); public void release() { native_release(); mRecordingInfoImpl.endRecordingCallbackHandling(); } private native void native_release(); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private static native final void native_init(); Loading media/jni/android_media_MediaRecorder.cpp +3 −3 Original line number Diff line number Diff line Loading @@ -574,7 +574,7 @@ android_media_MediaRecorder_native_reset(JNIEnv *env, jobject thiz) } static void android_media_MediaRecorder_release(JNIEnv *env, jobject thiz) android_media_MediaRecorder_native_release(JNIEnv *env, jobject thiz) { ALOGV("release"); sp<MediaRecorder> mr = setMediaRecorder(env, thiz, 0); Loading Loading @@ -669,7 +669,7 @@ static void android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz) { ALOGV("finalize"); android_media_MediaRecorder_release(env, thiz); android_media_MediaRecorder_native_release(env, thiz); } void android_media_MediaRecorder_setInputSurface( Loading Loading @@ -891,7 +891,7 @@ static const JNINativeMethod gMethods[] = { {"pause", "()V", (void *)android_media_MediaRecorder_pause}, {"resume", "()V", (void *)android_media_MediaRecorder_resume}, {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, {"release", "()V", (void *)android_media_MediaRecorder_release}, {"native_release", "()V", (void *)android_media_MediaRecorder_native_release}, {"native_init", "()V", (void *)android_media_MediaRecorder_native_init}, {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V", (void *)android_media_MediaRecorder_native_setup}, Loading Loading
media/java/android/media/AudioRecord.java +1 −0 Original line number Diff line number Diff line Loading @@ -1219,6 +1219,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, AudioManager.unregisterAudioPolicyAsyncStatic(mAudioCapturePolicy); mAudioCapturePolicy = null; } mRecordingInfoImpl.endRecordingCallbackHandling(); native_release(); mState = STATE_UNINITIALIZED; } Loading
media/java/android/media/AudioRecordingMonitorImpl.java +53 −112 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.media.AudioManager.AudioRecordingCallback; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; Loading @@ -46,9 +47,27 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { private static final String TAG = "android.media.AudioRecordingMonitor"; private static IAudioService sService; //lazy initialization, use getService() private final AudioRecordingMonitorClient mClient; private final Object mRecordCallbackLock = new Object(); @GuardedBy("mRecordCallbackLock") private List<AudioRecordingCallbackInfo> mRecordCallbackList = new ArrayList<AudioRecordingCallbackInfo>(); private final IRecordingConfigDispatcher mRecordingCallback = new IRecordingConfigDispatcher.Stub() { @Override public void dispatchRecordingConfigChange( List<AudioRecordingConfiguration> configs) { AudioRecordingConfiguration config = getMyConfig(configs); if (config != null) { configs.removeIf(c -> c != config); handleCallback(configs); } } }; AudioRecordingMonitorImpl(@NonNull AudioRecordingMonitorClient client) { mClient = client; } Loading @@ -62,7 +81,7 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { * @param cb non-null callback to register */ public void registerAudioRecordingCallback(@NonNull @CallbackExecutor Executor executor, @NonNull AudioManager.AudioRecordingCallback cb) { @NonNull AudioRecordingCallback cb) { if (cb == null) { throw new IllegalArgumentException("Illegal null AudioRecordingCallback"); } Loading @@ -72,13 +91,19 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { synchronized (mRecordCallbackLock) { // check if eventCallback already in list for (AudioRecordingCallbackInfo arci : mRecordCallbackList) { if (arci.mCb == cb) { if (arci.cb == cb) { throw new IllegalArgumentException( "AudioRecordingCallback already registered"); } } beginRecordingCallbackHandling(); mRecordCallbackList.add(new AudioRecordingCallbackInfo(executor, cb)); if (mRecordCallbackList.isEmpty()) { try { getService().registerRecordingCallback(mRecordingCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb, executor)); } } Loading @@ -87,25 +112,20 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { * {@link #registerAudioRecordingCallback(Executor, AudioManager.AudioRecordingCallback)}. * @param cb non-null callback to unregister */ public void unregisterAudioRecordingCallback(@NonNull AudioManager.AudioRecordingCallback cb) { public void unregisterAudioRecordingCallback(@NonNull AudioRecordingCallback cb) { if (cb == null) { throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument"); } synchronized (mRecordCallbackLock) { for (AudioRecordingCallbackInfo arci : mRecordCallbackList) { if (arci.mCb == cb) { // ok to remove while iterating over list as we exit iteration mRecordCallbackList.remove(arci); if (mRecordCallbackList.removeIf((arci) -> arci.cb == cb)) { if (mRecordCallbackList.size() == 0) { endRecordingCallbackHandling(); } return; } } } else { throw new IllegalArgumentException("AudioRecordingCallback was not registered"); } } } /** * Returns the current active audio recording for this audio recorder. Loading @@ -123,110 +143,31 @@ public class AudioRecordingMonitorImpl implements AudioRecordingMonitor { } } private static class AudioRecordingCallbackInfo { final AudioManager.AudioRecordingCallback mCb; final Executor mExecutor; AudioRecordingCallbackInfo(Executor e, AudioManager.AudioRecordingCallback cb) { mExecutor = e; mCb = cb; private static record AudioRecordingCallbackInfo(AudioRecordingCallback cb, Executor executor) { void dispatch(List<AudioRecordingConfiguration> configs) { executor.execute(() -> cb.onRecordingConfigChanged(configs)); } } private static final int MSG_RECORDING_CONFIG_CHANGE = 1; private final Object mRecordCallbackLock = new Object(); @GuardedBy("mRecordCallbackLock") @NonNull private LinkedList<AudioRecordingCallbackInfo> mRecordCallbackList = new LinkedList<AudioRecordingCallbackInfo>(); @GuardedBy("mRecordCallbackLock") private @Nullable HandlerThread mRecordingCallbackHandlerThread; @GuardedBy("mRecordCallbackLock") private @Nullable volatile Handler mRecordingCallbackHandler; @GuardedBy("mRecordCallbackLock") private final IRecordingConfigDispatcher mRecordingCallback = new IRecordingConfigDispatcher.Stub() { @Override public void dispatchRecordingConfigChange(List<AudioRecordingConfiguration> configs) { AudioRecordingConfiguration config = getMyConfig(configs); if (config != null) { private void handleCallback(List<AudioRecordingConfiguration> configs) { List<AudioRecordingCallbackInfo> cbInfoList; synchronized (mRecordCallbackLock) { if (mRecordingCallbackHandler != null) { final Message m = mRecordingCallbackHandler.obtainMessage( MSG_RECORDING_CONFIG_CHANGE/*what*/, config /*obj*/); mRecordingCallbackHandler.sendMessage(m); } } } } }; @GuardedBy("mRecordCallbackLock") private void beginRecordingCallbackHandling() { if (mRecordingCallbackHandlerThread == null) { mRecordingCallbackHandlerThread = new HandlerThread(TAG + ".RecordingCallback"); mRecordingCallbackHandlerThread.start(); final Looper looper = mRecordingCallbackHandlerThread.getLooper(); if (looper != null) { mRecordingCallbackHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_RECORDING_CONFIG_CHANGE: { if (msg.obj == null) { if (mRecordCallbackList.isEmpty()) { return; } ArrayList<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>(); configs.add((AudioRecordingConfiguration) msg.obj); final LinkedList<AudioRecordingCallbackInfo> cbInfoList; synchronized (mRecordCallbackLock) { if (mRecordCallbackList.size() == 0) { return; cbInfoList = new ArrayList<AudioRecordingCallbackInfo>(mRecordCallbackList); } cbInfoList = new LinkedList<AudioRecordingCallbackInfo>( mRecordCallbackList); Binder.withCleanCallingIdentity(() -> cbInfoList.forEach(cbi -> cbi.dispatch(configs))); } final long identity = Binder.clearCallingIdentity(); // Package private for cleanup from AudioRecord#release // AudioService tolerates double unregister calls void endRecordingCallbackHandling() { try { for (AudioRecordingCallbackInfo cbi : cbInfoList) { cbi.mExecutor.execute(() -> cbi.mCb.onRecordingConfigChanged(configs)); } } finally { Binder.restoreCallingIdentity(identity); } } break; default: Log.e(TAG, "Unknown event " + msg.what); break; } } }; final IAudioService service = getService(); try { service.registerRecordingCallback(mRecordingCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } } @GuardedBy("mRecordCallbackLock") private void endRecordingCallbackHandling() { if (mRecordingCallbackHandlerThread != null) { final IAudioService service = getService(); try { service.unregisterRecordingCallback(mRecordingCallback); getService().unregisterRecordingCallback(mRecordingCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mRecordingCallbackHandlerThread.quit(); mRecordingCallbackHandlerThread = null; } } AudioRecordingConfiguration getMyConfig(List<AudioRecordingConfiguration> configs) { Loading
media/java/android/media/MediaRecorder.java +6 −1 Original line number Diff line number Diff line Loading @@ -1975,7 +1975,12 @@ public class MediaRecorder implements AudioRouting, * may be expected when unnecessary multiple instances are used * at the same time. */ public native void release(); public void release() { native_release(); mRecordingInfoImpl.endRecordingCallbackHandling(); } private native void native_release(); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private static native final void native_init(); Loading
media/jni/android_media_MediaRecorder.cpp +3 −3 Original line number Diff line number Diff line Loading @@ -574,7 +574,7 @@ android_media_MediaRecorder_native_reset(JNIEnv *env, jobject thiz) } static void android_media_MediaRecorder_release(JNIEnv *env, jobject thiz) android_media_MediaRecorder_native_release(JNIEnv *env, jobject thiz) { ALOGV("release"); sp<MediaRecorder> mr = setMediaRecorder(env, thiz, 0); Loading Loading @@ -669,7 +669,7 @@ static void android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz) { ALOGV("finalize"); android_media_MediaRecorder_release(env, thiz); android_media_MediaRecorder_native_release(env, thiz); } void android_media_MediaRecorder_setInputSurface( Loading Loading @@ -891,7 +891,7 @@ static const JNINativeMethod gMethods[] = { {"pause", "()V", (void *)android_media_MediaRecorder_pause}, {"resume", "()V", (void *)android_media_MediaRecorder_resume}, {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, {"release", "()V", (void *)android_media_MediaRecorder_release}, {"native_release", "()V", (void *)android_media_MediaRecorder_native_release}, {"native_init", "()V", (void *)android_media_MediaRecorder_native_init}, {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V", (void *)android_media_MediaRecorder_native_setup}, Loading