initial support for music playback information (artist, album, track), fix encoding of empty strings in PebbleProtocol
This commit is contained in:
parent
ebea37fa8e
commit
20b3dffba6
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue