initial support for music playback information (artist, album, track), fix encoding of empty strings in PebbleProtocol

This commit is contained in:
Andreas Shimokawa 2015-02-08 23:53:40 +01:00
parent ebea37fa8e
commit 20b3dffba6
8 changed files with 123 additions and 36 deletions

View File

@ -13,10 +13,11 @@ annoy you more than it helps you ;)
Features:
* Incoming calls (caller, phone number)
* Incoming calls notification and display (caller, phone number)
* SMS notification (sender, body)
* K-9 Mail notification support (sender, subject, preview)
* Support for generic notificaions (above filtered out)
* Apollo Music Playback info (artist, album, track)
How to use (Pebble):
@ -28,7 +29,6 @@ Known Issues:
* No reconnect, if connection is lost, you have to press "connect" again
* Can't reject or hang up phone calls yet
* No outgoing call support
* Notifications are not properly queued, if two arrive at about the same time,
one of them might get lost (TODO: confirm)
* Android 4.4+ only, we can only change this by not handling generic

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="nodomain.freeyourgadget.gadgetbridge" >
package="nodomain.freeyourgadget.gadgetbridge">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
@ -13,13 +13,13 @@
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:theme="@style/AppTheme">
<activity
android:name=".SettingsActivity"
android:label="@string/app_name" />
<activity
android:name=".ControlCenter"
android:label="@string/app_name" >
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -30,17 +30,16 @@
<service
android:name=".NotificationListener"
android:label="@string/app_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<service android:name=".BluetoothCommunicationService" >
</service>
<service android:name=".BluetoothCommunicationService"></service>
<receiver
android:name=".PhoneCallReceiver"
android:enabled="false" >
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
@ -50,26 +49,33 @@
</receiver>
<receiver
android:name=".SMSReceiver"
android:enabled="false" >
android:enabled="false">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<receiver
android:name=".K9Receiver"
android:enabled="false" >
android:enabled="false">
<intent-filter>
<data android:scheme="email" />
<action android:name="com.fsck.k9.intent.action.EMAIL_RECEIVED" />
</intent-filter>
</receiver>
<receiver
android:name=".MusicPlaybackReceiver"
android:enabled="false">
<intent-filter>
<action android:name="com.andrew.apollo.metachanged" />
</intent-filter>
</receiver>
<receiver android:name=".StopServiceReceiver" />
<activity
android:name=".DebugActivity"
android:label="@string/title_activity_debug" >
</activity>
android:label="@string/title_activity_debug"></activity>
</application>
</manifest>

View File

