summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk43
-rw-r--r--AndroidManifest.xml40
-rw-r--r--jni/Android.mk49
-rw-r--r--jni/src/org_cyanogenmod_cmaudio_service_CMAudioService.cpp223
-rw-r--r--res/values/strings.xml19
-rw-r--r--src/org/cyanogenmod/cmaudio/service/CMAudioService.java135
6 files changed, 509 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright 2016, 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="1"
+ package="org.cyanogenmod.cmaudio.service">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+ <uses-permission android:name="cyanogenmod.permission.BIND_CORE_SERVICE" />
+ <uses-permission android:name="cyanogenmod.permission.MANAGE_AUDIO_SESSIONS" />
+
+ <application android:label="@string/app_name">
+
+ <service android:name="org.cyanogenmod.cmaudio.service.CMAudioService"
+ android:permission="cyanogenmod.permission.BIND_CORE_SERVICE"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="cyanogenmod.app.AudioManagerService"/>
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
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 <utils/Log.h>
+
+#include <JNIHelp.h>
+#include <jni.h>
+#include "core_jni_helpers.h"
+#include "android_media_AudioErrors.h"
+#include "android_runtime/AndroidRuntime.h"
+
+
+#include <media/AudioSystem.h>
+#include <media/AudioSession.h>
+
+#include <system/audio.h>
+#include <utils/threads.h>
+
+// ----------------------------------------------------------------------------
+
+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<AudioSessionInfo>& 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<AudioSessionInfo>> 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<AudioSessionInfo>& 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<void**>(&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,
+ "<init>", "(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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">CM Audio Service</string>
+</resources>
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<AudioSessionInfo> listAudioSessions(int streamType) throws RemoteException {
+ final ArrayList<AudioSessionInfo> sessions = new ArrayList<AudioSessionInfo>();
+
+ 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<AudioSessionInfo> sessions);
+
+}