summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArun Raghavan <arun.raghavan@collabora.co.uk>2012-04-13 16:58:45 +0530
committerArun Raghavan <arun.raghavan@collabora.co.uk>2012-04-13 16:58:45 +0530
commitc2b7425612748e957ed3a595552d41770c60315f (patch)
tree065b012a6911f0e11df20a3929ba4ca176dc14a0
parentfb686c631d8fa38c31c1724e5b9d5742ec03d922 (diff)
Implement actual switching
Still some kinks to work out, but broadly works
-rw-r--r--AndroidManifest.xml1
-rw-r--r--jni/Android.mk4
-rw-r--r--jni/output-switcher.c236
-rw-r--r--src/org/pulseaudio/outputswitcher/OutputSwitcher.java46
4 files changed, 282 insertions, 5 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0b28e0f..4401681 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="org.pulseaudio.outputswitcher"
android:versionCode="1"
android:versionName="1.0">
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:theme="@android:style/Theme.Holo">
<activity android:name="OutputSwitcher"
android:label="@string/app_name">
diff --git a/jni/Android.mk b/jni/Android.mk
index 468e263..476cfc5 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -16,7 +16,11 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
+RELATIVE_AOSP_PATH := ../../../source/out/target/product/maguro/obj
+
LOCAL_MODULE := output-switcher
LOCAL_SRC_FILES := output-switcher.c
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(RELATIVE_AOSP_PATH)/include
+LOCAL_LDFLAGS := -L$(LOCAL_PATH)/$(RELATIVE_AOSP_PATH)/lib -lpulse
include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/output-switcher.c b/jni/output-switcher.c
index a74be9b..4e5f1ac 100644
--- a/jni/output-switcher.c
+++ b/jni/output-switcher.c
@@ -15,14 +15,246 @@
*
*/
+#include <stdio.h>
+#include <string.h>
#include <jni.h>
+#include <pulse/pulseaudio.h>
-jboolean Java_org_pulseaudio_outputswitcher_OutputSwitcher_switchToNetwork(JNIEnv* env, jobject thiz)
+struct userdata {
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+
+ char *server_name;
+ uint32_t module_idx;
+ uint32_t local_sink_idx;
+ uint32_t remote_sink_idx;
+};
+
+/* This should likely be passed around in Java land as a long. */
+static struct userdata *userdata;
+
+static void context_si_info_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *user)
{
- return JNI_TRUE;
+ if (eol)
+ return;
+
+ /* XXX: Error handling */
+ pa_operation_unref(pa_context_move_sink_input_by_index(c, i->index, (uint32_t) user, NULL, NULL));
+}
+
+static void move_all_sink_inputs(struct userdata *u, uint32_t sink_idx)
+{
+ char buf[256];
+
+ /* XXX: Error handling */
+ pa_operation_unref(pa_context_get_sink_input_info_list(u->context, context_si_info_cb, (void *) sink_idx));
+
+ snprintf(buf, sizeof(buf), "%d", userdata->remote_sink_idx);
+ pa_operation_unref(pa_context_set_default_sink(userdata->context, buf, NULL, NULL));
+}
+
+static void context_state_cb(pa_context *c, void *user)
+{
+ struct userdata *u = (struct userdata *) user;
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_signal(u->mainloop, 0);
+ break;
+
+ default:
+ /* Not reached */
+ break;
+ }
+}
+
+static void context_subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *user)
+{
+ struct userdata *u = (struct userdata *) user;
+
+ if (((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) &&
+ ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)) {
+ /* New sink */
+ /* XXX: This assumes that the only new sinks will be remote, and that
+ * there will be only one remote sink */
+ u->remote_sink_idx = idx;
+
+ pa_threaded_mainloop_signal(u->mainloop, 0);
+ } else if (((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) &&
+ ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)) {
+ if (u->remote_sink_idx == idx) {
+ /* Module went away */
+ u->remote_sink_idx = 0;
+ u->module_idx = 0;
+ pa_xfree(u->server_name);
+ u->server_name = NULL;
+ }
+ }
+}
+
+static void context_index_cb(pa_context *c, uint32_t idx, void *user)
+{
+ struct userdata *u = (struct userdata *) user;
+
+ userdata->module_idx = idx;
+ pa_threaded_mainloop_signal(u->mainloop, 0);
+}
+
+static void init()
+{
+ int ret;
+ pa_context_state_t state;
+
+ if (userdata)
+ return;
+
+ userdata = pa_xnew0(struct userdata, 1);
+
+ userdata->mainloop = pa_threaded_mainloop_new();
+
+ userdata->context = pa_context_new(pa_threaded_mainloop_get_api(userdata->mainloop), "PA Output Switcher");
+
+ if ((ret = pa_context_connect(userdata->context, NULL, PA_CONTEXT_NOFLAGS, NULL)) < 0) {
+ /* XXX: How do we meaningfully assert here? */
+ return;
+ }
+
+ pa_context_set_state_callback(userdata->context, context_state_cb, userdata);
+ pa_context_set_subscribe_callback(userdata->context, context_subscribe_cb, userdata);
+
+ pa_threaded_mainloop_lock(userdata->mainloop);
+
+ pa_threaded_mainloop_start(userdata->mainloop);
+
+ state = pa_context_get_state(userdata->context);
+ while (state != PA_CONTEXT_READY && state != PA_CONTEXT_FAILED && state != PA_CONTEXT_TERMINATED) {
+ pa_threaded_mainloop_wait(userdata->mainloop);
+ state = pa_context_get_state(userdata->context);
+ }
+
+ if (state != PA_CONTEXT_READY) {
+ /* XXX: How do we meaningfully assert here? */
+ return;
+ }
+
+ /* XXX: Error handling */
+ pa_operation_unref(pa_context_subscribe(userdata->context, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL));
+
+ pa_threaded_mainloop_unlock(userdata->mainloop);
+
+ /* XXX: we never set local_sink_idx to the actual value, just assume it's 0 */
+}
+
+/* XXX: Where do we call this */
+void deinit()
+{
+ pa_threaded_mainloop_lock(userdata->mainloop);
+
+ pa_context_disconnect(userdata->context);
+ pa_context_unref(userdata->context);
+
+ pa_threaded_mainloop_stop(userdata->mainloop);
+ pa_threaded_mainloop_unlock(userdata->mainloop);
+ pa_threaded_mainloop_free(userdata->mainloop);
+
+ pa_xfree(userdata);
+}
+
+jboolean Java_org_pulseaudio_outputswitcher_OutputSwitcher_switchToNetwork(JNIEnv* env, jobject thiz, jstring jServerName)
+{
+ char buf[256];
+ const char *server_name = NULL;
+ jboolean ret = JNI_FALSE;
+
+ fprintf(stderr, "Something!\n");
+
+ if (jServerName)
+ server_name = (*env)->GetStringUTFChars(env, jServerName, NULL);
+
+ if (!server_name)
+ return JNI_FALSE;
+
+ if (!userdata)
+ init();
+
+ fprintf(stderr, "Inited. Server is %s\n", server_name);
+
+ pa_threaded_mainloop_lock(userdata->mainloop);
+
+ if (!userdata->server_name || strcmp(server_name, userdata->server_name) != 0) {
+ pa_operation *o;
+
+ fprintf(stderr, "New server: %s\n", server_name);
+
+ if (userdata->module_idx) {
+ fprintf(stderr, "Unloading old module\n");
+ pa_context_unload_module(userdata->context, userdata->module_idx, NULL, NULL);
+ }
+
+ snprintf(buf, sizeof(buf), "server=%s", server_name);
+
+ userdata->remote_sink_idx = 0;
+
+ fprintf(stderr, "Loading new module\n");
+
+ if (!(o = pa_context_load_module(userdata->context, "module-tunnel-sink", buf, context_index_cb, userdata))) {
+ /* XXX: Error handling */
+ goto out;
+ }
+
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(userdata->mainloop);
+ }
+
+ if (userdata->module_idx == -1) {
+ /* XXX: Error handling */
+ goto out;
+ }
+
+
+ fprintf(stderr, "Waiting for new sink (%d)\n", userdata->module_idx);
+
+ /* XXX: where are the locks, douchebag? */
+ while (!userdata->remote_sink_idx) {
+ pa_threaded_mainloop_wait(userdata->mainloop);
+ }
+
+ if (userdata->server_name)
+ pa_xfree(userdata->server_name);
+ userdata->server_name = pa_xstrdup(server_name);
+ }
+
+ fprintf(stderr, "Setting default sink to %d\n", userdata->remote_sink_idx);
+ move_all_sink_inputs(userdata, userdata->remote_sink_idx);
+
+ ret = JNI_TRUE;
+
+out:
+ pa_threaded_mainloop_unlock(userdata->mainloop);
+
+ (*env)->ReleaseStringUTFChars(env, jServerName, server_name);
+
+ return ret;
}
jboolean Java_org_pulseaudio_outputswitcher_OutputSwitcher_switchToLocal(JNIEnv* env, jobject thiz)
{
+ if (!userdata)
+ init();
+
+ pa_threaded_mainloop_lock(userdata->mainloop);
+
+ move_all_sink_inputs(userdata, userdata->local_sink_idx);
+
+ pa_threaded_mainloop_unlock(userdata->mainloop);
+
return JNI_TRUE;
}
diff --git a/src/org/pulseaudio/outputswitcher/OutputSwitcher.java b/src/org/pulseaudio/outputswitcher/OutputSwitcher.java
index 0900def..472a7d3 100644
--- a/src/org/pulseaudio/outputswitcher/OutputSwitcher.java
+++ b/src/org/pulseaudio/outputswitcher/OutputSwitcher.java
@@ -17,22 +17,62 @@ package org.pulseaudio.outputswitcher;
import android.app.Activity;
import android.os.Bundle;
+import android.widget.*;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
-public class OutputSwitcher extends Activity
+public class OutputSwitcher extends Activity implements RadioGroup.OnCheckedChangeListener
{
+ private WifiLock wifiLock;
+
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId)
+ {
+ if (checkedId == R.id.radio_local) {
+ if (switchToLocal())
+ updateStatus("Switched to local playback");
+ else
+ updateStatus("Failed to switch to local playback");
+
+ wifiLock.release();
+ } else {
+ EditText text_server = (EditText) findViewById(R.id.text_server);
+
+ if (switchToNetwork(text_server.getText().toString()))
+ updateStatus("Switched to remote playback on '" + text_server.getText().toString() + "'");
+ else
+ updateStatus("Failed to switch to remote playback on '" + text_server.getText().toString() + "'");
+
+ wifiLock.acquire();
+ }
+ }
+
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
+
+ RadioGroup group = (RadioGroup) findViewById(R.id.rgroup_output);
+ group.setOnCheckedChangeListener(this);
+
+ WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
+ wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "PA_WifiLock");
+ wifiLock.setReferenceCounted(false);
}
/* A native method that is implemented by the
* 'hello-jni' native library, which is packaged
* with this application.
*/
- public native int switchToNetwork();
- public native int switchToLocal();
+ public native boolean switchToNetwork(String serverName);
+ public native boolean switchToLocal();
+
+ private void updateStatus(String status)
+ {
+ TextView text_status = (TextView) findViewById(R.id.text_status);
+ text_status.setText(status);
+ }
static {
System.loadLibrary("output-switcher");