From b3cbad8ab60ca04fd3287187c8a9ea54c9124ae2 Mon Sep 17 00:00:00 2001 From: Roman Birg Date: Wed, 6 Jul 2016 13:37:39 -0700 Subject: CMAudioService: initial brokered port from cmsdk Change-Id: Ia13e69f121b5258535dbb047c9cb8ef8ea03bc69 Signed-off-by: Roman Birg --- Android.mk | 43 ++++ AndroidManifest.xml | 40 ++++ jni/Android.mk | 49 +++++ ..._cyanogenmod_cmaudio_service_CMAudioService.cpp | 223 +++++++++++++++++++++ res/values/strings.xml | 19 ++ .../cmaudio/service/CMAudioService.java | 135 +++++++++++++ 6 files changed, 509 insertions(+) create mode 100644 Android.mk create mode 100644 AndroidManifest.xml create mode 100644 jni/Android.mk create mode 100644 jni/src/org_cyanogenmod_cmaudio_service_CMAudioService.cpp create mode 100644 res/values/strings.xml create mode 100644 src/org/cyanogenmod/cmaudio/service/CMAudioService.java diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..7c20d7a --- /dev/null +++ b/Android.mk @@ -0,0 +1,43 @@ +# Copyright (C) 2016 The CyanogenMod 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. + +LOCAL_PATH:= $(call my-dir) + +# +# CM Audio Service +# +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := CMAudioService + +LOCAL_STATIC_JAVA_LIBRARIES := org.cyanogenmod.platform.internal + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PROGUARD_ENABLED := disabled + +LOCAL_JNI_SHARED_LIBRARIES := libcmaudio_jni + +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) + +ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),) + +# make jni +include $(call all-makefiles-under, $(LOCAL_PATH)) + +endif diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..b745d88 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100644 index 0000000..3179953 --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,49 @@ +# Copyright (C) 2016 The CyanogenMod 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + src/org_cyanogenmod_cmaudio_service_CMAudioService.cpp + +LOCAL_C_INCLUDES := \ + $(JNI_H_INCLUDE) \ + $(TOP)/frameworks/base/core/jni \ + $(TOP)/frameworks/av/include \ + $(TOP)/hardware/libhardware/include + +LOCAL_SHARED_LIBRARIES := \ + liblog \ + libandroid_runtime \ + libnativehelper \ + libcutils \ + libhardware \ + libmedia \ + libutils + +LOCAL_MODULE := libcmaudio_jni + +LOCAL_CPPFLAGS += $(JNI_CFLAGS) +LOCAL_LDLIBS := -llog +LOCAL_NDK_STL_VARIANT := stlport_shared +LOCAL_ARM_MODE := arm + +LOCAL_MODULE_TAGS := optional + +LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter + +include $(BUILD_SHARED_LIBRARY) + diff --git a/jni/src/org_cyanogenmod_cmaudio_service_CMAudioService.cpp b/jni/src/org_cyanogenmod_cmaudio_service_CMAudioService.cpp new file mode 100644 index 0000000..b377e1e --- /dev/null +++ b/jni/src/org_cyanogenmod_cmaudio_service_CMAudioService.cpp @@ -0,0 +1,223 @@ +/* +** +** Copyright 2016, The CyanogenMod 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. +*/ + +//#define LOG_NDEBUG 0 + +#define LOG_TAG "CMAudioService-JNI" + +#include + +#include +#include +#include "core_jni_helpers.h" +#include "android_media_AudioErrors.h" +#include "android_runtime/AndroidRuntime.h" + + +#include +#include + +#include +#include + +// ---------------------------------------------------------------------------- + +namespace android { + +static jclass gArrayListClass; +static struct { + jmethodID add; + jmethodID toArray; +} gArrayListMethods; + +static struct { + jmethodID postAudioSessionEventFromNative; +} gAudioSessionEventHandlerMethods; + +static jclass gAudioSessionInfoClass; +static jmethodID gAudioSessionInfoCstor; + +static jobject gThiz; + +static Mutex gCallbackLock; + +// ---------------------------------------------------------------------------- + +static void +org_cyanogenmod_cmaudio_service_CMAudioService_session_info_callback(int event, + sp& info, bool added) +{ + AutoMutex _l(gCallbackLock); + + if (gThiz == NULL) { + ALOGE("gThiz iz null"); + return; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + jobject jSession = env->NewObject(gAudioSessionInfoClass, gAudioSessionInfoCstor, + info->mSessionId, info->mStream, info->mFlags, info->mChannelMask, info->mUid); + + env->CallVoidMethod(gThiz, + gAudioSessionEventHandlerMethods.postAudioSessionEventFromNative, + event, jSession, added); + + env->DeleteLocalRef(jSession); +} + +JNIEXPORT void JNICALL +org_cyanogenmod_cmaudio_service_registerAudioSessionCallback(JNIEnv *env, jobject thiz, + jboolean enabled) +{ + ALOGV("registerAudioSessionCallback %d", enabled); + + if (enabled) { + if (gThiz == NULL) { + gThiz = env->NewGlobalRef(thiz); + } + + } else { + if (gThiz != NULL) { + env->DeleteGlobalRef(gThiz); + gThiz = NULL; + } + } + + AudioSystem::setAudioSessionCallback(enabled ? + org_cyanogenmod_cmaudio_service_CMAudioService_session_info_callback : NULL); +} + +JNIEXPORT jint JNICALL org_cyanogenmod_cmaudio_service_CMAudioService_listAudioSessions(JNIEnv *env, + jobject thiz, jint streams, jobject jSessions) +{ + ALOGV("listAudioSessions"); + + if (jSessions == NULL) { + ALOGE("listAudioSessions NULL arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jSessions, gArrayListClass)) { + ALOGE("listAudioSessions not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + status_t status; + Vector< sp> sessions; + + status = AudioSystem::listAudioSessions((audio_stream_type_t)streams, sessions); + if (status != NO_ERROR) { + ALOGE("AudioSystem::listAudioSessions error %d", status); + } else { + ALOGV("AudioSystem::listAudioSessions count=%zu", sessions.size()); + } + + jint jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + for (size_t i = 0; i < sessions.size(); i++) { + const sp& s = sessions.itemAt(i); + + jobject jSession = env->NewObject(gAudioSessionInfoClass, gAudioSessionInfoCstor, + s->mSessionId, s->mStream, s->mFlags, s->mChannelMask, s->mUid); + + if (jSession == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + + env->CallBooleanMethod(jSessions, gArrayListMethods.add, jSession); + env->DeleteLocalRef(jSession); + } + +exit: + return jStatus; +} + +} /* namespace android */ + +// ---------------------------------------------------------------------------- + +static const char* const kClassPathName = "org/cyanogenmod/cmaudio/service/CMAudioService"; + +static JNINativeMethod gMethods[] = { + {"native_listAudioSessions", "(ILjava/util/ArrayList;)I", + (void *)android::org_cyanogenmod_cmaudio_service_CMAudioService_listAudioSessions}, + {"native_registerAudioSessionCallback", "(Z)V", + (void *)android::org_cyanogenmod_cmaudio_service_registerAudioSessionCallback}, +}; + +static int registerNativeMethods(JNIEnv* env, const char* className, + JNINativeMethod* gMethods, int numMethods) { + jclass clazz; + clazz = env->FindClass(className); + if (clazz == NULL) { + ALOGE("Native registration unable to find class '%s'", className); + return JNI_FALSE; + } + if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { + ALOGE("RegisterNatives failed for '%s'", className); + return JNI_FALSE; + } + return JNI_TRUE; +} + + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + ALOGV("OnLoad"); + JNIEnv* env = 0; + + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + ALOGE("Error: GetEnv failed in JNI_OnLoad"); + return -1; + } + jclass serviceClass = env->FindClass(kClassPathName); + if (!serviceClass) { + ALOGE("Failed to get class reference"); + return -1; + } + + if (!registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods))) { + ALOGE("Error: could not register native methods cmaudio service"); + return -1; + } + + jclass arrayListClass = android::FindClassOrDie(env, "java/util/ArrayList"); + android::gArrayListClass = android::MakeGlobalRefOrDie(env, arrayListClass); + android::gArrayListMethods.add = android::GetMethodIDOrDie(env, arrayListClass, "add", + "(Ljava/lang/Object;)Z"); + android::gArrayListMethods.toArray = android::GetMethodIDOrDie(env, arrayListClass, + "toArray", "()[Ljava/lang/Object;"); + + jclass audioSessionInfoClass = android::FindClassOrDie(env, + "cyanogenmod/media/AudioSessionInfo"); + android::gAudioSessionInfoClass = android::MakeGlobalRefOrDie(env, audioSessionInfoClass); + android::gAudioSessionInfoCstor = android::GetMethodIDOrDie(env, audioSessionInfoClass, + "", "(IIIII)V"); + + android::gAudioSessionEventHandlerMethods.postAudioSessionEventFromNative = + android::GetMethodIDOrDie(env, env->FindClass(kClassPathName), + "audioSessionCallbackFromNative", "(ILcyanogenmod/media/AudioSessionInfo;Z)V"); + + ALOGV("loaded successfully!!!"); + + return JNI_VERSION_1_6; +} + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..7044182 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,19 @@ + + + + CM Audio Service + diff --git a/src/org/cyanogenmod/cmaudio/service/CMAudioService.java b/src/org/cyanogenmod/cmaudio/service/CMAudioService.java new file mode 100644 index 0000000..c7b584f --- /dev/null +++ b/src/org/cyanogenmod/cmaudio/service/CMAudioService.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.cmaudio.service; + + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import cyanogenmod.media.AudioSessionInfo; +import cyanogenmod.media.CMAudioManager; +import cyanogenmod.media.ICMAudioService; +import cyanogenmod.platform.Manifest; + +public class CMAudioService extends Service { + + private static final String TAG = "CMAudioService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + static { + System.loadLibrary("cmaudio_jni"); + if (DEBUG) Log.d(TAG, "loaded jni lib"); + } + + public static final int MSG_BROADCAST_SESSION = 1; + + private static final int AUDIO_STATUS_OK = 0; + + //keep in sync with include/media/AudioPolicy.h + private final static int AUDIO_OUTPUT_SESSION_EFFECTS_UPDATE = 10; + + private final Handler mHandler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + if (DEBUG) Log.d(TAG, "handleMessage() called with: " + "msg = [" + msg + "]"); + switch (msg.what) { + case MSG_BROADCAST_SESSION: + broadcastSessionChanged(msg.arg1 == 1, (AudioSessionInfo) msg.obj); + break; + } + return true; + } + }); + + @Override + public void onCreate() { + super.onCreate(); + native_registerAudioSessionCallback(true); + } + + @Override + public void onDestroy() { + native_registerAudioSessionCallback(false); + super.onDestroy(); + } + + private final IBinder mBinder = new ICMAudioService.Stub() { + + @Override + public List listAudioSessions(int streamType) throws RemoteException { + final ArrayList sessions = new ArrayList(); + + int status = native_listAudioSessions(streamType, sessions); + if (status != AUDIO_STATUS_OK) { + Log.e(TAG, "Error retrieving audio sessions! status=" + status); + } + + return sessions; + } + + }; + + private void broadcastSessionChanged(boolean added, AudioSessionInfo sessionInfo) { + Intent i = new Intent(CMAudioManager.ACTION_AUDIO_SESSIONS_CHANGED); + i.putExtra(CMAudioManager.EXTRA_SESSION_INFO, sessionInfo); + i.putExtra(CMAudioManager.EXTRA_SESSION_ADDED, added); + + sendBroadcastToAll(i, Manifest.permission.OBSERVE_AUDIO_SESSIONS); + } + + private void sendBroadcastToAll(Intent intent, String receiverPermission) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + + if (DEBUG) Log.d(TAG, "Sending broadcast: " + intent); + + sendBroadcastAsUser(intent, UserHandle.ALL, receiverPermission); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + /* + * Handles events from JNI + */ + private void audioSessionCallbackFromNative(int event, AudioSessionInfo sessionInfo, + boolean added) { + + switch (event) { + case AUDIO_OUTPUT_SESSION_EFFECTS_UPDATE: + mHandler.obtainMessage(MSG_BROADCAST_SESSION, added ? 1 : 0, 0, sessionInfo) + .sendToTarget(); + break; + default: + Log.e(TAG, "Unknown event " + event); + } + } + + private native void native_registerAudioSessionCallback(boolean enabled); + + private native int native_listAudioSessions(int stream, ArrayList sessions); + +} -- cgit v1.2.3