Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit de41d3cd authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by Android (Google) Code Review
Browse files

Merge changes from topic 'recordingcallback'

* changes:
  Audio recording notification API
  AudioManager event dispatcher: make more generic
parents b05fd886 d3c71f07
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 \
+29 −3
Original line number Diff line number Diff line
@@ -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);
+183 −17
Original line number Diff line number Diff line
@@ -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,28 +2159,37 @@ 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) {
                        switch (msg.what) {
                            case MSSG_FOCUS_CHANGE:
                                OnAudioFocusChangeListener listener = null;
                                synchronized(mFocusListenerLock) {
                                    listener = findFocusListener((String)msg.obj);
@@ -2187,7 +2197,35 @@ public class AudioManager {
                                if (listener != null) {
                                    Log.d(TAG, "AudioManager dispatching onAudioFocusChange("
                                            + msg.what + ") for " + msg.obj);
                            listener.onAudioFocusChange(msg.what);
                                    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
+18 −0
Original line number Diff line number Diff line
/* 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 −0
Original line number Diff line number Diff line
/*
 * 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
Loading