@ -29,9 +29,6 @@ import java.nio.ByteOrder;
import java.util.Set;
public class BluetoothCommunicationService extends Service {
private static final String TAG = "BluetoothCommunicationService";
private static final int NOTIFICATION_ID = 1;
public static final String ACTION_START
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.start";
public static final String ACTION_CONNECT
@ -46,7 +43,10 @@ public class BluetoothCommunicationService extends Service {
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.callstate";
public static final String ACTION_SETTIME
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.settime";
public static final String ACTION_SETMUSICINFO
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.setmusicinfo";
private static final String TAG = "BluetoothCommunicationService";
private static final int NOTIFICATION_ID = 1;
private BluetoothAdapter mBtAdapter = null;
private BluetoothSocket mBtSocket = null;
private BtSocketIoThread mBtSocketIoThread = null;
@ -58,6 +58,7 @@ public class BluetoothCommunicationService extends Service {
PhoneCallReceiver.class,
SMSReceiver.class,
K9Receiver.class,
MusicPlaybackReceiver.class,
//NotificationListener.class, // disabling this leads to loss of permission to read notifications
};
@ -183,19 +184,22 @@ public class BluetoothCommunicationService extends Service {
mBtSocketIoThread.write(msg);
} else if (intent.getAction().equals(ACTION_CALLSTATE)) {
byte phoneState = intent.getByteExtra("call_state", (byte) 0);
String phoneNumber = null;
if (intent.hasExtra("call_phonenumber")) {
phoneNumber = intent.getStringExtra("call_phonenumber");
}
String phoneNumber = intent.getStringExtra("call_phonenumber");
String callerName = null;
if (phoneNumber != null) {
callerName = getContactDisplayNameByNumber(phoneNumber);
}
byte[] msg = PebbleProtocol.encodePhoneState(phoneNumber, callerName, phoneState);
byte[] msg = PebbleProtocol.encodeSetPhoneState(phoneNumber, callerName, phoneState);
mBtSocketIoThread.write(msg);
} else if (intent.getAction().equals(ACTION_SETTIME)) {
byte[] msg = PebbleProtocol.encodeSetTime(-1);
mBtSocketIoThread.write(msg);
} else if (intent.getAction().equals(ACTION_SETMUSICINFO)) {
String artist = intent.getStringExtra("music_artist");
String album = intent.getStringExtra("music_album");
String track = intent.getStringExtra("music_track");
byte[] msg = PebbleProtocol.encodeSetMusicInfo(artist, album, track);
mBtSocketIoThread.write(msg);
} else if (intent.getAction().equals(ACTION_START)) {
startForeground(NOTIFICATION_ID, createNotification("Gadgetbridge running"));
mStarted = true;
@ -255,9 +259,9 @@ public class BluetoothCommunicationService extends Service {
private class BtSocketIoThread extends Thread {
private final String mmBtDeviceAddress;
private InputStream mmInStream = null;
private OutputStream mmOutStream = null;
private final String mmBtDeviceAddress;
private boolean mQuit = false;
private boolean mmIsConnected = false;

View File

@ -12,6 +12,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class DebugActivity extends ActionBarActivity {
Button sendSMSButton;
Button sendEmailButton;
@ -20,6 +21,7 @@ public class DebugActivity extends ActionBarActivity {
Button startCallButton;
Button endCallButton;
Button testNotificationButton;
Button setMusicInfoButton;
Button setTimeButton;
EditText editContent;
@ -108,6 +110,19 @@ public class DebugActivity extends ActionBarActivity {
}
});
setMusicInfoButton = (Button) findViewById(R.id.setMusicInfoButton);
setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(DebugActivity.this, BluetoothCommunicationService.class);
startIntent.setAction(BluetoothCommunicationService.ACTION_SETMUSICINFO);
startIntent.putExtra("music_artist", editContent.getText().toString() + "(artist)");
startIntent.putExtra("music_album", editContent.getText().toString() + "(album)");
startIntent.putExtra("music_track", editContent.getText().toString() + "(track)");
startService(startIntent);
}
});
setTimeButton = (Button) findViewById(R.id.setTimeButton);
setTimeButton.setOnClickListener(new View.OnClickListener() {
@Override

View File

@ -0,0 +1,26 @@
package nodomain.freeyourgadget.gadgetbridge;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class MusicPlaybackReceiver extends BroadcastReceiver {
private final String TAG = this.getClass().getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String artist = intent.getStringExtra("artist");
String album = intent.getStringExtra("album");
String track = intent.getStringExtra("track");
Log.i(TAG, "Current track: " + artist + ", " + album + ", " + track);
Intent startIntent = new Intent(context, BluetoothCommunicationService.class);
startIntent.setAction(BluetoothCommunicationService.ACTION_SETMUSICINFO);
startIntent.putExtra("music_artist", artist);
startIntent.putExtra("music_album", album);
startIntent.putExtra("music_track", track);
context.startService(startIntent);
}
}

View File

@ -45,6 +45,8 @@ public class PebbleProtocol {
static final byte PHONECONTROL_START = 8;
static final byte PHONECONTROL_END = 9;
static final byte MUSICCONTROL_SETMUSICINFO = 16;
static final short LENGTH_PREFIX = 4;
static final short LENGTH_SETTIME = 9;
static final short LENGTH_PHONEVERSION = 21;
@ -52,7 +54,11 @@ public class PebbleProtocol {
static final byte TIME_GETTIME = 0;
static final byte TIME_SETTIME = 2;
static final byte PHONEVERSION_APPVERSION = 2; // increase this if pebble complains
static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains
static final byte PHONEVERSION_APPVERSION_MAJOR = 2;
static final byte PHONEVERSION_APPVERSION_MINOR = 2;
static final byte PHONEVERSION_APPVERSION_PATCH = 2;
static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = (int) 0x80000000;
@ -72,14 +78,21 @@ public class PebbleProtocol {
static final byte PHONEVERSION_REMOTE_OS_LINUX = 4;
static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5;
static byte[] encodeMessage(short endpoint, byte type, String[] parts) {
static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) {
// Calculate length first
int length = LENGTH_PREFIX + 1;
for (String s : parts) {
if (s == null) continue;
if (s == null || s.equals("")) {
length++; // encode null or empty strings as 0x00 later
continue;
}
length += (1 + s.getBytes().length);
}
if (endpoint == ENDPOINT_PHONECONTROL) {
length += 4; //for cookie;
}
// Encode Prefix
ByteBuffer buf = ByteBuffer.allocate(length);
buf.order(ByteOrder.BIG_ENDIAN);
@ -87,9 +100,16 @@ public class PebbleProtocol {
buf.putShort(endpoint);
buf.put(type);
if (endpoint == ENDPOINT_PHONECONTROL) {
buf.putInt(cookie);
}
// Encode Pascal-Style Strings
for (String s : parts) {
if (s == null) continue;
if (s == null || s.equals("")) {
//buf.put((byte)0x01);
buf.put((byte) 0x00);
continue;
}
int partlength = s.getBytes().length;
if (partlength > 255) partlength = 255;
@ -106,7 +126,7 @@ public class PebbleProtocol {
String tsstring = ts.toString(); // SIC
String[] parts = {from, body, tsstring};
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_SMS, parts);
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_SMS, 0, parts);
}
public static byte[] encodeEmail(String from, String subject, String body) {
@ -115,7 +135,7 @@ public class PebbleProtocol {
String tsstring = ts.toString(); // SIC
String[] parts = {from, body, tsstring, subject};
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, parts);
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, 0, parts);
}
public static byte[] encodeSetTime(long ts) {
@ -133,10 +153,14 @@ public class PebbleProtocol {
return buf.array();
}
public static byte[] encodePhoneState(String number, String name, byte state) {
String cookie = "000"; // That's a dirty trick to make the cookie part 4 bytes long :P
String[] parts = {cookie, number, name};
return encodeMessage(ENDPOINT_PHONECONTROL, state, parts);
public static byte[] encodeSetPhoneState(String number, String name, byte state) {
String[] parts = {number, name};
return encodeMessage(ENDPOINT_PHONECONTROL, state, 0, parts);
}
public static byte[] encodeSetMusicInfo(String artist, String album, String track) {
String[] parts = {artist, album, track};
return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts);
}
public static byte[] encodePhoneVersion(byte os) {
@ -153,7 +177,11 @@ public class PebbleProtocol {
buf.putInt(0);
}
buf.putInt(PHONEVERSION_REMOTE_CAPS_SMS | PHONEVERSION_REMOTE_CAPS_TELEPHONY | os);
buf.putInt(0x02020000); // app version code
buf.put(PHONEVERSION_APPVERSION_MAGIC);
buf.put(PHONEVERSION_APPVERSION_MAJOR);
buf.put(PHONEVERSION_APPVERSION_MINOR);
buf.put(PHONEVERSION_APPVERSION_PATCH);
return buf.array();
}

View File

@ -53,8 +53,7 @@ public class PhoneCallReceiver extends BroadcastReceiver {
if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
//pebblePhoneCommand = PebbleProtocol.PHONECONTROL_MISSEDCALL;
pebblePhoneCommand = PebbleProtocol.PHONECONTROL_END; // MISSED CALL DOES NOT WORK
}
else {
} else {
pebblePhoneCommand = PebbleProtocol.PHONECONTROL_END;
}
break;

View File

@ -91,4 +91,13 @@
android:layout_alignEnd="@+id/outgoingCallButton"
android:layout_toEndOf="@+id/incomingCallButton" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="set music info"
android:id="@+id/setMusicInfoButton"
android:layout_below="@+id/startCallButton"
android:layout_alignParentStart="true"
android:layout_alignEnd="@+id/endCallButton" />
</RelativeLayout>