diff options
author | Steve Kondik <steve@cyngn.com> | 2016-04-23 00:57:07 -0700 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2016-04-27 22:13:36 -0700 |
commit | bf3c0cf948d3fc0134d44ba5d8986e070d3d6439 (patch) | |
tree | 86d3e2b60a04f208b85a5d59cb66c19f8a1ffa26 /cm | |
parent | 153bd2287cf5658d8eba9e9179404c223fbb9d51 (diff) | |
download | vendor_cmsdk-bf3c0cf948d3fc0134d44ba5d8986e070d3d6439.zip vendor_cmsdk-bf3c0cf948d3fc0134d44ba5d8986e070d3d6439.tar.gz vendor_cmsdk-bf3c0cf948d3fc0134d44ba5d8986e070d3d6439.tar.bz2 |
cmsdk: Add CMAudioManager
* This is a rework of the session callback API which previously
lived in the framework due to JNI usage. This has been split out
and cleaned up for CMSDK.
* The JNI library lives on the server side, and the app-level
callback has been changed to a protected broadcast. This allows
us to wake up registered services when these events occur.
* Additionally, we support listing all active audio sessions.
* Also brings some JNI love/hate over to CMSDK.
Change-Id: I31c293943474419e3db088bb7ffab75f7440ac0f
Diffstat (limited to 'cm')
-rw-r--r-- | cm/jni/Android.mk | 40 | ||||
-rw-r--r-- | cm/jni/src/onload.cpp | 45 | ||||
-rw-r--r-- | cm/jni/src/org_cyanogenmod_platform_internal_CMAudioService.cpp | 170 | ||||
-rw-r--r-- | cm/lib/main/java/org/cyanogenmod/platform/internal/CMAudioService.java | 174 | ||||
-rw-r--r-- | cm/res/AndroidManifest.xml | 9 | ||||
-rw-r--r-- | cm/res/res/values/config.xml | 1 | ||||
-rw-r--r-- | cm/res/res/values/strings.xml | 4 |
7 files changed, 443 insertions, 0 deletions
diff --git a/cm/jni/Android.mk b/cm/jni/Android.mk new file mode 100644 index 0000000..5e1da9f --- /dev/null +++ b/cm/jni/Android.mk @@ -0,0 +1,40 @@ +# 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_platform_internal_CMAudioService.cpp \ + src/onload.cpp + +LOCAL_C_INCLUDES := \ + $(JNI_H_INCLUDE) \ + $(TOP)/frameworks/base/core/jni \ + $(TOP)/frameworks/av/include + +LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ + libmedia \ + liblog \ + libcutils \ + libutils \ + +LOCAL_MODULE := libcmsdk_platform_jni +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter + +include $(BUILD_SHARED_LIBRARY) + diff --git a/cm/jni/src/onload.cpp b/cm/jni/src/onload.cpp new file mode 100644 index 0000000..d9892ba --- /dev/null +++ b/cm/jni/src/onload.cpp @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#include "JNIHelp.h" +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" + +namespace android { + +int register_org_cyanogenmod_platform_internal_CMAudioService(JNIEnv* env); + +}; + +using namespace android; + +extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + ALOGE("GetEnv failed!"); + return result; + } + ALOG_ASSERT(env, "Could not retrieve the env!"); + + register_org_cyanogenmod_platform_internal_CMAudioService(env); + + return JNI_VERSION_1_4; +} + diff --git a/cm/jni/src/org_cyanogenmod_platform_internal_CMAudioService.cpp b/cm/jni/src/org_cyanogenmod_platform_internal_CMAudioService.cpp new file mode 100644 index 0000000..3d717cf --- /dev/null +++ b/cm/jni/src/org_cyanogenmod_platform_internal_CMAudioService.cpp @@ -0,0 +1,170 @@ +/* +** +** 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 <media/AudioSystem.h> +#include <media/AudioSession.h> + +#include <system/audio.h> +#include <utils/threads.h> + +// ---------------------------------------------------------------------------- + +namespace android { + +static const char* const kClassPathName = "org/cyanogenmod/platform/internal/CMAudioService"; + +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_platform_internal_CMAudioService_session_info_callback(int event, + sp<AudioSessionInfo>& info, bool added) +{ + AutoMutex _l(gCallbackLock); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + return; + } + + 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); +} + +static void +org_cyanogenmod_platform_internal_CMAudioService_registerAudioSessionCallback( + JNIEnv *env, jobject thiz, jboolean enabled) +{ + if (gThiz == NULL) { + gThiz = env->NewGlobalRef(thiz); + } + + AudioSystem::setAudioSessionCallback( enabled ? + org_cyanogenmod_platform_internal_CMAudioService_session_info_callback : NULL); +} + +static jint +org_cyanogenmod_platform_internal_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=%d", 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; +} + + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"native_listAudioSessions", "(ILjava/util/ArrayList;)I", + (void *)org_cyanogenmod_platform_internal_CMAudioService_listAudioSessions}, + {"native_registerAudioSessionCallback", "(Z)V", + (void *)org_cyanogenmod_platform_internal_CMAudioService_registerAudioSessionCallback}, +}; + +int register_org_cyanogenmod_platform_internal_CMAudioService(JNIEnv *env) +{ + jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList"); + gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass); + gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z"); + gArrayListMethods.toArray = GetMethodIDOrDie(env, arrayListClass, "toArray", "()[Ljava/lang/Object;"); + + jclass audioSessionInfoClass = FindClassOrDie(env, "cyanogenmod/media/AudioSessionInfo"); + gAudioSessionInfoClass = MakeGlobalRefOrDie(env, audioSessionInfoClass); + gAudioSessionInfoCstor = GetMethodIDOrDie(env, audioSessionInfoClass, "<init>", "(IIIII)V"); + + gAudioSessionEventHandlerMethods.postAudioSessionEventFromNative = + GetMethodIDOrDie(env, env->FindClass(kClassPathName), + "audioSessionCallbackFromNative", "(ILcyanogenmod/media/AudioSessionInfo;Z)V"); + + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +} /* namespace android */ diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/CMAudioService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/CMAudioService.java new file mode 100644 index 0000000..99f3f6f --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/CMAudioService.java @@ -0,0 +1,174 @@ +/* + * 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.platform.internal; + +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import cyanogenmod.app.CMContextConstants; +import cyanogenmod.media.AudioSessionInfo; +import cyanogenmod.media.CMAudioManager; +import cyanogenmod.media.ICMAudioService; +import cyanogenmod.platform.Manifest; + +public class CMAudioService extends SystemService { + + private static final String TAG = "CMAudioService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + + 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 static boolean sNativeLibraryLoaded; + + static { + try { + System.loadLibrary("cmsdk_platform_jni"); + sNativeLibraryLoaded = true; + + } catch (Throwable t) { + sNativeLibraryLoaded = false; + Log.w(TAG, "CMSDK native platform unavailable"); + } + } + + public CMAudioService(Context context) { + super(context); + + mContext = context; + } + + @Override + public void onStart() { + if (!mContext.getPackageManager().hasSystemFeature( + CMContextConstants.Features.AUDIO)) { + Log.wtf(TAG, "CM Audio service started by system server but feature xml not" + + " declared. Not publishing binder service!"); + return; + } + + if (!sNativeLibraryLoaded) { + Log.wtf(TAG, "CM Audio service started by system server by native library is" + + "unavailable. Service will be unavailable."); + return; + } + publishBinderService(CMContextConstants.CM_AUDIO_SERVICE, mBinder); + } + + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_BOOT_COMPLETED) { + if (sNativeLibraryLoaded) { + native_registerAudioSessionCallback(true); + } + } + } + + private final IBinder mBinder = new ICMAudioService.Stub() { + + @Override + public List<AudioSessionInfo> listAudioSessions(int streamType) throws RemoteException { + final ArrayList<AudioSessionInfo> sessions = new ArrayList<AudioSessionInfo>(); + if (!sNativeLibraryLoaded) { + // no sessions for u + return sessions; + } + + int status = native_listAudioSessions(streamType, sessions); + if (status != AUDIO_STATUS_OK) { + Log.e(TAG, "Error retrieving audio sessions! status=" + status); + } + + return sessions; + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(); + pw.println("CMAudio Service State:"); + try { + List<AudioSessionInfo> sessions = listAudioSessions(-1); + if (sessions.size() > 0) { + pw.println(" Audio sessions:"); + for (AudioSessionInfo info : sessions) { + pw.println(" " + info.toString()); + } + } else { + pw.println(" No active audio sessions"); + } + } catch (RemoteException e) { + // nothing + } + } + }; + + 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); + + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Log.d(TAG, "Sending broadcast: " + intent.toString()); + + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, receiverPermission); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /* + * Handles events from JNI + */ + private synchronized void audioSessionCallbackFromNative(int event, + AudioSessionInfo sessionInfo, boolean added) { + + switch (event) { + case AUDIO_OUTPUT_SESSION_EFFECTS_UPDATE: + broadcastSessionChanged(added, sessionInfo); + break; + default: + Log.e(TAG, "Unknown event " + event); + } + } + + private native final void native_registerAudioSessionCallback(boolean enabled); + + private native final int native_listAudioSessions( + int stream, ArrayList<AudioSessionInfo> sessions); +} diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml index c2be8dc..4c2d228 100644 --- a/cm/res/AndroidManifest.xml +++ b/cm/res/AndroidManifest.xml @@ -24,6 +24,7 @@ <protected-broadcast android:name="cyanogenmod.intent.action.SCREEN_CAMERA_GESTURE" /> <protected-broadcast android:name="cyanogenmod.intent.action.INITIALIZE_CM_HARDWARE" /> + <protected-broadcast android:name="cyanogenmod.intent.action.ACTION_AUDIO_SESSIONS_CHANGED" /> <!-- Must be required by an, to ensure that only the system can bind to it. @hide --> @@ -211,6 +212,14 @@ android:description="@string/permdesc_weather_bind" android:protectionLevel="signature"/> + <!-- Allows an application to observe system-wide changes to audio sessions + @hide --> + <permission android:name="cyanogenmod.permission.OBSERVE_AUDIO_SESSIONS" + android:label="@string/permlab_observe_audio_sessions" + android:description="@string/permdesc_observe_audio_sessions" + android:protectionLevel="normal"/> + + <!-- Allows an application to access the weather service. <p>Although the protection is normal, this permission should be required ONLY by those apps meant to do something meaningful with the data provided by the service (LockClock, SysUI)--> diff --git a/cm/res/res/values/config.xml b/cm/res/res/values/config.xml index 3755e4b..db621b1 100644 --- a/cm/res/res/values/config.xml +++ b/cm/res/res/values/config.xml @@ -98,5 +98,6 @@ <item>org.cyanogenmod.platform.internal.LiveLockScreenServiceBroker</item> <item>org.cyanogenmod.platform.internal.CMWeatherManagerService</item> <item>org.cyanogenmod.platform.internal.display.LiveDisplayService</item> + <item>org.cyanogenmod.platform.internal.CMAudioService</item> </string-array> </resources> diff --git a/cm/res/res/values/strings.xml b/cm/res/res/values/strings.xml index 18bfd6a..2c57e00 100644 --- a/cm/res/res/values/strings.xml +++ b/cm/res/res/values/strings.xml @@ -214,4 +214,8 @@ <string name="permlab_manageLiveDisplay">manage LiveDisplay settings</string> <string name="permdesc_manageLiveDisplay">Allows an app to configure advanced display settings.</string> + <!-- CMAudioService - observe session changes permission --> + <string name="permlab_observe_audio_sessions">observe audio session changes</string> + <string name="permdesc_observe_audio_sessions">Allows an app to observe audio streams being created and destroyed.</string> + </resources> |