diff options
author | Arun Raghavan <arun.raghavan@collabora.co.uk> | 2012-04-13 16:58:45 +0530 |
---|---|---|
committer | Arun Raghavan <arun.raghavan@collabora.co.uk> | 2012-04-13 16:58:45 +0530 |
commit | c2b7425612748e957ed3a595552d41770c60315f (patch) | |
tree | 065b012a6911f0e11df20a3929ba4ca176dc14a0 | |
parent | fb686c631d8fa38c31c1724e5b9d5742ec03d922 (diff) |
Implement actual switching
Still some kinks to work out, but broadly works
-rw-r--r-- | AndroidManifest.xml | 1 | ||||
-rw-r--r-- | jni/Android.mk | 4 | ||||
-rw-r--r-- | jni/output-switcher.c | 236 | ||||
-rw-r--r-- | src/org/pulseaudio/outputswitcher/OutputSwitcher.java | 46 |
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"); |