2017-03-10 14:53:19 +01:00
/ * Copyright ( C ) 2015 - 2017 Andreas Shimokawa , Carsten Pfeiffer , Daniele
Gobbetti , Julien Pivotto , Kevin Richter , Steffen Liebergeld , Uwe Hermann
This file is part of Gadgetbridge .
Gadgetbridge is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Gadgetbridge is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>. */
2015-08-03 23:51:53 +02:00
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble ;
2015-01-07 14:00:18 +01:00
2015-09-13 18:37:59 +02:00
import android.util.Base64 ;
2015-05-11 23:30:38 +02:00
import android.util.Pair ;
2015-03-07 17:44:39 +01:00
2015-09-13 18:20:15 +02:00
import org.json.JSONArray ;
import org.json.JSONException ;
import org.json.JSONObject ;
2015-05-12 11:06:22 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2015-01-07 14:00:18 +01:00
import java.nio.ByteBuffer ;
import java.nio.ByteOrder ;
2015-05-11 23:30:38 +02:00
import java.util.ArrayList ;
2015-10-04 15:53:11 +02:00
import java.util.HashMap ;
2017-04-20 20:09:29 +02:00
import java.util.List ;
2015-10-04 15:53:11 +02:00
import java.util.Map ;
2015-05-15 21:34:38 +02:00
import java.util.Random ;
2015-01-07 14:00:18 +01:00
import java.util.SimpleTimeZone ;
2015-05-12 11:06:22 +02:00
import java.util.UUID ;
2015-01-07 14:00:18 +01:00
2015-06-23 11:54:33 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent ;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo ;
2015-08-16 00:32:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement ;
2015-09-17 19:21:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage ;
2015-06-23 11:54:33 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl ;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl ;
2015-08-31 22:27:25 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl ;
2015-06-24 23:55:51 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot ;
2015-06-23 11:54:33 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes ;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo ;
2017-02-19 22:59:37 +01:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging ;
2015-09-13 18:20:15 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID ;
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice ;
2015-08-09 21:42:27 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp ;
2016-02-02 17:33:24 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser ;
2016-05-16 17:30:11 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec ;
2016-04-04 20:08:34 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec ;
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec ;
2016-06-09 20:00:14 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec ;
2015-09-24 14:45:21 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType ;
2016-12-30 20:14:13 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.Weather ;
2016-12-31 15:56:05 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec ;
2015-08-18 01:26:15 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol ;
2015-03-26 12:06:26 +01:00
2015-04-01 18:34:52 +02:00
public class PebbleProtocol extends GBDeviceProtocol {
2015-03-07 17:44:39 +01:00
2015-05-12 06:28:11 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( PebbleProtocol . class ) ;
2015-03-07 17:44:39 +01:00
2017-01-01 13:55:07 +01:00
private static final short ENDPOINT_TIME = 11 ;
private static final short ENDPOINT_FIRMWAREVERSION = 16 ;
private static final short ENDPOINT_PHONEVERSION = 17 ;
private static final short ENDPOINT_SYSTEMMESSAGE = 18 ;
private static final short ENDPOINT_MUSICCONTROL = 32 ;
private static final short ENDPOINT_PHONECONTROL = 33 ;
2015-01-07 14:00:18 +01:00
static final short ENDPOINT_APPLICATIONMESSAGE = 48 ;
2017-01-01 13:55:07 +01:00
private static final short ENDPOINT_LAUNCHER = 49 ;
private static final short ENDPOINT_APPRUNSTATE = 52 ; // FW >=3.x
private static final short ENDPOINT_LOGS = 2000 ;
private static final short ENDPOINT_PING = 2001 ;
private static final short ENDPOINT_LOGDUMP = 2002 ;
private static final short ENDPOINT_RESET = 2003 ;
private static final short ENDPOINT_APP = 2004 ;
private static final short ENDPOINT_APPLOGS = 2006 ;
private static final short ENDPOINT_NOTIFICATION = 3000 ; // FW 1.x-2-x
private static final short ENDPOINT_EXTENSIBLENOTIFS = 3010 ; // FW 2.x
private static final short ENDPOINT_RESOURCE = 4000 ;
private static final short ENDPOINT_SYSREG = 5000 ;
private static final short ENDPOINT_FCTREG = 5001 ;
private static final short ENDPOINT_APPMANAGER = 6000 ;
private static final short ENDPOINT_APPFETCH = 6001 ; // FW >=3.x
private static final short ENDPOINT_DATALOG = 6778 ;
private static final short ENDPOINT_RUNKEEPER = 7000 ;
private static final short ENDPOINT_SCREENSHOT = 8000 ;
private static final short ENDPOINT_AUDIOSTREAM = 10000 ;
private static final short ENDPOINT_VOICECONTROL = 11000 ;
private static final short ENDPOINT_NOTIFICATIONACTION = 11440 ; // FW >=3.x, TODO: find a better name
private static final short ENDPOINT_APPREORDER = ( short ) 0xabcd ; // FW >=3.x
private static final short ENDPOINT_BLOBDB = ( short ) 0xb1db ; // FW >=3.x
private static final short ENDPOINT_PUTBYTES = ( short ) 0xbeef ;
private static final byte APPRUNSTATE_START = 1 ;
private static final byte APPRUNSTATE_STOP = 2 ;
private static final byte BLOBDB_INSERT = 1 ;
private static final byte BLOBDB_DELETE = 4 ;
private static final byte BLOBDB_CLEAR = 5 ;
private static final byte BLOBDB_PIN = 1 ;
private static final byte BLOBDB_APP = 2 ;
private static final byte BLOBDB_REMINDER = 3 ;
private static final byte BLOBDB_NOTIFICATION = 4 ;
private static final byte BLOBDB_WEATHER = 5 ;
private static final byte BLOBDB_CANNED_MESSAGES = 6 ;
private static final byte BLOBDB_PREFERENCES = 7 ;
private static final byte BLOBDB_APPSETTINGS = 9 ;
private static final byte BLOBDB_APPGLANCE = 11 ;
private static final byte BLOBDB_SUCCESS = 1 ;
private static final byte BLOBDB_GENERALFAILURE = 2 ;
private static final byte BLOBDB_INVALIDOPERATION = 3 ;
private static final byte BLOBDB_INVALIDDATABASEID = 4 ;
private static final byte BLOBDB_INVALIDDATA = 5 ;
private static final byte BLOBDB_KEYDOESNOTEXIST = 6 ;
private static final byte BLOBDB_DATABASEFULL = 7 ;
private static final byte BLOBDB_DATASTALE = 8 ;
private static final byte NOTIFICATION_EMAIL = 0 ;
private static final byte NOTIFICATION_SMS = 1 ;
private static final byte NOTIFICATION_TWITTER = 2 ;
private static final byte NOTIFICATION_FACEBOOK = 3 ;
private static final byte PHONECONTROL_ANSWER = 1 ;
private static final byte PHONECONTROL_HANGUP = 2 ;
private static final byte PHONECONTROL_GETSTATE = 3 ;
private static final byte PHONECONTROL_INCOMINGCALL = 4 ;
private static final byte PHONECONTROL_OUTGOINGCALL = 5 ;
private static final byte PHONECONTROL_MISSEDCALL = 6 ;
private static final byte PHONECONTROL_RING = 7 ;
private static final byte PHONECONTROL_START = 8 ;
private static final byte PHONECONTROL_END = 9 ;
private static final byte MUSICCONTROL_SETMUSICINFO = 0x10 ;
private static final byte MUSICCONTROL_SETPLAYSTATE = 0x11 ;
private static final byte MUSICCONTROL_PLAYPAUSE = 1 ;
private static final byte MUSICCONTROL_PAUSE = 2 ;
private static final byte MUSICCONTROL_PLAY = 3 ;
private static final byte MUSICCONTROL_NEXT = 4 ;
private static final byte MUSICCONTROL_PREVIOUS = 5 ;
private static final byte MUSICCONTROL_VOLUMEUP = 6 ;
private static final byte MUSICCONTROL_VOLUMEDOWN = 7 ;
private static final byte MUSICCONTROL_GETNOWPLAYING = 8 ;
private static final byte MUSICCONTROL_STATE_PAUSED = 0x00 ;
private static final byte MUSICCONTROL_STATE_PLAYING = 0x01 ;
private static final byte MUSICCONTROL_STATE_REWINDING = 0x02 ;
private static final byte MUSICCONTROL_STATE_FASTWORWARDING = 0x03 ;
private static final byte MUSICCONTROL_STATE_UNKNOWN = 0x04 ;
private static final byte NOTIFICATIONACTION_ACK = 0 ;
private static final byte NOTIFICATIONACTION_NACK = 1 ;
private static final byte NOTIFICATIONACTION_INVOKE = 0x02 ;
private static final byte NOTIFICATIONACTION_RESPONSE = 0x11 ;
private static final byte TIME_GETTIME = 0 ;
private static final byte TIME_SETTIME = 2 ;
private static final byte TIME_SETTIME_UTC = 3 ;
private static final byte FIRMWAREVERSION_GETVERSION = 0 ;
private static final byte APPMANAGER_GETAPPBANKSTATUS = 1 ;
private static final byte APPMANAGER_REMOVEAPP = 2 ;
private static final byte APPMANAGER_REFRESHAPP = 3 ;
private static final byte APPMANAGER_GETUUIDS = 5 ;
private static final int APPMANAGER_RES_SUCCESS = 1 ;
private static final byte APPLICATIONMESSAGE_PUSH = 1 ;
private static final byte APPLICATIONMESSAGE_REQUEST = 2 ;
private static final byte APPLICATIONMESSAGE_ACK = ( byte ) 0xff ;
private static final byte APPLICATIONMESSAGE_NACK = ( byte ) 0x7f ;
private static final byte DATALOG_OPENSESSION = 0x01 ;
private static final byte DATALOG_SENDDATA = 0x02 ;
private static final byte DATALOG_CLOSE = 0x03 ;
private static final byte DATALOG_TIMEOUT = 0x07 ;
private static final byte DATALOG_REPORTSESSIONS = ( byte ) 0x84 ;
private static final byte DATALOG_ACK = ( byte ) 0x85 ;
private static final byte DATALOG_NACK = ( byte ) 0x86 ;
private static final byte PING_PING = 0 ;
private static final byte PING_PONG = 1 ;
private static final byte PUTBYTES_INIT = 1 ;
private static final byte PUTBYTES_SEND = 2 ;
private static final byte PUTBYTES_COMMIT = 3 ;
private static final byte PUTBYTES_ABORT = 4 ;
private static final byte PUTBYTES_COMPLETE = 5 ;
2015-04-06 20:58:35 +02:00
2015-08-03 23:09:49 +02:00
public static final byte PUTBYTES_TYPE_FIRMWARE = 1 ;
public static final byte PUTBYTES_TYPE_RECOVERY = 2 ;
public static final byte PUTBYTES_TYPE_SYSRESOURCES = 3 ;
2015-04-07 19:33:23 +02:00
public static final byte PUTBYTES_TYPE_RESOURCES = 4 ;
2015-04-06 20:58:35 +02:00
public static final byte PUTBYTES_TYPE_BINARY = 5 ;
2015-10-06 16:56:01 +02:00
public static final byte PUTBYTES_TYPE_FILE = 6 ;
2015-04-07 19:33:23 +02:00
public static final byte PUTBYTES_TYPE_WORKER = 7 ;
2015-01-07 14:00:18 +01:00
2017-01-01 13:55:07 +01:00
private static final byte RESET_REBOOT = 0 ;
private static final byte SCREENSHOT_TAKE = 0 ;
private static final byte SYSTEMMESSAGE_NEWFIRMWAREAVAILABLE = 0 ;
private static final byte SYSTEMMESSAGE_FIRMWARESTART = 1 ;
private static final byte SYSTEMMESSAGE_FIRMWARECOMPLETE = 2 ;
private static final byte SYSTEMMESSAGE_FIRMWAREFAIL = 3 ;
private static final byte SYSTEMMESSAGE_FIRMWARE_UPTODATE = 4 ;
private static final byte SYSTEMMESSAGE_FIRMWARE_OUTOFDATE = 5 ;
private static final byte SYSTEMMESSAGE_STOPRECONNECTING = 6 ;
private static final byte SYSTEMMESSAGE_STARTRECONNECTING = 7 ;
private static final byte PHONEVERSION_REQUEST = 0 ;
private static final byte PHONEVERSION_APPVERSION_MAGIC = 2 ; // increase this if pebble complains
private static final byte PHONEVERSION_APPVERSION_MAJOR = 2 ;
private static final byte PHONEVERSION_APPVERSION_MINOR = 3 ;
private static final byte PHONEVERSION_APPVERSION_PATCH = 0 ;
private static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = 0x80000000 ;
private static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010 ;
private static final int PHONEVERSION_REMOTE_CAPS_SMS = 0x00000020 ;
private static final int PHONEVERSION_REMOTE_CAPS_GPS = 0x00000040 ;
private static final int PHONEVERSION_REMOTE_CAPS_BTLE = 0x00000080 ;
private static final int PHONEVERSION_REMOTE_CAPS_REARCAMERA = 0x00000100 ;
private static final int PHONEVERSION_REMOTE_CAPS_ACCEL = 0x00000200 ;
private static final int PHONEVERSION_REMOTE_CAPS_GYRO = 0x00000400 ;
private static final int PHONEVERSION_REMOTE_CAPS_COMPASS = 0x00000800 ;
private static final byte PHONEVERSION_REMOTE_OS_UNKNOWN = 0 ;
private static final byte PHONEVERSION_REMOTE_OS_IOS = 1 ;
private static final byte PHONEVERSION_REMOTE_OS_ANDROID = 2 ;
private static final byte PHONEVERSION_REMOTE_OS_OSX = 3 ;
private static final byte PHONEVERSION_REMOTE_OS_LINUX = 4 ;
private static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5 ;
2017-02-19 22:59:37 +01:00
static final byte TYPE_BYTEARRAY = 0 ;
2017-01-01 13:55:07 +01:00
private static final byte TYPE_CSTRING = 1 ;
2017-02-19 22:59:37 +01:00
static final byte TYPE_UINT = 2 ;
static final byte TYPE_INT = 3 ;
2017-01-01 13:55:07 +01:00
private final short LENGTH_PREFIX = 4 ;
private static final byte LENGTH_UUID = 16 ;
private static final long GB_UUID_MASK = 0x4767744272646700L ;
2016-05-24 13:11:57 +02:00
2016-06-15 22:53:05 +02:00
// base is -8
2015-09-23 23:19:38 +02:00
private static final String [ ] hwRevisions = {
// Emulator
2016-07-04 22:09:56 +02:00
"silk_bb2" , "robert_bb" , "silk_bb" ,
2016-06-15 22:53:05 +02:00
"spalding_bb2" , "snowy_bb2" , "snowy_bb" ,
"bb2" , "bb" ,
2015-09-23 23:19:38 +02:00
"unknown" ,
2016-07-04 22:09:56 +02:00
// Pebble Classic Series
2015-09-23 23:19:38 +02:00
"ev1" , "ev2" , "ev2_3" , "ev2_4" , "v1_5" , "v2_0" ,
2016-07-04 22:09:56 +02:00
// Pebble Time Series
2016-06-15 22:53:05 +02:00
"snowy_evt2" , "snowy_dvt" , "spalding_dvt" , "snowy_s3" , "spalding" ,
2016-07-04 22:09:56 +02:00
// Pebble 2 Series
"silk_evt" , "robert_evt" , "silk"
2015-09-23 23:19:38 +02:00
} ;
2015-11-23 23:04:46 +01:00
private static final Random mRandom = new Random ( ) ;
2015-05-18 20:56:19 +02:00
2016-06-16 00:24:27 +02:00
int mFwMajor = 3 ;
2017-02-19 22:59:37 +01:00
boolean mEnablePebbleKit = false ;
2017-02-10 23:06:34 +01:00
boolean mAlwaysACKPebbleKit = false ;
2016-12-31 12:35:40 +01:00
private boolean mForceProtocol = false ;
private GBDeviceEventScreenshot mDevEventScreenshot = null ;
private int mScreenshotRemaining = - 1 ;
2015-06-19 12:34:33 +02:00
2015-08-22 00:14:14 +02:00
//monochrome black + white
2016-12-31 12:35:40 +01:00
private static final byte [ ] clut_pebble = {
2015-08-22 00:14:14 +02:00
0x00 , 0x00 , 0x00 , 0x00 ,
( byte ) 0xff , ( byte ) 0xff , ( byte ) 0xff , 0x00
} ;
// linear BGR222 (6 bit, 64 entries)
2016-12-31 12:35:40 +01:00
private static final byte [ ] clut_pebbletime = new byte [ ] {
2015-08-22 00:14:14 +02:00
0x00 , 0x00 , 0x00 , 0x00 ,
0x55 , 0x00 , 0x00 , 0x00 ,
( byte ) 0xaa , 0x00 , 0x00 , 0x00 ,
( byte ) 0xff , 0x00 , 0x00 , 0x00 ,
0x00 , 0x55 , 0x00 , 0x00 ,
0x55 , 0x55 , 0x00 , 0x00 ,
( byte ) 0xaa , 0x55 , 0x00 , 0x00 ,
( byte ) 0xff , 0x55 , 0x00 , 0x00 ,
0x00 , ( byte ) 0xaa , 0x00 , 0x00 ,
0x55 , ( byte ) 0xaa , 0x00 , 0x00 ,
( byte ) 0xaa , ( byte ) 0xaa , 0x00 , 0x00 ,
( byte ) 0xff , ( byte ) 0xaa , 0x00 , 0x00 ,
0x00 , ( byte ) 0xff , 0x00 , 0x00 ,
0x55 , ( byte ) 0xff , 0x00 , 0x00 ,
( byte ) 0xaa , ( byte ) 0xff , 0x00 , 0x00 ,
( byte ) 0xff , ( byte ) 0xff , 0x00 , 0x00 ,
0x00 , 0x00 , 0x55 , 0x00 ,
0x55 , 0x00 , 0x55 , 0x00 ,
( byte ) 0xaa , 0x00 , 0x55 , 0x00 ,
( byte ) 0xff , 0x00 , 0x55 , 0x00 ,
0x00 , 0x55 , 0x55 , 0x00 ,
0x55 , 0x55 , 0x55 , 0x00 ,
( byte ) 0xaa , 0x55 , 0x55 , 0x00 ,
( byte ) 0xff , 0x55 , 0x55 , 0x00 ,
0x00 , ( byte ) 0xaa , 0x55 , 0x00 ,
0x55 , ( byte ) 0xaa , 0x55 , 0x00 ,
( byte ) 0xaa , ( byte ) 0xaa , 0x55 , 0x00 ,
( byte ) 0xff , ( byte ) 0xaa , 0x55 , 0x00 ,
0x00 , ( byte ) 0xff , 0x55 , 0x00 ,
0x55 , ( byte ) 0xff , 0x55 , 0x00 ,
( byte ) 0xaa , ( byte ) 0xff , 0x55 , 0x00 ,
( byte ) 0xff , ( byte ) 0xff , 0x55 , 0x00 ,
0x00 , 0x00 , ( byte ) 0xaa , 0x00 ,
0x55 , 0x00 , ( byte ) 0xaa , 0x00 ,
( byte ) 0xaa , 0x00 , ( byte ) 0xaa , 0x00 ,
( byte ) 0xff , 0x00 , ( byte ) 0xaa , 0x00 ,
0x00 , 0x55 , ( byte ) 0xaa , 0x00 ,
0x55 , 0x55 , ( byte ) 0xaa , 0x00 ,
( byte ) 0xaa , 0x55 , ( byte ) 0xaa , 0x00 ,
( byte ) 0xff , 0x55 , ( byte ) 0xaa , 0x00 ,
0x00 , ( byte ) 0xaa , ( byte ) 0xaa , 0x00 ,
0x55 , ( byte ) 0xaa , ( byte ) 0xaa , 0x00 ,
( byte ) 0xaa , ( byte ) 0xaa , ( byte ) 0xaa , 0x00 ,
( byte ) 0xff , ( byte ) 0xaa , ( byte ) 0xaa , 0x00 ,
0x00 , ( byte ) 0xff , ( byte ) 0xaa , 0x00 ,
0x55 , ( byte ) 0xff , ( byte ) 0xaa , 0x00 ,
( byte ) 0xaa , ( byte ) 0xff , ( byte ) 0xaa , 0x00 ,
( byte ) 0xff , ( byte ) 0xff , ( byte ) 0xaa , 0x00 ,
0x00 , 0x00 , ( byte ) 0xff , 0x00 ,
0x55 , 0x00 , ( byte ) 0xff , 0x00 ,
( byte ) 0xaa , 0x00 , ( byte ) 0xff , 0x00 ,
( byte ) 0xff , 0x00 , ( byte ) 0xff , 0x00 ,
0x00 , 0x55 , ( byte ) 0xff , 0x00 ,
0x55 , 0x55 , ( byte ) 0xff , 0x00 ,
( byte ) 0xaa , 0x55 , ( byte ) 0xff , 0x00 ,
( byte ) 0xff , 0x55 , ( byte ) 0xff , 0x00 ,
0x00 , ( byte ) 0xaa , ( byte ) 0xff , 0x00 ,
0x55 , ( byte ) 0xaa , ( byte ) 0xff , 0x00 ,
( byte ) 0xaa , ( byte ) 0xaa , ( byte ) 0xff , 0x00 ,
( byte ) 0xff , ( byte ) 0xaa , ( byte ) 0xff , 0x00 ,
0x00 , ( byte ) 0xff , ( byte ) 0xff , 0x00 ,
0x55 , ( byte ) 0xff , ( byte ) 0xff , 0x00 ,
( byte ) 0xaa , ( byte ) 0xff , ( byte ) 0xff , 0x00 ,
( byte ) 0xff , ( byte ) 0xff , ( byte ) 0xff , 0x00 ,
} ;
2015-05-21 18:17:39 +02:00
byte last_id = - 1 ;
2015-11-23 23:04:46 +01:00
private final ArrayList < UUID > tmpUUIDS = new ArrayList < > ( ) ;
2015-05-18 20:56:19 +02:00
2016-01-27 23:00:44 +01:00
public static final UUID UUID_PEBBLE_HEALTH = UUID . fromString ( "36d8c6ed-4c83-4fa1-a9e2-8f12dc941f8c" ) ; // FIXME: store somewhere else, this is also accessed by other code
2016-11-15 11:56:14 +01:00
public static final UUID UUID_WORKOUT = UUID . fromString ( "fef82c82-7176-4e22-88de-35a3fc18d43f" ) ; // FIXME: store somewhere else, this is also accessed by other code
2016-12-30 20:14:13 +01:00
public static final UUID UUID_WEATHER = UUID . fromString ( "61b22bc8-1e29-460d-a236-3fe409a439ff" ) ; // FIXME: store somewhere else, this is also accessed by other code
2017-01-09 15:11:50 +01:00
public static final UUID UUID_NOTIFICATIONS = UUID . fromString ( "b2cae818-10f8-46df-ad2b-98ad2254a3c1" ) ;
2015-10-04 15:53:11 +02:00
private static final UUID UUID_GBPEBBLE = UUID . fromString ( "61476764-7465-7262-6469-656775527a6c" ) ;
private static final UUID UUID_MORPHEUZ = UUID . fromString ( "5be44f1d-d262-4ea6-aa30-ddbec1e3cab2" ) ;
private static final UUID UUID_MISFIT = UUID . fromString ( "0b73b76a-cd65-4dc2-9585-aaa213320858" ) ;
2015-12-23 09:04:01 +01:00
private static final UUID UUID_PEBBLE_TIMESTYLE = UUID . fromString ( "4368ffa4-f0fb-4823-90be-f754b076bdaa" ) ;
2015-12-23 14:22:28 +01:00
private static final UUID UUID_PEBSTYLE = UUID . fromString ( "da05e84d-e2a2-4020-a2dc-9cdcf265fcdd" ) ;
2016-01-10 20:12:52 +01:00
private static final UUID UUID_MARIOTIME = UUID . fromString ( "43caa750-2896-4f46-94dc-1adbd4bc1ff3" ) ;
2016-12-31 20:08:53 +01:00
private static final UUID UUID_HELTHIFY = UUID . fromString ( "7ee97b2c-95e8-4720-b94e-70fccd905d98" ) ;
2017-01-01 16:24:46 +01:00
private static final UUID UUID_TREKVOLLE = UUID . fromString ( "2da02267-7a19-4e49-9ed1-439d25db14e4" ) ;
2017-01-07 22:41:10 +01:00
private static final UUID UUID_SQUARE = UUID . fromString ( "cb332373-4ee5-4c5c-8912-4f62af2d756c" ) ;
2017-01-08 15:27:01 +01:00
private static final UUID UUID_ZALEWSZCZAK_CROWEX = UUID . fromString ( "a88b3151-2426-43c6-b1d0-9b288b3ec47e" ) ;
private static final UUID UUID_ZALEWSZCZAK_FANCY = UUID . fromString ( "014e17bf-5878-4781-8be1-8ef998cee1ba" ) ;
private static final UUID UUID_ZALEWSZCZAK_TALLY = UUID . fromString ( "abb51965-52e2-440a-b93c-843eeacb697d" ) ;
2017-02-05 17:21:04 +01:00
private static final UUID UUID_OBSIDIAN = UUID . fromString ( "ef42caba-0c65-4879-ab23-edd2bde68824" ) ;
2015-10-04 15:53:11 +02:00
2016-02-03 23:27:35 +01:00
private static final UUID UUID_ZERO = new UUID ( 0 , 0 ) ;
2015-10-04 15:53:11 +02:00
2016-12-30 15:26:44 +01:00
private static final UUID UUID_LOCATION = UUID . fromString ( "2c7e6a86-51e5-4ddd-b606-db43d1e4ad28" ) ; // might be the location of "Berlin" or "Auto"
2016-05-16 23:00:04 +02:00
private final Map < UUID , AppMessageHandler > mAppMessageHandlers = new HashMap < > ( ) ;
2015-10-04 15:53:11 +02:00
2016-12-31 18:56:24 +01:00
private UUID currentRunningApp = UUID_ZERO ;
2016-05-16 23:00:04 +02:00
public PebbleProtocol ( GBDevice device ) {
super ( device ) ;
2015-10-04 15:53:11 +02:00
mAppMessageHandlers . put ( UUID_MORPHEUZ , new AppMessageHandlerMorpheuz ( UUID_MORPHEUZ , PebbleProtocol . this ) ) ;
2015-10-21 16:11:16 +02:00
mAppMessageHandlers . put ( UUID_MISFIT , new AppMessageHandlerMisfit ( UUID_MISFIT , PebbleProtocol . this ) ) ;
2015-12-23 09:04:01 +01:00
mAppMessageHandlers . put ( UUID_PEBBLE_TIMESTYLE , new AppMessageHandlerTimeStylePebble ( UUID_PEBBLE_TIMESTYLE , PebbleProtocol . this ) ) ;
2017-01-01 16:24:46 +01:00
//mAppMessageHandlers.put(UUID_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this));
2016-01-10 20:12:52 +01:00
mAppMessageHandlers . put ( UUID_MARIOTIME , new AppMessageHandlerMarioTime ( UUID_MARIOTIME , PebbleProtocol . this ) ) ;
2016-12-31 20:08:53 +01:00
mAppMessageHandlers . put ( UUID_HELTHIFY , new AppMessageHandlerHealthify ( UUID_HELTHIFY , PebbleProtocol . this ) ) ;
2017-01-01 16:24:46 +01:00
mAppMessageHandlers . put ( UUID_TREKVOLLE , new AppMessageHandlerTrekVolle ( UUID_TREKVOLLE , PebbleProtocol . this ) ) ;
2017-01-07 22:41:10 +01:00
mAppMessageHandlers . put ( UUID_SQUARE , new AppMessageHandlerSquare ( UUID_SQUARE , PebbleProtocol . this ) ) ;
2017-01-08 15:27:01 +01:00
mAppMessageHandlers . put ( UUID_ZALEWSZCZAK_CROWEX , new AppMessageHandlerZalewszczak ( UUID_ZALEWSZCZAK_CROWEX , PebbleProtocol . this ) ) ;
mAppMessageHandlers . put ( UUID_ZALEWSZCZAK_FANCY , new AppMessageHandlerZalewszczak ( UUID_ZALEWSZCZAK_FANCY , PebbleProtocol . this ) ) ;
mAppMessageHandlers . put ( UUID_ZALEWSZCZAK_TALLY , new AppMessageHandlerZalewszczak ( UUID_ZALEWSZCZAK_TALLY , PebbleProtocol . this ) ) ;
2017-02-05 17:21:04 +01:00
mAppMessageHandlers . put ( UUID_OBSIDIAN , new AppMessageHandlerObsidian ( UUID_OBSIDIAN , PebbleProtocol . this ) ) ;
2015-10-04 15:53:11 +02:00
}
2015-05-18 20:56:19 +02:00
2016-02-03 23:27:35 +01:00
private final HashMap < Byte , DatalogSession > mDatalogSessions = new HashMap < > ( ) ;
2016-12-04 17:21:29 +01:00
private byte [ ] encodeSimpleMessage ( short endpoint , byte command ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_SIMPLEMESSAGE = 1 ;
2015-06-19 23:54:31 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_SIMPLEMESSAGE ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_SIMPLEMESSAGE ) ;
buf . putShort ( endpoint ) ;
buf . put ( command ) ;
return buf . array ( ) ;
}
2016-02-03 23:27:35 +01:00
private byte [ ] encodeMessage ( short endpoint , byte type , int cookie , String [ ] parts ) {
2015-01-07 14:00:18 +01:00
// Calculate length first
int length = LENGTH_PREFIX + 1 ;
2015-03-22 00:34:54 +01:00
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
length + + ; // encode null or empty strings as 0x00 later
continue ;
}
length + = ( 1 + s . getBytes ( ) . length ) ;
2015-02-08 23:53:40 +01:00
}
2015-01-07 14:00:18 +01:00
}
2015-02-08 23:53:40 +01:00
if ( endpoint = = ENDPOINT_PHONECONTROL ) {
length + = 4 ; //for cookie;
}
2015-01-07 14:00:18 +01:00
// Encode Prefix
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) ( length - LENGTH_PREFIX ) ) ;
buf . putShort ( endpoint ) ;
buf . put ( type ) ;
2015-02-08 23:53:40 +01:00
if ( endpoint = = ENDPOINT_PHONECONTROL ) {
buf . putInt ( cookie ) ;
}
2015-01-07 14:00:18 +01:00
// Encode Pascal-Style Strings
2015-03-22 00:34:54 +01:00
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
buf . put ( ( byte ) 0x00 ) ;
continue ;
}
2015-01-07 14:00:18 +01:00
2015-03-22 00:34:54 +01:00
int partlength = s . getBytes ( ) . length ;
if ( partlength > 255 ) partlength = 255 ;
buf . put ( ( byte ) partlength ) ;
buf . put ( s . getBytes ( ) , 0 , partlength ) ;
}
2015-01-07 14:00:18 +01:00
}
return buf . array ( ) ;
}
2015-09-24 14:45:21 +02:00
@Override
public byte [ ] encodeNotification ( NotificationSpec notificationSpec ) {
2015-12-14 23:31:31 +01:00
boolean hasHandle = notificationSpec . id ! = - 1 & & notificationSpec . phoneNumber = = null ;
2015-09-24 14:45:21 +02:00
int id = notificationSpec . id ! = - 1 ? notificationSpec . id : mRandom . nextInt ( ) ;
String title ;
String subtitle = null ;
2017-02-09 17:35:46 +01:00
// for SMS that came in though the SMS receiver
2015-09-24 14:45:21 +02:00
if ( notificationSpec . sender ! = null ) {
title = notificationSpec . sender ;
subtitle = notificationSpec . subject ;
} else {
title = notificationSpec . title ;
}
2015-05-21 18:57:34 +02:00
Long ts = System . currentTimeMillis ( ) ;
2016-06-16 00:24:27 +02:00
if ( mFwMajor < 3 ) {
2015-08-18 00:12:40 +02:00
ts + = ( SimpleTimeZone . getDefault ( ) . getOffset ( ts ) ) ;
}
2015-05-21 18:57:34 +02:00
ts / = 1000 ;
2015-01-07 14:00:18 +01:00
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2015-07-19 00:26:43 +02:00
// 3.x notification
2015-12-13 12:03:57 +01:00
return encodeBlobdbNotification ( id , ( int ) ( ts & 0xffffffffL ) , title , subtitle , notificationSpec . body , notificationSpec . sourceName , hasHandle , notificationSpec . type , notificationSpec . cannedReplies ) ;
2016-10-11 11:54:52 +02:00
} else if ( mForceProtocol | | notificationSpec . type ! = NotificationType . GENERIC_EMAIL ) {
2015-07-19 00:26:43 +02:00
// 2.x notification
2015-12-13 12:03:57 +01:00
return encodeExtensibleNotification ( id , ( int ) ( ts & 0xffffffffL ) , title , subtitle , notificationSpec . body , notificationSpec . sourceName , hasHandle , notificationSpec . cannedReplies ) ;
2015-07-19 00:26:43 +02:00
} else {
// 1.x notification on FW 2.X
2015-09-24 14:45:21 +02:00
String [ ] parts = { title , notificationSpec . body , ts . toString ( ) , subtitle } ;
2015-09-12 16:55:47 +02:00
// be aware that type is at this point always NOTIFICATION_EMAIL
2015-09-24 14:45:21 +02:00
return encodeMessage ( ENDPOINT_NOTIFICATION , NOTIFICATION_EMAIL , 0 , parts ) ;
2015-05-15 21:34:38 +02:00
}
2015-07-19 00:26:43 +02:00
}
2015-06-19 12:34:33 +02:00
2017-01-09 16:33:00 +01:00
@Override
public byte [ ] encodeDeleteNotification ( int id ) {
return encodeBlobdb ( new UUID ( GB_UUID_MASK , id ) , BLOBDB_DELETE , BLOBDB_NOTIFICATION , null ) ;
}
2016-05-16 17:30:11 +02:00
@Override
public byte [ ] encodeAddCalendarEvent ( CalendarEventSpec calendarEventSpec ) {
long id = calendarEventSpec . id ! = - 1 ? calendarEventSpec . id : mRandom . nextLong ( ) ;
int iconId ;
2017-04-20 20:09:29 +02:00
ArrayList < Pair < Integer , Object > > attributes = new ArrayList < > ( ) ;
attributes . add ( new Pair < > ( 1 , ( Object ) calendarEventSpec . title ) ) ;
2016-05-16 17:30:11 +02:00
switch ( calendarEventSpec . type ) {
case CalendarEventSpec . TYPE_SUNRISE :
iconId = PebbleIconID . SUNRISE ;
break ;
case CalendarEventSpec . TYPE_SUNSET :
iconId = PebbleIconID . SUNSET ;
break ;
default :
iconId = PebbleIconID . TIMELINE_CALENDAR ;
2017-04-20 20:09:29 +02:00
attributes . add ( new Pair < > ( 3 , ( Object ) calendarEventSpec . description ) ) ;
2016-05-16 17:30:11 +02:00
}
2017-04-20 20:09:29 +02:00
return encodeTimelinePin ( new UUID ( GB_UUID_MASK | calendarEventSpec . type , id ) , calendarEventSpec . timestamp , ( short ) ( calendarEventSpec . durationInSeconds / 60 ) , iconId , attributes ) ;
2016-05-16 23:37:40 +02:00
}
@Override
2016-05-24 13:11:57 +02:00
public byte [ ] encodeDeleteCalendarEvent ( byte type , long id ) {
return encodeBlobdb ( new UUID ( GB_UUID_MASK | type , id ) , BLOBDB_DELETE , BLOBDB_PIN , null ) ;
2016-05-16 17:30:11 +02:00
}
2015-05-12 06:28:11 +02:00
@Override
2015-08-21 00:58:18 +02:00
public byte [ ] encodeSetTime ( ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_SETTIME = 5 ;
2015-08-21 00:58:18 +02:00
long ts = System . currentTimeMillis ( ) ;
2015-08-18 00:12:40 +02:00
long ts_offset = ( SimpleTimeZone . getDefault ( ) . getOffset ( ts ) ) ;
ByteBuffer buf ;
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2015-12-08 14:59:24 +01:00
String timezone = SimpleTimeZone . getDefault ( ) . getID ( ) ;
short length = ( short ) ( LENGTH_SETTIME + timezone . getBytes ( ) . length + 3 ) ;
2015-08-18 00:12:40 +02:00
buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( length ) ;
buf . putShort ( ENDPOINT_TIME ) ;
buf . put ( TIME_SETTIME_UTC ) ;
buf . putInt ( ( int ) ( ts / 1000 ) ) ;
buf . putShort ( ( short ) ( ts_offset / 60000 ) ) ;
2015-12-08 14:59:24 +01:00
buf . put ( ( byte ) timezone . getBytes ( ) . length ) ;
2015-08-18 00:12:40 +02:00
buf . put ( timezone . getBytes ( ) ) ;
LOG . info ( timezone ) ;
} else {
buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_SETTIME ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_SETTIME ) ;
buf . putShort ( ENDPOINT_TIME ) ;
buf . put ( TIME_SETTIME ) ;
buf . putInt ( ( int ) ( ( ts + ts_offset ) / 1000 ) ) ;
}
2015-01-07 14:00:18 +01:00
return buf . array ( ) ;
}
2015-06-21 23:53:23 +02:00
@Override
public byte [ ] encodeFindDevice ( boolean start ) {
2016-12-31 12:15:44 +01:00
return encodeSetCallState ( "Where are you?" , "Gadgetbridge" , start ? CallSpec . CALL_INCOMING : CallSpec . CALL_END ) ;
/ *
2016-12-30 15:26:44 +01:00
int ts = ( int ) ( System . currentTimeMillis ( ) / 1000 ) ;
if ( start ) {
//return encodeWeatherPin(ts, "Weather", "1°/-1°", "Gadgetbridge is Sunny", "Berlin", 37);
}
2016-12-31 12:15:44 +01:00
* /
2015-06-21 23:53:23 +02:00
}
2016-12-04 17:21:29 +01:00
private byte [ ] encodeExtensibleNotification ( int id , int timestamp , String title , String subtitle , String body , String sourceName , boolean hasHandle , String [ ] cannedReplies ) {
2015-09-02 22:43:22 +02:00
final short ACTION_LENGTH_MIN = 10 ;
2015-07-21 01:33:13 +02:00
String [ ] parts = { title , subtitle , body } ;
2015-05-15 21:34:38 +02:00
// Calculate length first
2015-09-02 22:43:22 +02:00
byte actions_count ;
short actions_length ;
String dismiss_string ;
String open_string = "Open on phone" ;
2015-09-25 00:53:40 +02:00
String mute_string = "Mute" ;
2015-12-13 12:03:57 +01:00
String reply_string = "Reply" ;
2015-09-25 00:53:40 +02:00
if ( sourceName ! = null ) {
mute_string + = " " + sourceName ;
}
2015-09-02 22:43:22 +02:00
byte dismiss_action_id ;
2015-09-25 00:53:40 +02:00
2017-02-10 23:11:21 +01:00
if ( hasHandle & & ! "ALARMCLOCKRECEIVER" . equals ( sourceName ) ) {
2015-09-25 00:53:40 +02:00
actions_count = 3 ;
2015-09-02 22:43:22 +02:00
dismiss_string = "Dismiss" ;
dismiss_action_id = 0x02 ;
2015-09-25 00:53:40 +02:00
actions_length = ( short ) ( ACTION_LENGTH_MIN * actions_count + dismiss_string . getBytes ( ) . length + open_string . getBytes ( ) . length + mute_string . getBytes ( ) . length ) ;
2015-09-01 21:58:36 +02:00
} else {
2015-09-02 22:43:22 +02:00
actions_count = 1 ;
dismiss_string = "Dismiss all" ;
dismiss_action_id = 0x03 ;
2015-09-19 15:32:09 +02:00
actions_length = ( short ) ( ACTION_LENGTH_MIN * actions_count + dismiss_string . getBytes ( ) . length ) ;
2015-09-01 21:58:36 +02:00
}
2015-12-13 12:03:57 +01:00
int replies_length = - 1 ;
2015-12-20 00:58:14 +01:00
if ( cannedReplies ! = null & & cannedReplies . length > 0 ) {
2015-12-13 12:03:57 +01:00
actions_count + + ;
for ( String reply : cannedReplies ) {
replies_length + = reply . getBytes ( ) . length + 1 ;
}
actions_length + = ACTION_LENGTH_MIN + reply_string . getBytes ( ) . length + replies_length + 3 ; // 3 = attribute id (byte) + length(short)
}
2015-05-15 21:34:38 +02:00
byte attributes_count = 0 ;
2015-09-02 22:43:22 +02:00
int length = 21 + 10 + actions_length ;
2015-05-15 21:34:38 +02:00
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
attributes_count + + ;
length + = ( 3 + s . getBytes ( ) . length ) ;
}
}
// Encode Prefix
ByteBuffer buf = ByteBuffer . allocate ( length + LENGTH_PREFIX ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) ( length ) ) ;
buf . putShort ( ENDPOINT_EXTENSIBLENOTIFS ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ; // !
buf . put ( ( byte ) 0x00 ) ; // ?
buf . put ( ( byte ) 0x01 ) ; // add notifications
2015-09-05 20:40:12 +02:00
buf . putInt ( 0x00000000 ) ; // flags - ?
2015-05-15 21:34:38 +02:00
buf . putInt ( id ) ;
buf . putInt ( 0x00000000 ) ; // ANCS id
buf . putInt ( timestamp ) ;
buf . put ( ( byte ) 0x01 ) ; // layout - ?
2015-09-02 22:43:22 +02:00
buf . put ( attributes_count ) ;
buf . put ( actions_count ) ;
2015-05-15 21:34:38 +02:00
byte attribute_id = 0 ;
// Encode Pascal-Style Strings
if ( parts ! = null ) {
for ( String s : parts ) {
attribute_id + + ;
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
int partlength = s . getBytes ( ) . length ;
if ( partlength > 255 ) partlength = 255 ;
buf . put ( attribute_id ) ;
buf . putShort ( ( short ) partlength ) ;
buf . put ( s . getBytes ( ) , 0 , partlength ) ;
}
}
2015-08-31 22:27:25 +02:00
2015-09-02 22:43:22 +02:00
// dismiss action
buf . put ( dismiss_action_id ) ;
buf . put ( ( byte ) 0x04 ) ; // dismiss
2015-08-31 22:27:25 +02:00
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) dismiss_string . getBytes ( ) . length ) ;
2015-09-02 22:43:22 +02:00
buf . put ( dismiss_string . getBytes ( ) ) ;
2015-12-13 12:03:57 +01:00
// open and mute actions
2017-02-10 23:16:22 +01:00
if ( hasHandle & & ! "ALARMCLOCKRECEIVER" . equals ( sourceName ) ) {
2015-09-02 22:43:22 +02:00
buf . put ( ( byte ) 0x01 ) ;
2015-09-25 00:53:40 +02:00
buf . put ( ( byte ) 0x02 ) ; // generic
2015-09-02 22:43:22 +02:00
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) open_string . getBytes ( ) . length ) ;
2015-09-02 22:43:22 +02:00
buf . put ( open_string . getBytes ( ) ) ;
2015-09-25 00:53:40 +02:00
buf . put ( ( byte ) 0x04 ) ;
buf . put ( ( byte ) 0x02 ) ; // generic
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
buf . putShort ( ( short ) mute_string . getBytes ( ) . length ) ;
buf . put ( mute_string . getBytes ( ) ) ;
2015-09-02 22:43:22 +02:00
}
2015-08-31 22:27:25 +02:00
2015-12-17 00:05:42 +01:00
if ( cannedReplies ! = null & & replies_length > 0 ) {
2015-12-13 12:03:57 +01:00
buf . put ( ( byte ) 0x05 ) ;
buf . put ( ( byte ) 0x03 ) ; // reply action
buf . put ( ( byte ) 0x02 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // title
buf . putShort ( ( short ) reply_string . getBytes ( ) . length ) ;
buf . put ( reply_string . getBytes ( ) ) ;
buf . put ( ( byte ) 0x08 ) ; // canned replies
buf . putShort ( ( short ) replies_length ) ;
for ( int i = 0 ; i < cannedReplies . length - 1 ; i + + ) {
buf . put ( cannedReplies [ i ] . getBytes ( ) ) ;
buf . put ( ( byte ) 0x00 ) ;
}
// last one must not be zero terminated, else we get an additional emply reply
buf . put ( cannedReplies [ cannedReplies . length - 1 ] . getBytes ( ) ) ;
}
2015-05-15 21:34:38 +02:00
return buf . array ( ) ;
}
2016-01-26 10:48:50 +01:00
private byte [ ] encodeBlobdb ( Object key , byte command , byte db , byte [ ] blob ) {
int length = 5 ;
int key_length ;
if ( key instanceof UUID ) {
key_length = LENGTH_UUID ;
} else if ( key instanceof String ) {
key_length = ( ( String ) key ) . getBytes ( ) . length ;
} else {
LOG . warn ( "unknown key type" ) ;
return null ;
}
if ( key_length > 255 ) {
LOG . warn ( "key is too long" ) ;
return null ;
}
length + = key_length ;
2015-08-21 14:29:12 +02:00
if ( blob ! = null ) {
length + = blob . length + 2 ;
}
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) length ) ;
buf . putShort ( ENDPOINT_BLOBDB ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( command ) ;
buf . putShort ( ( short ) mRandom . nextInt ( ) ) ; // token
buf . put ( db ) ;
2016-01-26 10:48:50 +01:00
buf . put ( ( byte ) key_length ) ;
if ( key instanceof UUID ) {
UUID uuid = ( UUID ) key ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
} else {
buf . put ( ( ( String ) key ) . getBytes ( ) ) ;
}
2015-08-21 14:29:12 +02:00
if ( blob ! = null ) {
buf . putShort ( ( short ) blob . length ) ;
buf . put ( blob ) ;
}
return buf . array ( ) ;
}
2016-11-15 12:17:51 +01:00
byte [ ] encodeActivateHealth ( boolean activate ) {
2016-01-26 10:48:50 +01:00
byte [ ] blob ;
if ( activate ) {
2016-01-29 17:45:35 +01:00
ByteBuffer buf = ByteBuffer . allocate ( 9 ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2016-02-02 17:33:24 +01:00
ActivityUser activityUser = new ActivityUser ( ) ;
2016-05-13 23:47:47 +02:00
Integer heightMm = activityUser . getHeightCm ( ) * 10 ;
2016-01-29 17:45:35 +01:00
buf . putShort ( heightMm . shortValue ( ) ) ;
2016-05-13 23:47:47 +02:00
Integer weigthDag = activityUser . getWeightKg ( ) * 100 ;
2016-01-29 17:45:35 +01:00
buf . putShort ( weigthDag . shortValue ( ) ) ;
2016-02-03 20:23:56 +01:00
buf . put ( ( byte ) 0x01 ) ; //activate tracking
2016-02-06 21:38:55 +01:00
buf . put ( ( byte ) 0x00 ) ; //activity Insights
buf . put ( ( byte ) 0x00 ) ; //sleep Insights
2016-05-13 23:47:47 +02:00
buf . put ( ( byte ) activityUser . getAge ( ) ) ;
buf . put ( ( byte ) activityUser . getGender ( ) ) ;
2016-01-29 17:45:35 +01:00
blob = buf . array ( ) ;
2016-01-26 10:48:50 +01:00
} else {
2016-01-28 11:59:23 +01:00
blob = new byte [ ] { 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ;
2016-01-25 23:15:19 +01:00
}
2016-11-15 12:17:51 +01:00
return encodeBlobdb ( "activityPreferences" , BLOBDB_INSERT , BLOBDB_PREFERENCES , blob ) ;
2016-01-25 23:15:19 +01:00
}
2016-11-15 12:17:51 +01:00
byte [ ] encodeSetSaneDistanceUnit ( boolean sane ) {
2016-02-07 21:59:14 +01:00
byte value ;
if ( sane ) {
value = 0x00 ;
} else {
value = 0x01 ;
}
return encodeBlobdb ( "unitsDistance" , BLOBDB_INSERT , BLOBDB_PREFERENCES , new byte [ ] { value } ) ;
2016-02-07 16:44:16 +01:00
}
2016-11-15 12:17:51 +01:00
byte [ ] encodeActivateHRM ( boolean activate ) {
return encodeBlobdb ( "hrmPreferences" , BLOBDB_INSERT , BLOBDB_PREFERENCES ,
activate ? new byte [ ] { 0x01 } : new byte [ ] { 0x00 } ) ;
}
2016-12-30 15:26:44 +01:00
byte [ ] encodeActivateWeather ( boolean activate ) {
if ( activate ) {
ByteBuffer buf = ByteBuffer . allocate ( 0x61 ) ;
buf . put ( ( byte ) 1 ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putLong ( UUID_LOCATION . getMostSignificantBits ( ) ) ;
buf . putLong ( UUID_LOCATION . getLeastSignificantBits ( ) ) ;
// disable remaining 5 possible location
2017-01-01 13:55:07 +01:00
buf . put ( new byte [ 60 - LENGTH_UUID ] ) ;
2016-12-30 15:26:44 +01:00
return encodeBlobdb ( "weatherApp" , BLOBDB_INSERT , BLOBDB_APPSETTINGS , buf . array ( ) ) ;
} else {
return encodeBlobdb ( "weatherApp" , BLOBDB_DELETE , BLOBDB_APPSETTINGS , null ) ;
}
}
2016-11-15 12:17:51 +01:00
byte [ ] encodeReportDataLogSessions ( ) {
2016-02-03 23:27:35 +01:00
return encodeSimpleMessage ( ENDPOINT_DATALOG , DATALOG_REPORTSESSIONS ) ;
2016-01-25 23:15:19 +01:00
}
2015-12-20 00:40:38 +01:00
private byte [ ] encodeBlobDBClear ( byte database ) {
final short LENGTH_BLOBDB_CLEAR = 4 ;
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_BLOBDB_CLEAR ) ;
2015-12-19 20:52:44 +01:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2015-12-20 00:40:38 +01:00
buf . putShort ( LENGTH_BLOBDB_CLEAR ) ;
2015-12-19 20:52:44 +01:00
buf . putShort ( ENDPOINT_BLOBDB ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( BLOBDB_CLEAR ) ;
buf . putShort ( ( short ) mRandom . nextInt ( ) ) ; // token
2015-12-20 00:40:38 +01:00
buf . put ( database ) ;
2015-12-19 20:52:44 +01:00
return buf . array ( ) ;
}
2017-04-20 20:09:29 +02:00
private byte [ ] encodeTimelinePin ( UUID uuid , int timestamp , short duration , int icon_id , List < Pair < Integer , Object > > attributes ) {
2015-09-08 14:15:46 +02:00
final short TIMELINE_PIN_LENGTH = 46 ;
2017-04-20 10:40:28 +02:00
//FIXME: dont depend layout on icon :P
byte layout_id = 0x01 ;
if ( icon_id = = PebbleIconID . TIMELINE_CALENDAR ) {
layout_id = 0x02 ;
}
2015-09-08 14:15:46 +02:00
icon_id | = 0x80000000 ;
byte attributes_count = 2 ;
byte actions_count = 0 ;
2017-04-20 20:09:29 +02:00
int attributes_length = 10 ;
for ( Pair < Integer , Object > pair : attributes ) {
if ( pair . first = = null | | pair . second = = null )
continue ;
if ( pair . second instanceof Integer ) {
attributes_length + = 7 ;
} else if ( pair . second instanceof Byte ) {
attributes_length + = 4 ;
} else if ( pair . second instanceof String ) {
attributes_length + = ( ( String ) pair . second ) . getBytes ( ) . length + 3 ;
} else if ( pair . second instanceof byte [ ] ) {
attributes_length + = ( ( byte [ ] ) pair . second ) . length + 3 ;
} else {
LOG . warn ( "unsupported type for timeline attributes: " + pair . second . getClass ( ) . toString ( ) ) ;
}
2015-12-20 19:50:48 +01:00
}
2015-09-08 14:15:46 +02:00
int pin_length = TIMELINE_PIN_LENGTH + attributes_length ;
ByteBuffer buf = ByteBuffer . allocate ( pin_length ) ;
// pin - 46 bytes
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
buf . putLong ( 0 ) ; // parent
buf . putLong ( 0 ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . putInt ( timestamp ) ; // 32-bit timestamp
buf . putShort ( duration ) ;
buf . put ( ( byte ) 0x02 ) ; // type (0x02 = pin)
buf . putShort ( ( short ) 0x0001 ) ; // flags 0x0001 = ?
2017-04-20 10:40:28 +02:00
buf . put ( layout_id ) ; // layout was (0x02 = pin?), 0x01 needed for subtitle but seems to do no harm if there isn't one
2015-09-08 14:15:46 +02:00
buf . putShort ( ( short ) attributes_length ) ; // total length of all attributes and actions in bytes
buf . put ( attributes_count ) ;
buf . put ( actions_count ) ;
buf . put ( ( byte ) 4 ) ; // icon
buf . putShort ( ( short ) 4 ) ; // length of int
buf . putInt ( icon_id ) ;
2017-04-20 20:09:29 +02:00
for ( Pair < Integer , Object > pair : attributes ) {
if ( pair . first = = null | | pair . second = = null )
continue ;
buf . put ( pair . first . byteValue ( ) ) ;
if ( pair . second instanceof Integer ) {
buf . putShort ( ( short ) 4 ) ;
buf . putInt ( ( ( Integer ) pair . second ) ) ;
} else if ( pair . second instanceof Byte ) {
buf . putShort ( ( short ) 1 ) ;
buf . put ( ( Byte ) pair . second ) ;
} else if ( pair . second instanceof String ) {
buf . putShort ( ( short ) ( ( String ) pair . second ) . getBytes ( ) . length ) ;
buf . put ( ( ( String ) pair . second ) . getBytes ( ) ) ;
} else if ( pair . second instanceof byte [ ] ) {
buf . putShort ( ( short ) ( ( byte [ ] ) pair . second ) . length ) ;
buf . put ( ( byte [ ] ) pair . second ) ;
}
2015-12-20 19:50:48 +01:00
}
2015-09-08 14:15:46 +02:00
return encodeBlobdb ( uuid , BLOBDB_INSERT , BLOBDB_PIN , buf . array ( ) ) ;
}
2015-12-13 12:03:57 +01:00
private byte [ ] encodeBlobdbNotification ( int id , int timestamp , String title , String subtitle , String body , String sourceName , boolean hasHandle , NotificationType notificationType , String [ ] cannedReplies ) {
2015-09-02 22:43:22 +02:00
final short NOTIFICATION_PIN_LENGTH = 46 ;
final short ACTION_LENGTH_MIN = 10 ;
2015-08-17 13:57:01 +02:00
String [ ] parts = { title , subtitle , body } ;
2016-12-09 17:54:19 +01:00
if ( notificationType = = null ) {
notificationType = NotificationType . UNKNOWN ;
2015-08-17 13:57:01 +02:00
}
2016-12-09 17:54:19 +01:00
int icon_id = notificationType . icon ;
byte color_id = notificationType . color ;
2015-06-16 23:14:51 +02:00
// Calculate length first
2015-09-02 22:43:22 +02:00
byte actions_count ;
short actions_length ;
String dismiss_string ;
String open_string = "Open on phone" ;
2015-09-25 00:53:40 +02:00
String mute_string = "Mute" ;
2015-12-13 12:03:57 +01:00
String reply_string = "Reply" ;
2015-09-25 00:53:40 +02:00
if ( sourceName ! = null ) {
mute_string + = " " + sourceName ;
}
2015-09-02 22:43:22 +02:00
byte dismiss_action_id ;
2017-02-10 23:16:22 +01:00
if ( hasHandle & & ! "ALARMCLOCKRECEIVER" . equals ( sourceName ) ) {
2015-09-25 00:53:40 +02:00
actions_count = 3 ;
2015-09-02 22:43:22 +02:00
dismiss_string = "Dismiss" ;
dismiss_action_id = 0x02 ;
2015-09-25 00:53:40 +02:00
actions_length = ( short ) ( ACTION_LENGTH_MIN * actions_count + dismiss_string . getBytes ( ) . length + open_string . getBytes ( ) . length + mute_string . getBytes ( ) . length ) ;
2015-09-01 21:58:36 +02:00
} else {
2015-09-02 22:43:22 +02:00
actions_count = 1 ;
dismiss_string = "Dismiss all" ;
dismiss_action_id = 0x03 ;
2015-09-19 15:32:09 +02:00
actions_length = ( short ) ( ACTION_LENGTH_MIN * actions_count + dismiss_string . getBytes ( ) . length ) ;
2015-09-01 21:58:36 +02:00
}
2015-12-13 12:03:57 +01:00
int replies_length = - 1 ;
2015-12-20 00:58:14 +01:00
if ( cannedReplies ! = null & & cannedReplies . length > 0 ) {
2015-12-13 12:03:57 +01:00
actions_count + + ;
for ( String reply : cannedReplies ) {
replies_length + = reply . getBytes ( ) . length + 1 ;
}
actions_length + = ACTION_LENGTH_MIN + reply_string . getBytes ( ) . length + replies_length + 3 ; // 3 = attribute id (byte) + length(short)
}
2015-09-13 13:41:56 +02:00
byte attributes_count = 2 ; // icon
short attributes_length = ( short ) ( 11 + actions_length ) ;
2015-06-16 23:14:51 +02:00
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
attributes_count + + ;
attributes_length + = ( 3 + s . getBytes ( ) . length ) ;
}
}
2015-07-21 22:05:25 +02:00
short pin_length = ( short ) ( NOTIFICATION_PIN_LENGTH + attributes_length ) ;
2015-08-21 14:29:12 +02:00
ByteBuffer buf = ByteBuffer . allocate ( pin_length ) ;
2015-06-16 23:14:51 +02:00
2015-08-21 14:29:12 +02:00
// pin - 46 bytes
2015-06-16 23:14:51 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2017-01-09 16:33:00 +01:00
buf . putLong ( GB_UUID_MASK ) ;
buf . putLong ( id ) ;
2017-01-09 15:11:50 +01:00
buf . putLong ( UUID_NOTIFICATIONS . getMostSignificantBits ( ) ) ;
buf . putLong ( UUID_NOTIFICATIONS . getLeastSignificantBits ( ) ) ;
2015-06-16 23:14:51 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . putInt ( timestamp ) ; // 32-bit timestamp
buf . putShort ( ( short ) 0 ) ; // duration
buf . put ( ( byte ) 0x01 ) ; // type (0x01 = notification)
2015-08-22 00:29:52 +02:00
buf . putShort ( ( short ) 0x0001 ) ; // flags 0x0001 = ?
2015-08-18 00:12:40 +02:00
buf . put ( ( byte ) 0x04 ) ; // layout (0x04 = notification?)
2015-07-21 22:05:25 +02:00
buf . putShort ( attributes_length ) ; // total length of all attributes and actions in bytes
2015-07-21 21:29:08 +02:00
buf . put ( attributes_count ) ;
buf . put ( actions_count ) ;
2015-06-16 23:14:51 +02:00
byte attribute_id = 0 ;
// Encode Pascal-Style Strings
if ( parts ! = null ) {
for ( String s : parts ) {
attribute_id + + ;
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
int partlength = s . getBytes ( ) . length ;
2015-12-07 12:25:34 +01:00
if ( partlength > 512 ) partlength = 512 ;
2015-06-16 23:14:51 +02:00
buf . put ( attribute_id ) ;
buf . putShort ( ( short ) partlength ) ;
buf . put ( s . getBytes ( ) , 0 , partlength ) ;
}
}
2015-08-17 13:57:01 +02:00
buf . put ( ( byte ) 4 ) ; // icon
buf . putShort ( ( short ) 4 ) ; // length of int
2015-09-12 16:55:47 +02:00
buf . putInt ( 0x80000000 | icon_id ) ;
2015-08-17 13:57:01 +02:00
2015-09-13 13:41:56 +02:00
buf . put ( ( byte ) 28 ) ; // background_color
buf . putShort ( ( short ) 1 ) ; // length of int
buf . put ( color_id ) ;
2015-09-02 22:43:22 +02:00
// dismiss action
buf . put ( dismiss_action_id ) ;
2015-08-27 18:01:19 +02:00
buf . put ( ( byte ) 0x02 ) ; // generic action, dismiss did not do anything
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) dismiss_string . getBytes ( ) . length ) ;
2015-09-02 22:43:22 +02:00
buf . put ( dismiss_string . getBytes ( ) ) ;
2015-09-01 21:58:36 +02:00
2015-09-25 00:53:40 +02:00
// open and mute actions
2017-02-10 23:16:22 +01:00
if ( hasHandle & & ! "ALARMCLOCKRECEIVER" . equals ( sourceName ) ) {
2015-09-02 22:43:22 +02:00
buf . put ( ( byte ) 0x01 ) ;
buf . put ( ( byte ) 0x02 ) ; // generic action
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) open_string . getBytes ( ) . length ) ;
2015-09-02 22:43:22 +02:00
buf . put ( open_string . getBytes ( ) ) ;
2015-09-25 00:53:40 +02:00
buf . put ( ( byte ) 0x04 ) ;
buf . put ( ( byte ) 0x02 ) ; // generic action
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
buf . putShort ( ( short ) mute_string . getBytes ( ) . length ) ;
buf . put ( mute_string . getBytes ( ) ) ;
2015-09-02 22:43:22 +02:00
}
2015-12-13 12:03:57 +01:00
2015-12-17 00:05:42 +01:00
if ( cannedReplies ! = null & & replies_length > 0 ) {
2015-12-13 12:03:57 +01:00
buf . put ( ( byte ) 0x05 ) ;
buf . put ( ( byte ) 0x03 ) ; // reply action
buf . put ( ( byte ) 0x02 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // title
buf . putShort ( ( short ) reply_string . getBytes ( ) . length ) ;
buf . put ( reply_string . getBytes ( ) ) ;
buf . put ( ( byte ) 0x08 ) ; // canned replies
buf . putShort ( ( short ) replies_length ) ;
for ( int i = 0 ; i < cannedReplies . length - 1 ; i + + ) {
buf . put ( cannedReplies [ i ] . getBytes ( ) ) ;
buf . put ( ( byte ) 0x00 ) ;
}
// last one must not be zero terminated, else we get an additional emply reply
buf . put ( cannedReplies [ cannedReplies . length - 1 ] . getBytes ( ) ) ;
}
2015-08-21 14:29:12 +02:00
return encodeBlobdb ( UUID . randomUUID ( ) , BLOBDB_INSERT , BLOBDB_NOTIFICATION , buf . array ( ) ) ;
2015-06-16 23:14:51 +02:00
}
2016-12-04 17:21:29 +01:00
private byte [ ] encodeActionResponse2x ( int id , byte actionId , int iconId , String caption ) {
2015-09-19 15:32:09 +02:00
short length = ( short ) ( 18 + caption . getBytes ( ) . length ) ;
2015-09-05 20:40:12 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( length ) ;
buf . putShort ( ENDPOINT_EXTENSIBLENOTIFS ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( NOTIFICATIONACTION_RESPONSE ) ;
buf . putInt ( id ) ;
2015-09-25 00:53:40 +02:00
buf . put ( actionId ) ;
2015-09-05 20:40:12 +02:00
buf . put ( NOTIFICATIONACTION_ACK ) ;
buf . put ( ( byte ) 2 ) ; //nr of attributes
buf . put ( ( byte ) 6 ) ; // icon
buf . putShort ( ( short ) 4 ) ; // length
buf . putInt ( iconId ) ;
buf . put ( ( byte ) 2 ) ; // title
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) caption . getBytes ( ) . length ) ;
2015-09-05 20:40:12 +02:00
buf . put ( caption . getBytes ( ) ) ;
return buf . array ( ) ;
}
2016-12-30 15:26:44 +01:00
private byte [ ] encodeWeatherPin ( int timestamp , String title , String subtitle , String body , String location , int iconId ) {
final short NOTIFICATION_PIN_LENGTH = 46 ;
final short ACTION_LENGTH_MIN = 10 ;
String [ ] parts = { title , subtitle , body , location , "test" , "test" } ;
// Calculate length first
byte actions_count = 1 ;
short actions_length ;
String remove_string = "Remove" ;
actions_length = ( short ) ( ACTION_LENGTH_MIN * actions_count + remove_string . getBytes ( ) . length ) ;
byte attributes_count = 3 ;
short attributes_length = ( short ) ( 21 + actions_length ) ;
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
attributes_count + + ;
attributes_length + = ( 3 + s . getBytes ( ) . length ) ;
}
}
UUID uuid = UUID . fromString ( "61b22bc8-1e29-460d-a236-3fe409a43901" ) ;
short pin_length = ( short ) ( NOTIFICATION_PIN_LENGTH + attributes_length ) ;
ByteBuffer buf = ByteBuffer . allocate ( pin_length ) ;
// pin (46 bytes)
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) | 0xff ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . putInt ( timestamp ) ; // 32-bit timestamp
buf . putShort ( ( short ) 0 ) ; // duration
buf . put ( ( byte ) 0x02 ) ; // type (0x02 = pin)
buf . putShort ( ( short ) 0x0001 ) ; // flags 0x0001 = ?
buf . put ( ( byte ) 0x06 ) ; // layout (0x06 = weather)
buf . putShort ( attributes_length ) ; // total length of all attributes and actions in bytes
buf . put ( attributes_count ) ;
buf . put ( actions_count ) ;
byte attribute_id = 0 ;
// Encode Pascal-Style Strings
if ( parts ! = null ) {
for ( String s : parts ) {
attribute_id + + ;
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
int partlength = s . getBytes ( ) . length ;
if ( partlength > 512 ) partlength = 512 ;
if ( attribute_id = = 4 ) {
buf . put ( ( byte ) 11 ) ;
} else if ( attribute_id = = 5 ) {
buf . put ( ( byte ) 25 ) ;
} else if ( attribute_id = = 6 ) {
buf . put ( ( byte ) 26 ) ;
} else {
buf . put ( attribute_id ) ;
}
buf . putShort ( ( short ) partlength ) ;
buf . put ( s . getBytes ( ) , 0 , partlength ) ;
}
}
buf . put ( ( byte ) 4 ) ; // icon
buf . putShort ( ( short ) 4 ) ; // length of int
buf . putInt ( 0x80000000 | iconId ) ;
buf . put ( ( byte ) 6 ) ; // icon
buf . putShort ( ( short ) 4 ) ; // length of int
buf . putInt ( 0x80000000 | iconId ) ;
buf . put ( ( byte ) 14 ) ; // last updated
buf . putShort ( ( short ) 4 ) ; // length of int
buf . putInt ( timestamp ) ;
// remove action
buf . put ( ( byte ) 123 ) ; // action id
buf . put ( ( byte ) 0x09 ) ; // remove
buf . put ( ( byte ) 0x01 ) ; // number attributes
buf . put ( ( byte ) 0x01 ) ; // attribute id (title)
buf . putShort ( ( short ) remove_string . getBytes ( ) . length ) ;
buf . put ( remove_string . getBytes ( ) ) ;
return encodeBlobdb ( uuid , BLOBDB_INSERT , BLOBDB_PIN , buf . array ( ) ) ;
}
2016-12-31 15:56:05 +01:00
@Override
public byte [ ] encodeSendWeather ( WeatherSpec weatherSpec ) {
2017-01-01 14:19:35 +01:00
byte [ ] forecastProtocol = null ;
2016-12-31 18:56:24 +01:00
byte [ ] watchfaceProtocol = null ;
2017-01-01 14:19:35 +01:00
int length = 0 ;
if ( mFwMajor > = 4 ) {
forecastProtocol = encodeWeatherForecast ( weatherSpec ) ;
length + = forecastProtocol . length ;
}
2016-12-31 18:56:24 +01:00
AppMessageHandler handler = mAppMessageHandlers . get ( currentRunningApp ) ;
if ( handler ! = null ) {
watchfaceProtocol = handler . encodeUpdateWeather ( weatherSpec ) ;
2017-01-01 14:19:35 +01:00
if ( watchfaceProtocol ! = null ) {
length + = watchfaceProtocol . length ;
}
2016-12-31 18:56:24 +01:00
}
2017-01-01 14:19:35 +01:00
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
2016-12-31 18:56:24 +01:00
2017-01-01 14:19:35 +01:00
if ( forecastProtocol ! = null ) {
2016-12-31 18:56:24 +01:00
buf . put ( forecastProtocol ) ;
2017-01-01 14:19:35 +01:00
}
if ( watchfaceProtocol ! = null ) {
2016-12-31 18:56:24 +01:00
buf . put ( watchfaceProtocol ) ;
}
2017-01-01 14:19:35 +01:00
return buf . array ( ) ;
2016-12-30 20:14:13 +01:00
}
2017-01-01 14:19:35 +01:00
private byte [ ] encodeWeatherForecast ( WeatherSpec weatherSpec ) {
2016-12-30 15:26:44 +01:00
final short WEATHER_FORECAST_LENGTH = 20 ;
2017-01-01 14:19:35 +01:00
String [ ] parts = { weatherSpec . location , weatherSpec . currentCondition } ;
2016-12-30 15:26:44 +01:00
// Calculate length first
short attributes_length = 0 ;
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
attributes_length + = ( 2 + s . getBytes ( ) . length ) ;
}
}
short pin_length = ( short ) ( WEATHER_FORECAST_LENGTH + attributes_length ) ;
ByteBuffer buf = ByteBuffer . allocate ( pin_length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 3 ) ; // unknown, always 3?
2017-01-01 14:19:35 +01:00
buf . putShort ( ( short ) ( weatherSpec . currentTemp - 273 ) ) ;
2017-01-01 16:24:46 +01:00
buf . put ( Weather . mapToPebbleCondition ( weatherSpec . currentConditionCode ) ) ;
2017-01-01 14:19:35 +01:00
buf . putShort ( ( short ) ( weatherSpec . todayMaxTemp - 273 ) ) ;
buf . putShort ( ( short ) ( weatherSpec . todayMinTemp - 273 ) ) ;
2017-01-01 16:24:46 +01:00
buf . put ( Weather . mapToPebbleCondition ( weatherSpec . tomorrowConditionCode ) ) ;
2017-01-01 14:19:35 +01:00
buf . putShort ( ( short ) ( weatherSpec . tomorrowMaxTemp - 273 ) ) ;
buf . putShort ( ( short ) ( weatherSpec . tomorrowMinTemp - 273 ) ) ;
buf . putInt ( weatherSpec . timestamp ) ;
buf . put ( ( byte ) 0 ) ; // automatic location 0=manual 1=auto
2016-12-30 15:26:44 +01:00
buf . putShort ( attributes_length ) ;
// Encode Pascal-Style Strings
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
continue ;
}
int partlength = s . getBytes ( ) . length ;
if ( partlength > 512 ) partlength = 512 ;
buf . putShort ( ( short ) partlength ) ;
buf . put ( s . getBytes ( ) , 0 , partlength ) ;
}
}
return encodeBlobdb ( UUID_LOCATION , BLOBDB_INSERT , BLOBDB_WEATHER , buf . array ( ) ) ;
}
2016-12-04 17:21:29 +01:00
private byte [ ] encodeActionResponse ( UUID uuid , int iconId , String caption ) {
2015-09-19 15:32:09 +02:00
short length = ( short ) ( 29 + caption . getBytes ( ) . length ) ;
2015-08-27 18:01:19 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( length ) ;
buf . putShort ( ENDPOINT_NOTIFICATIONACTION ) ;
buf . put ( NOTIFICATIONACTION_RESPONSE ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( NOTIFICATIONACTION_ACK ) ;
buf . put ( ( byte ) 2 ) ; //nr of attributes
buf . put ( ( byte ) 6 ) ; // icon
buf . putShort ( ( short ) 4 ) ; // length
2015-09-01 21:58:36 +02:00
buf . putInt ( 0x80000000 | iconId ) ;
2015-08-27 18:01:19 +02:00
buf . put ( ( byte ) 2 ) ; // title
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) caption . getBytes ( ) . length ) ;
2015-09-01 21:58:36 +02:00
buf . put ( caption . getBytes ( ) ) ;
2015-08-27 18:01:19 +02:00
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeInstallMetadata ( UUID uuid , String appName , short appVersion , short sdkVersion , int flags , int iconId ) {
2015-08-14 12:50:44 +02:00
final short METADATA_LENGTH = 126 ;
byte [ ] name_buf = new byte [ 96 ] ;
2015-09-19 15:32:09 +02:00
System . arraycopy ( appName . getBytes ( ) , 0 , name_buf , 0 , appName . getBytes ( ) . length ) ;
2015-08-21 14:29:12 +02:00
ByteBuffer buf = ByteBuffer . allocate ( METADATA_LENGTH ) ;
2015-08-14 12:50:44 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ; // watchapp uuid
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2015-08-19 01:40:39 +02:00
buf . putInt ( flags ) ;
2015-08-16 11:33:32 +02:00
buf . putInt ( iconId ) ;
2015-08-14 12:50:44 +02:00
buf . putShort ( appVersion ) ;
buf . putShort ( sdkVersion ) ;
buf . put ( ( byte ) 0 ) ; // app_face_bgcolor
buf . put ( ( byte ) 0 ) ; // app_face_template_id
buf . put ( name_buf ) ; // 96 bytes
2015-08-21 14:29:12 +02:00
return encodeBlobdb ( uuid , BLOBDB_INSERT , BLOBDB_APP , buf . array ( ) ) ;
2015-08-14 12:50:44 +02:00
}
2017-01-01 13:55:07 +01:00
byte [ ] encodeAppFetchAck ( ) {
final short LENGTH_APPFETCH = 2 ;
2015-08-16 11:33:32 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_APPFETCH ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_APPFETCH ) ;
buf . putShort ( ENDPOINT_APPFETCH ) ;
buf . put ( ( byte ) 0x01 ) ;
buf . put ( ( byte ) 0x01 ) ;
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeGetTime ( ) {
2015-06-19 23:54:31 +02:00
return encodeSimpleMessage ( ENDPOINT_TIME , TIME_GETTIME ) ;
2015-04-07 23:57:12 +02:00
}
2015-05-12 06:28:11 +02:00
@Override
2016-04-04 20:08:34 +02:00
public byte [ ] encodeSetCallState ( String number , String name , int command ) {
2015-02-08 23:53:40 +01:00
String [ ] parts = { number , name } ;
2015-02-12 16:00:45 +01:00
byte pebbleCmd ;
switch ( command ) {
2016-04-04 20:08:34 +02:00
case CallSpec . CALL_START :
2015-02-12 16:00:45 +01:00
pebbleCmd = PHONECONTROL_START ;
break ;
2016-04-04 20:08:34 +02:00
case CallSpec . CALL_END :
2015-02-12 16:00:45 +01:00
pebbleCmd = PHONECONTROL_END ;
break ;
2016-04-04 20:08:34 +02:00
case CallSpec . CALL_INCOMING :
2015-02-12 16:00:45 +01:00
pebbleCmd = PHONECONTROL_INCOMINGCALL ;
break ;
2016-04-04 20:08:34 +02:00
case CallSpec . CALL_OUTGOING :
2015-03-07 14:40:59 +01:00
// pebbleCmd = PHONECONTROL_OUTGOINGCALL;
/ *
* HACK / WORKAROUND for non - working outgoing call display .
* Just send a incoming call command immediately followed by a start call command
* This prevents vibration of the Pebble .
* /
byte [ ] callmsg = encodeMessage ( ENDPOINT_PHONECONTROL , PHONECONTROL_INCOMINGCALL , 0 , parts ) ;
byte [ ] startmsg = encodeMessage ( ENDPOINT_PHONECONTROL , PHONECONTROL_START , 0 , parts ) ;
byte [ ] msg = new byte [ callmsg . length + startmsg . length ] ;
System . arraycopy ( callmsg , 0 , msg , 0 , callmsg . length ) ;
System . arraycopy ( startmsg , 0 , msg , startmsg . length , startmsg . length ) ;
return msg ;
// END HACK
2015-02-12 16:00:45 +01:00
default :
return null ;
}
return encodeMessage ( ENDPOINT_PHONECONTROL , pebbleCmd , 0 , parts ) ;
2015-02-08 23:53:40 +01:00
}
2016-03-27 19:50:32 +02:00
public byte [ ] encodeSetMusicState ( byte state , int position , int playRate , byte shuffle , byte repeat ) {
2016-06-19 12:17:19 +02:00
if ( mFwMajor < 3 ) {
return null ;
}
2016-06-09 20:00:14 +02:00
byte playState ;
switch ( state ) {
case MusicStateSpec . STATE_PLAYING :
playState = MUSICCONTROL_STATE_PLAYING ;
break ;
case MusicStateSpec . STATE_PAUSED :
playState = MUSICCONTROL_STATE_PAUSED ;
break ;
default :
playState = MUSICCONTROL_STATE_UNKNOWN ;
break ;
}
2016-03-27 19:50:32 +02:00
int length = LENGTH_PREFIX + 12 ;
// Encode Prefix
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) ( length - LENGTH_PREFIX ) ) ;
buf . putShort ( ENDPOINT_MUSICCONTROL ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( MUSICCONTROL_SETPLAYSTATE ) ;
2016-06-09 20:00:14 +02:00
buf . put ( playState ) ;
2016-06-11 23:37:03 +02:00
buf . putInt ( position * 1000 ) ;
2016-03-27 19:50:32 +02:00
buf . putInt ( playRate ) ;
buf . put ( shuffle ) ;
buf . put ( repeat ) ;
return buf . array ( ) ;
}
2015-05-12 06:28:11 +02:00
@Override
2016-03-27 17:44:20 +02:00
public byte [ ] encodeSetMusicInfo ( String artist , String album , String track , int duration , int trackCount , int trackNr ) {
2015-02-08 23:53:40 +01:00
String [ ] parts = { artist , album , track } ;
2016-06-19 12:17:19 +02:00
if ( duration = = 0 | | mFwMajor < 3 ) {
2016-03-27 17:44:20 +02:00
return encodeMessage ( ENDPOINT_MUSICCONTROL , MUSICCONTROL_SETMUSICINFO , 0 , parts ) ;
} else {
// Calculate length first
2016-03-27 19:50:32 +02:00
int length = LENGTH_PREFIX + 9 ;
2016-03-27 17:44:20 +02:00
if ( parts ! = null ) {
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
length + + ; // encode null or empty strings as 0x00 later
continue ;
}
length + = ( 1 + s . getBytes ( ) . length ) ;
}
}
// Encode Prefix
2016-06-08 20:16:28 +02:00
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
2016-03-27 17:44:20 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) ( length - LENGTH_PREFIX ) ) ;
buf . putShort ( ENDPOINT_MUSICCONTROL ) ;
buf . put ( MUSICCONTROL_SETMUSICINFO ) ;
// Encode Pascal-Style Strings
for ( String s : parts ) {
if ( s = = null | | s . equals ( "" ) ) {
buf . put ( ( byte ) 0x00 ) ;
continue ;
}
int partlength = s . getBytes ( ) . length ;
if ( partlength > 255 ) partlength = 255 ;
buf . put ( ( byte ) partlength ) ;
buf . put ( s . getBytes ( ) , 0 , partlength ) ;
}
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2016-03-27 19:50:32 +02:00
buf . putInt ( duration * 1000 ) ;
buf . putShort ( ( short ) ( trackCount & 0xffff ) ) ;
buf . putShort ( ( short ) ( trackNr & 0xffff ) ) ;
2016-03-27 17:44:20 +02:00
2016-03-27 19:50:32 +02:00
return buf . array ( ) ;
2016-03-27 17:44:20 +02:00
}
2015-01-07 14:00:18 +01:00
}
2015-05-12 06:28:11 +02:00
@Override
2015-04-01 18:34:52 +02:00
public byte [ ] encodeFirmwareVersionReq ( ) {
2015-06-19 23:54:31 +02:00
return encodeSimpleMessage ( ENDPOINT_FIRMWAREVERSION , FIRMWAREVERSION_GETVERSION ) ;
2015-03-25 22:23:45 +01:00
}
2015-05-12 06:28:11 +02:00
@Override
2015-04-01 18:34:52 +02:00
public byte [ ] encodeAppInfoReq ( ) {
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2015-12-17 23:09:52 +01:00
return null ; // can't do this on 3.x :(
}
2015-06-19 23:54:31 +02:00
return encodeSimpleMessage ( ENDPOINT_APPMANAGER , APPMANAGER_GETUUIDS ) ;
2015-03-22 00:34:54 +01:00
}
2015-05-18 22:40:39 +02:00
@Override
2015-09-13 21:44:26 +02:00
public byte [ ] encodeAppStart ( UUID uuid , boolean start ) {
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_APPRUNSTATE = 17 ;
2015-08-11 13:55:35 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_APPRUNSTATE ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_APPRUNSTATE ) ;
buf . putShort ( ENDPOINT_APPRUNSTATE ) ;
2015-09-13 21:44:26 +02:00
buf . put ( start ? APPRUNSTATE_START : APPRUNSTATE_STOP ) ;
2015-08-11 13:55:35 +02:00
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
return buf . array ( ) ;
} else {
ArrayList < Pair < Integer , Object > > pairs = new ArrayList < > ( ) ;
2015-09-13 21:44:26 +02:00
int param = start ? 1 : 0 ;
pairs . add ( new Pair < > ( 1 , ( Object ) param ) ) ;
2015-08-11 13:55:35 +02:00
return encodeApplicationMessagePush ( ENDPOINT_LAUNCHER , uuid , pairs ) ;
}
2015-05-18 22:40:39 +02:00
}
2015-05-12 06:28:11 +02:00
@Override
2015-05-18 20:56:19 +02:00
public byte [ ] encodeAppDelete ( UUID uuid ) {
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2016-01-27 23:00:44 +01:00
if ( UUID_PEBBLE_HEALTH . equals ( uuid ) ) {
return encodeActivateHealth ( false ) ;
}
2016-11-15 12:17:51 +01:00
if ( UUID_WORKOUT . equals ( uuid ) ) {
return encodeActivateHRM ( false ) ;
}
2016-12-30 20:14:13 +01:00
if ( UUID_WEATHER . equals ( uuid ) ) { //TODO: probably it wasn't present in firmware 3
return encodeActivateWeather ( false ) ;
}
2015-08-21 14:29:12 +02:00
return encodeBlobdb ( uuid , BLOBDB_DELETE , BLOBDB_APP , null ) ;
2015-08-17 13:07:34 +02:00
} else {
2017-01-01 13:55:07 +01:00
final short LENGTH_REMOVEAPP_2X = 17 ;
2015-08-21 14:29:12 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_REMOVEAPP_2X ) ;
2015-08-17 13:07:34 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_REMOVEAPP_2X ) ;
buf . putShort ( ENDPOINT_APPMANAGER ) ;
buf . put ( APPMANAGER_REMOVEAPP ) ;
2015-08-21 14:29:12 +02:00
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
return buf . array ( ) ;
2015-08-17 13:07:34 +02:00
}
2015-03-26 18:11:47 +01:00
}
2015-07-24 01:34:50 +02:00
private byte [ ] encodePhoneVersion2x ( byte os ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_PHONEVERSION = 17 ;
2015-03-26 18:11:47 +01:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_PHONEVERSION ) ;
2015-01-20 23:51:55 +01:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2015-03-26 18:11:47 +01:00
buf . putShort ( LENGTH_PHONEVERSION ) ;
2015-01-20 23:51:55 +01:00
buf . putShort ( ENDPOINT_PHONEVERSION ) ;
2015-02-06 23:28:24 +01:00
buf . put ( ( byte ) 0x01 ) ;
buf . putInt ( - 1 ) ; //0xffffffff
2015-01-20 23:51:55 +01:00
if ( os = = PHONEVERSION_REMOTE_OS_ANDROID ) {
buf . putInt ( PHONEVERSION_SESSION_CAPS_GAMMARAY ) ;
} else {
buf . putInt ( 0 ) ;
}
buf . putInt ( PHONEVERSION_REMOTE_CAPS_SMS | PHONEVERSION_REMOTE_CAPS_TELEPHONY | os ) ;
2015-02-08 23:53:40 +01:00
buf . put ( PHONEVERSION_APPVERSION_MAGIC ) ;
buf . put ( PHONEVERSION_APPVERSION_MAJOR ) ;
buf . put ( PHONEVERSION_APPVERSION_MINOR ) ;
buf . put ( PHONEVERSION_APPVERSION_PATCH ) ;
2015-01-20 23:51:55 +01:00
return buf . array ( ) ;
}
2015-07-24 01:34:50 +02:00
private byte [ ] encodePhoneVersion3x ( byte os ) {
2015-12-20 00:40:38 +01:00
final short LENGTH_PHONEVERSION3X = 25 ;
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_PHONEVERSION3X ) ;
2015-07-24 01:34:50 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2015-12-20 00:40:38 +01:00
buf . putShort ( LENGTH_PHONEVERSION3X ) ;
2015-07-24 01:34:50 +02:00
buf . putShort ( ENDPOINT_PHONEVERSION ) ;
buf . put ( ( byte ) 0x01 ) ;
buf . putInt ( - 1 ) ; //0xffffffff
buf . putInt ( 0 ) ;
buf . putInt ( os ) ;
buf . put ( PHONEVERSION_APPVERSION_MAGIC ) ;
2016-11-10 11:05:32 +01:00
buf . put ( ( byte ) 4 ) ; // major
buf . put ( ( byte ) 1 ) ; // minor
buf . put ( ( byte ) 1 ) ; // patch
2015-12-20 00:40:38 +01:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2016-11-10 11:05:32 +01:00
buf . putLong ( 0x00000000000029af ) ; //flags
2015-07-24 01:34:50 +02:00
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
private byte [ ] encodePhoneVersion ( byte os ) {
2015-07-24 01:34:50 +02:00
return encodePhoneVersion3x ( os ) ;
}
2015-05-18 22:40:39 +02:00
@Override
public byte [ ] encodeReboot ( ) {
2015-06-19 23:54:31 +02:00
return encodeSimpleMessage ( ENDPOINT_RESET , RESET_REBOOT ) ;
2015-05-18 22:40:39 +02:00
}
2015-06-24 00:23:38 +02:00
@Override
public byte [ ] encodeScreenshotReq ( ) {
2015-06-24 23:55:51 +02:00
return encodeSimpleMessage ( ENDPOINT_SCREENSHOT , SCREENSHOT_TAKE ) ;
2015-06-24 00:23:38 +02:00
}
2016-06-12 01:20:12 +02:00
@Override
public byte [ ] encodeAppReorder ( UUID [ ] uuids ) {
2016-06-09 19:55:36 +02:00
int length = 2 + uuids . length * LENGTH_UUID ;
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) length ) ;
buf . putShort ( ENDPOINT_APPREORDER ) ;
buf . put ( ( byte ) 0x01 ) ;
buf . put ( ( byte ) uuids . length ) ;
for ( UUID uuid : uuids ) {
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
}
return buf . array ( ) ;
}
2016-06-24 10:25:08 +02:00
@Override
public byte [ ] encodeSetCannedMessages ( CannedMessagesSpec cannedMessagesSpec ) {
if ( cannedMessagesSpec . cannedMessages = = null | | cannedMessagesSpec . cannedMessages . length = = 0 ) {
return null ;
}
String blobDBKey ;
switch ( cannedMessagesSpec . type ) {
case CannedMessagesSpec . TYPE_MISSEDCALLS :
blobDBKey = "com.pebble.android.phone" ;
break ;
case CannedMessagesSpec . TYPE_NEWSMS :
blobDBKey = "com.pebble.sendText" ;
break ;
default :
return null ;
}
int replies_length = - 1 ;
for ( String reply : cannedMessagesSpec . cannedMessages ) {
replies_length + = reply . getBytes ( ) . length + 1 ;
}
ByteBuffer buf = ByteBuffer . allocate ( 12 + replies_length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . putInt ( 0x00000000 ) ; // unknown
2016-12-01 20:18:36 +01:00
buf . put ( ( byte ) 0x00 ) ; // attributes count?
2016-06-24 10:25:08 +02:00
buf . put ( ( byte ) 0x01 ) ; // actions count?
// action
buf . put ( ( byte ) 0x00 ) ; // action id
buf . put ( ( byte ) 0x03 ) ; // action type = reply
buf . put ( ( byte ) 0x01 ) ; // attributes count
buf . put ( ( byte ) 0x08 ) ; // canned messages
buf . putShort ( ( short ) replies_length ) ;
for ( int i = 0 ; i < cannedMessagesSpec . cannedMessages . length - 1 ; i + + ) {
buf . put ( cannedMessagesSpec . cannedMessages [ i ] . getBytes ( ) ) ;
buf . put ( ( byte ) 0x00 ) ;
}
// last one must not be zero terminated, else we get an additional empty reply
buf . put ( cannedMessagesSpec . cannedMessages [ cannedMessagesSpec . cannedMessages . length - 1 ] . getBytes ( ) ) ;
return encodeBlobdb ( blobDBKey , BLOBDB_INSERT , BLOBDB_CANNED_MESSAGES , buf . array ( ) ) ;
}
2015-04-06 20:58:35 +02:00
/* pebble specific install methods */
2016-12-04 17:21:29 +01:00
byte [ ] encodeUploadStart ( byte type , int app_id , int size , String filename ) {
2015-08-16 00:32:36 +02:00
short length ;
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 & & ( type ! = PUTBYTES_TYPE_FILE ) ) {
2017-01-01 13:55:07 +01:00
length = ( short ) 10 ;
2015-08-16 00:32:36 +02:00
type | = 0b10000000 ;
} else {
2017-01-01 13:55:07 +01:00
length = ( short ) 7 ;
2015-08-16 00:32:36 +02:00
}
2015-10-06 16:56:01 +02:00
if ( type = = PUTBYTES_TYPE_FILE & & filename ! = null ) {
length + = filename . getBytes ( ) . length + 1 ;
}
2015-08-16 00:32:36 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
2015-04-06 20:58:35 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2015-08-16 00:32:36 +02:00
buf . putShort ( length ) ;
2015-04-06 20:58:35 +02:00
buf . putShort ( ENDPOINT_PUTBYTES ) ;
buf . put ( PUTBYTES_INIT ) ;
buf . putInt ( size ) ;
buf . put ( type ) ;
2015-10-06 16:56:01 +02:00
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 & & ( type ! = PUTBYTES_TYPE_FILE ) ) {
2015-08-16 00:32:36 +02:00
buf . putInt ( app_id ) ;
} else {
// slot
buf . put ( ( byte ) app_id ) ;
}
2015-10-06 16:56:01 +02:00
if ( type = = PUTBYTES_TYPE_FILE & & filename ! = null ) {
buf . put ( filename . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
2015-04-06 20:58:35 +02:00
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeUploadChunk ( int token , byte [ ] buffer , int size ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_UPLOADCHUNK = 9 ;
2015-04-06 20:58:35 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_UPLOADCHUNK + size ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) ( LENGTH_UPLOADCHUNK + size ) ) ;
buf . putShort ( ENDPOINT_PUTBYTES ) ;
buf . put ( PUTBYTES_SEND ) ;
buf . putInt ( token ) ;
buf . putInt ( size ) ;
buf . put ( buffer , 0 , size ) ;
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeUploadCommit ( int token , int crc ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_UPLOADCOMMIT = 9 ;
2015-04-06 23:37:17 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_UPLOADCOMMIT ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_UPLOADCOMMIT ) ;
buf . putShort ( ENDPOINT_PUTBYTES ) ;
buf . put ( PUTBYTES_COMMIT ) ;
buf . putInt ( token ) ;
buf . putInt ( crc ) ;
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeUploadComplete ( int token ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_UPLOADCOMPLETE = 5 ;
2015-04-06 23:37:17 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_UPLOADCOMPLETE ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_UPLOADCOMPLETE ) ;
buf . putShort ( ENDPOINT_PUTBYTES ) ;
buf . put ( PUTBYTES_COMPLETE ) ;
buf . putInt ( token ) ;
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeUploadCancel ( int token ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_UPLOADCANCEL = 5 ;
2015-04-09 18:48:52 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_UPLOADCANCEL ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_UPLOADCANCEL ) ;
buf . putShort ( ENDPOINT_PUTBYTES ) ;
buf . put ( PUTBYTES_ABORT ) ;
buf . putInt ( token ) ;
return buf . array ( ) ;
}
2015-04-17 12:23:19 +02:00
private byte [ ] encodeSystemMessage ( byte systemMessage ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_SYSTEMMESSAGE = 2 ;
2015-04-17 12:23:19 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_SYSTEMMESSAGE ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_SYSTEMMESSAGE ) ;
buf . putShort ( ENDPOINT_SYSTEMMESSAGE ) ;
buf . put ( ( byte ) 0 ) ;
buf . put ( systemMessage ) ;
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeInstallFirmwareStart ( ) {
2015-04-17 12:23:19 +02:00
return encodeSystemMessage ( SYSTEMMESSAGE_FIRMWARESTART ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeInstallFirmwareComplete ( ) {
2015-04-17 12:23:19 +02:00
return encodeSystemMessage ( SYSTEMMESSAGE_FIRMWARECOMPLETE ) ;
}
public byte [ ] encodeInstallFirmwareError ( ) {
return encodeSystemMessage ( SYSTEMMESSAGE_FIRMWAREFAIL ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeAppRefresh ( int index ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_REFRESHAPP = 5 ;
2015-04-07 19:33:23 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_REFRESHAPP ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_REFRESHAPP ) ;
buf . putShort ( ENDPOINT_APPMANAGER ) ;
buf . put ( APPMANAGER_REFRESHAPP ) ;
buf . putInt ( index ) ;
return buf . array ( ) ;
}
2015-04-06 23:37:17 +02:00
2016-12-04 17:21:29 +01:00
private byte [ ] encodeDatalog ( byte handle , byte reply ) {
2015-04-26 01:43:24 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + 2 ) ;
2015-04-26 00:53:48 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2015-04-26 01:43:24 +02:00
buf . putShort ( ( short ) 2 ) ;
2015-04-26 00:53:48 +02:00
buf . putShort ( ENDPOINT_DATALOG ) ;
2015-04-26 01:43:24 +02:00
buf . put ( reply ) ;
buf . put ( handle ) ;
2015-04-26 00:53:48 +02:00
return buf . array ( ) ;
}
2015-05-21 18:17:39 +02:00
byte [ ] encodeApplicationMessageAck ( UUID uuid , byte id ) {
2017-01-13 08:16:33 +01:00
if ( uuid = = null ) {
uuid = currentRunningApp ;
}
2015-05-21 18:17:39 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + 18 ) ; // +ACK
2015-05-05 14:41:10 +02:00
buf . order ( ByteOrder . BIG_ENDIAN ) ;
2015-05-06 23:45:25 +02:00
buf . putShort ( ( short ) 18 ) ;
2015-05-05 14:41:10 +02:00
buf . putShort ( ENDPOINT_APPLICATIONMESSAGE ) ;
2015-05-06 23:45:25 +02:00
buf . put ( APPLICATIONMESSAGE_ACK ) ;
2015-05-21 18:17:39 +02:00
buf . put ( id ) ;
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
2017-01-11 23:42:40 +01:00
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
2015-05-11 23:30:38 +02:00
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
private byte [ ] encodePing ( byte command , int cookie ) {
2017-01-01 13:55:07 +01:00
final short LENGTH_PING = 5 ;
2015-08-09 21:42:27 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_PING ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_PING ) ;
buf . putShort ( ENDPOINT_PING ) ;
buf . put ( command ) ;
buf . putInt ( cookie ) ;
return buf . array ( ) ;
}
2015-05-11 23:30:38 +02:00
2016-12-04 16:54:47 +01:00
byte [ ] encodeEnableAppLogs ( boolean enable ) {
final short LENGTH_APPLOGS = 1 ;
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + LENGTH_APPLOGS ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( LENGTH_APPLOGS ) ;
buf . putShort ( ENDPOINT_APPLOGS ) ;
buf . put ( ( byte ) ( enable ? 1 : 0 ) ) ;
return buf . array ( ) ;
}
2015-05-21 18:17:39 +02:00
private ArrayList < Pair < Integer , Object > > decodeDict ( ByteBuffer buf ) {
2015-11-23 22:46:12 +01:00
ArrayList < Pair < Integer , Object > > dict = new ArrayList < > ( ) ;
2015-05-21 18:17:39 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
byte dictSize = buf . get ( ) ;
while ( dictSize - - > 0 ) {
Integer key = buf . getInt ( ) ;
byte type = buf . get ( ) ;
2015-09-17 19:21:22 +02:00
short length = buf . getShort ( ) ;
2015-05-21 18:17:39 +02:00
switch ( type ) {
2015-09-17 19:21:22 +02:00
case TYPE_INT :
case TYPE_UINT :
2017-01-01 17:58:34 +01:00
if ( length = = 1 ) {
dict . add ( new Pair < Integer , Object > ( key , buf . get ( ) ) ) ;
} else if ( length = = 2 ) {
dict . add ( new Pair < Integer , Object > ( key , buf . getShort ( ) ) ) ;
} else {
dict . add ( new Pair < Integer , Object > ( key , buf . getInt ( ) ) ) ;
}
2015-05-21 18:17:39 +02:00
break ;
case TYPE_CSTRING :
case TYPE_BYTEARRAY :
byte [ ] bytes = new byte [ length ] ;
buf . get ( bytes ) ;
if ( type = = TYPE_BYTEARRAY ) {
dict . add ( new Pair < Integer , Object > ( key , bytes ) ) ;
} else {
2015-12-13 23:59:26 +01:00
dict . add ( new Pair < Integer , Object > ( key , new String ( bytes ) ) ) ;
2015-05-21 18:17:39 +02:00
}
break ;
default :
}
}
return dict ;
}
2015-09-17 19:21:22 +02:00
private GBDeviceEvent [ ] decodeDictToJSONAppMessage ( UUID uuid , ByteBuffer buf ) throws JSONException {
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
byte dictSize = buf . get ( ) ;
if ( dictSize = = 0 ) {
LOG . info ( "dict size is 0, ignoring" ) ;
return null ;
}
JSONArray jsonArray = new JSONArray ( ) ;
while ( dictSize - - > 0 ) {
JSONObject jsonObject = new JSONObject ( ) ;
Integer key = buf . getInt ( ) ;
byte type = buf . get ( ) ;
short length = buf . getShort ( ) ;
jsonObject . put ( "key" , key ) ;
2017-01-11 22:39:08 +01:00
if ( type = = TYPE_CSTRING ) {
length - - ;
}
2015-09-17 19:21:22 +02:00
jsonObject . put ( "length" , length ) ;
switch ( type ) {
case TYPE_UINT :
jsonObject . put ( "type" , "uint" ) ;
if ( length = = 1 ) {
jsonObject . put ( "value" , buf . get ( ) & 0xff ) ;
} else if ( length = = 2 ) {
jsonObject . put ( "value" , buf . getShort ( ) & 0xffff ) ;
} else {
jsonObject . put ( "value" , buf . getInt ( ) & 0xffffffffL ) ;
}
break ;
case TYPE_INT :
jsonObject . put ( "type" , "int" ) ;
if ( length = = 1 ) {
jsonObject . put ( "value" , buf . get ( ) ) ;
} else if ( length = = 2 ) {
jsonObject . put ( "value" , buf . getShort ( ) ) ;
} else {
jsonObject . put ( "value" , buf . getInt ( ) ) ;
}
break ;
case TYPE_BYTEARRAY :
case TYPE_CSTRING :
byte [ ] bytes = new byte [ length ] ;
buf . get ( bytes ) ;
if ( type = = TYPE_BYTEARRAY ) {
jsonObject . put ( "type" , "bytes" ) ;
2017-01-10 22:30:55 +01:00
jsonObject . put ( "value" , new String ( Base64 . encode ( bytes , Base64 . NO_WRAP ) ) ) ;
2015-09-17 19:21:22 +02:00
} else {
jsonObject . put ( "type" , "string" ) ;
2015-12-13 23:59:26 +01:00
jsonObject . put ( "value" , new String ( bytes ) ) ;
2017-01-11 22:39:08 +01:00
buf . get ( ) ; // skip null-termination;
2015-09-17 19:21:22 +02:00
}
break ;
default :
LOG . info ( "unknown type in appmessage, ignoring" ) ;
return null ;
}
jsonArray . put ( jsonObject ) ;
}
2017-02-10 23:06:34 +01:00
GBDeviceEventSendBytes sendBytesAck = null ;
if ( mAlwaysACKPebbleKit ) {
// this is a hack we send an ack to the Pebble immediately because somebody said it helps some PebbleKit apps :P
sendBytesAck = new GBDeviceEventSendBytes ( ) ;
sendBytesAck . encodedBytes = encodeApplicationMessageAck ( uuid , last_id ) ;
}
2015-09-17 19:21:22 +02:00
GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage ( ) ;
appMessage . appUUID = uuid ;
appMessage . id = last_id & 0xff ;
appMessage . message = jsonArray . toString ( ) ;
2017-02-10 23:06:34 +01:00
return new GBDeviceEvent [ ] { appMessage , sendBytesAck } ;
2015-09-17 19:21:22 +02:00
}
2015-05-21 18:17:39 +02:00
byte [ ] encodeApplicationMessagePush ( short endpoint , UUID uuid , ArrayList < Pair < Integer , Object > > pairs ) {
2015-08-14 12:50:44 +02:00
int length = LENGTH_UUID + 3 ; // UUID + (PUSH + id + length of dict)
2015-05-11 23:30:38 +02:00
for ( Pair < Integer , Object > pair : pairs ) {
2017-01-24 11:07:00 +01:00
if ( pair . first = = null | | pair . second = = null )
continue ;
2015-05-11 23:30:38 +02:00
length + = 7 ; // key + type + length
if ( pair . second instanceof Integer ) {
length + = 4 ;
2015-09-19 15:32:09 +02:00
} else if ( pair . second instanceof Short ) {
2015-09-18 00:03:34 +02:00
length + = 2 ;
2015-09-19 15:32:09 +02:00
} else if ( pair . second instanceof Byte ) {
2015-09-18 00:03:34 +02:00
length + = 1 ;
2015-05-11 23:30:38 +02:00
} else if ( pair . second instanceof String ) {
2015-09-19 15:32:09 +02:00
length + = ( ( String ) pair . second ) . getBytes ( ) . length + 1 ;
2015-09-13 18:37:59 +02:00
} else if ( pair . second instanceof byte [ ] ) {
length + = ( ( byte [ ] ) pair . second ) . length ;
2016-12-04 16:54:47 +01:00
} else {
2016-10-28 00:32:45 +02:00
LOG . warn ( "unknown type: " + pair . second . getClass ( ) . toString ( ) ) ;
2015-05-11 23:30:38 +02:00
}
}
2015-09-13 18:37:59 +02:00
2015-05-11 23:30:38 +02:00
ByteBuffer buf = ByteBuffer . allocate ( LENGTH_PREFIX + length ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
buf . putShort ( ( short ) length ) ;
buf . putShort ( endpoint ) ; // 48 or 49
2015-05-05 14:41:10 +02:00
buf . put ( APPLICATIONMESSAGE_PUSH ) ;
buf . put ( + + last_id ) ;
2015-05-12 11:06:22 +02:00
buf . putLong ( uuid . getMostSignificantBits ( ) ) ;
buf . putLong ( uuid . getLeastSignificantBits ( ) ) ;
2015-05-11 23:30:38 +02:00
buf . put ( ( byte ) pairs . size ( ) ) ;
2015-09-18 00:03:34 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2015-05-11 23:30:38 +02:00
for ( Pair < Integer , Object > pair : pairs ) {
2017-01-20 19:17:00 +01:00
if ( pair . first = = null | | pair . second = = null )
continue ;
2015-05-11 23:30:38 +02:00
buf . putInt ( pair . first ) ;
if ( pair . second instanceof Integer ) {
2015-09-17 19:21:22 +02:00
buf . put ( TYPE_INT ) ;
2015-09-18 00:03:34 +02:00
buf . putShort ( ( short ) 4 ) ; // length
2015-05-11 23:30:38 +02:00
buf . putInt ( ( int ) pair . second ) ;
2015-09-18 00:03:34 +02:00
} else if ( pair . second instanceof Short ) {
buf . put ( TYPE_INT ) ;
buf . putShort ( ( short ) 2 ) ; // length
buf . putShort ( ( short ) pair . second ) ;
} else if ( pair . second instanceof Byte ) {
buf . put ( TYPE_INT ) ;
buf . putShort ( ( short ) 1 ) ; // length
buf . put ( ( byte ) pair . second ) ;
2015-05-11 23:30:38 +02:00
} else if ( pair . second instanceof String ) {
2015-09-13 18:37:59 +02:00
String str = ( String ) pair . second ;
2015-05-21 18:17:39 +02:00
buf . put ( TYPE_CSTRING ) ;
2015-09-19 15:32:09 +02:00
buf . putShort ( ( short ) ( str . getBytes ( ) . length + 1 ) ) ;
2015-09-13 18:37:59 +02:00
buf . put ( str . getBytes ( ) ) ;
2015-05-11 23:30:38 +02:00
buf . put ( ( byte ) 0 ) ;
2015-09-13 18:37:59 +02:00
} else if ( pair . second instanceof byte [ ] ) {
byte [ ] bytes = ( byte [ ] ) pair . second ;
buf . put ( TYPE_BYTEARRAY ) ;
buf . putShort ( ( short ) bytes . length ) ;
buf . put ( bytes ) ;
2015-05-11 23:30:38 +02:00
}
}
2015-05-05 14:41:10 +02:00
return buf . array ( ) ;
}
2016-12-04 17:21:29 +01:00
byte [ ] encodeApplicationMessageFromJSON ( UUID uuid , JSONArray jsonArray ) {
2015-09-13 18:20:15 +02:00
ArrayList < Pair < Integer , Object > > pairs = new ArrayList < > ( ) ;
for ( int i = 0 ; i < jsonArray . length ( ) ; i + + ) {
try {
JSONObject jsonObject = ( JSONObject ) jsonArray . get ( i ) ;
String type = ( String ) jsonObject . get ( "type" ) ;
2017-01-18 22:10:10 +01:00
int key = jsonObject . getInt ( "key" ) ;
int length = jsonObject . getInt ( "length" ) ;
2015-09-18 00:03:34 +02:00
switch ( type ) {
case "uint" :
case "int" :
if ( length = = 1 ) {
pairs . add ( new Pair < > ( key , ( Object ) ( byte ) jsonObject . getInt ( "value" ) ) ) ;
} else if ( length = = 2 ) {
pairs . add ( new Pair < > ( key , ( Object ) ( short ) jsonObject . getInt ( "value" ) ) ) ;
} else {
if ( type . equals ( "uint" ) ) {
pairs . add ( new Pair < > ( key , ( Object ) ( int ) ( jsonObject . getInt ( "value" ) & 0xffffffffL ) ) ) ;
} else {
pairs . add ( new Pair < > ( key , ( Object ) jsonObject . getInt ( "value" ) ) ) ;
}
}
break ;
case "string" :
pairs . add ( new Pair < > ( key , ( Object ) jsonObject . getString ( "value" ) ) ) ;
break ;
case "bytes" :
byte [ ] bytes = Base64 . decode ( jsonObject . getString ( "value" ) , Base64 . NO_WRAP ) ;
pairs . add ( new Pair < > ( key , ( Object ) bytes ) ) ;
break ;
2015-09-13 18:20:15 +02:00
}
} catch ( JSONException e ) {
return null ;
}
}
return encodeApplicationMessagePush ( ENDPOINT_APPLICATIONMESSAGE , uuid , pairs ) ;
}
2016-12-04 17:21:29 +01:00
private byte reverseBits ( byte in ) {
2015-06-25 23:34:50 +02:00
byte out = 0 ;
for ( int i = 0 ; i < 8 ; i + + ) {
byte bit = ( byte ) ( in & 1 ) ;
out = ( byte ) ( ( out < < 1 ) | bit ) ;
in = ( byte ) ( in > > 1 ) ;
}
return out ;
}
2015-05-12 11:06:22 +02:00
2015-08-26 23:17:32 +02:00
private GBDeviceEventScreenshot decodeScreenshot ( ByteBuffer buf , int length ) {
2015-06-24 23:55:51 +02:00
if ( mDevEventScreenshot = = null ) {
byte result = buf . get ( ) ;
mDevEventScreenshot = new GBDeviceEventScreenshot ( ) ;
int version = buf . getInt ( ) ;
2015-08-22 00:14:14 +02:00
if ( result ! = 0 ) {
2015-06-24 23:55:51 +02:00
return null ;
}
mDevEventScreenshot . width = buf . getInt ( ) ;
mDevEventScreenshot . height = buf . getInt ( ) ;
2015-08-22 00:14:14 +02:00
if ( version = = 1 ) {
mDevEventScreenshot . bpp = 1 ;
mDevEventScreenshot . clut = clut_pebble ;
} else {
mDevEventScreenshot . bpp = 8 ;
mDevEventScreenshot . clut = clut_pebbletime ;
2015-06-24 23:55:51 +02:00
}
2015-08-22 00:14:14 +02:00
mScreenshotRemaining = ( mDevEventScreenshot . width * mDevEventScreenshot . height * mDevEventScreenshot . bpp ) / 8 ;
2015-06-24 23:55:51 +02:00
mDevEventScreenshot . data = new byte [ mScreenshotRemaining ] ;
length - = 13 ;
}
if ( mScreenshotRemaining = = - 1 ) {
return null ;
}
2015-06-25 23:34:50 +02:00
for ( int i = 0 ; i < length ; i + + ) {
2015-08-22 00:14:14 +02:00
byte corrected = buf . get ( ) ;
if ( mDevEventScreenshot . bpp = = 1 ) {
corrected = reverseBits ( corrected ) ;
} else {
corrected = ( byte ) ( corrected & 0b00111111 ) ;
}
2015-06-25 23:34:50 +02:00
mDevEventScreenshot . data [ mDevEventScreenshot . data . length - mScreenshotRemaining + i ] = corrected ;
}
2015-06-24 23:55:51 +02:00
mScreenshotRemaining - = length ;
LOG . info ( "Screenshot remaining bytes " + mScreenshotRemaining ) ;
if ( mScreenshotRemaining = = 0 ) {
mScreenshotRemaining = - 1 ;
LOG . info ( "Got screenshot : " + mDevEventScreenshot . width + "x" + mDevEventScreenshot . height + " " + "pixels" ) ;
GBDeviceEventScreenshot devEventScreenshot = mDevEventScreenshot ;
mDevEventScreenshot = null ;
return devEventScreenshot ;
}
return null ;
}
2015-12-20 21:23:39 +01:00
private GBDeviceEvent [ ] decodeAction ( ByteBuffer buf ) {
2015-08-27 15:02:29 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
byte command = buf . get ( ) ;
2015-08-27 18:01:19 +02:00
if ( command = = NOTIFICATIONACTION_INVOKE ) {
2015-12-20 21:23:39 +01:00
int id ;
2016-12-04 17:21:29 +01:00
UUID uuid = new UUID ( 0 , 0 ) ;
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2016-12-04 17:21:29 +01:00
uuid = getUUID ( buf ) ;
id = ( int ) ( uuid . getLeastSignificantBits ( ) & 0xffffffffL ) ;
2015-12-20 21:23:39 +01:00
} else {
id = buf . getInt ( ) ;
}
2015-08-27 18:01:19 +02:00
byte action = buf . get ( ) ;
2016-06-21 00:31:53 +02:00
if ( action > = 0x00 & & action < = 0x05 ) {
2015-12-13 22:43:53 +01:00
GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl ( ) ;
devEvtNotificationControl . handle = id ;
2015-09-01 21:58:36 +02:00
String caption = "undefined" ;
int icon_id = 1 ;
2015-12-20 21:23:39 +01:00
boolean needsAck2x = true ;
2015-09-01 21:58:36 +02:00
switch ( action ) {
case 0x01 :
2015-12-13 22:43:53 +01:00
devEvtNotificationControl . event = GBDeviceEventNotificationControl . Event . OPEN ;
2015-09-01 21:58:36 +02:00
caption = "Opened" ;
2015-09-13 15:21:07 +02:00
icon_id = PebbleIconID . DURING_PHONE_CALL ;
2015-09-01 21:58:36 +02:00
break ;
case 0x02 :
2015-12-13 22:43:53 +01:00
devEvtNotificationControl . event = GBDeviceEventNotificationControl . Event . DISMISS ;
2015-09-01 21:58:36 +02:00
caption = "Dismissed" ;
2015-09-13 15:21:07 +02:00
icon_id = PebbleIconID . RESULT_DISMISSED ;
2015-12-20 21:23:39 +01:00
needsAck2x = false ;
2015-09-01 21:58:36 +02:00
break ;
case 0x03 :
2015-12-13 22:43:53 +01:00
devEvtNotificationControl . event = GBDeviceEventNotificationControl . Event . DISMISS_ALL ;
2015-09-01 21:58:36 +02:00
caption = "All dismissed" ;
2015-09-13 15:21:07 +02:00
icon_id = PebbleIconID . RESULT_DISMISSED ;
2015-12-20 21:23:39 +01:00
needsAck2x = false ;
2015-09-01 21:58:36 +02:00
break ;
2015-09-25 00:53:40 +02:00
case 0x04 :
2015-12-13 22:43:53 +01:00
devEvtNotificationControl . event = GBDeviceEventNotificationControl . Event . MUTE ;
2015-09-25 00:53:40 +02:00
caption = "Muted" ;
icon_id = PebbleIconID . RESULT_MUTE ;
break ;
2015-12-13 12:03:57 +01:00
case 0x05 :
2016-06-21 00:31:53 +02:00
case 0x00 :
2015-12-31 01:43:00 +01:00
boolean failed = true ;
2015-12-13 22:43:53 +01:00
byte attribute_count = buf . get ( ) ;
if ( attribute_count > 0 ) {
byte attribute = buf . get ( ) ;
if ( attribute = = 0x01 ) { // reply string is in attribute 0x01
short length = buf . getShort ( ) ;
if ( length > 64 ) length = 64 ;
byte [ ] reply = new byte [ length ] ;
buf . get ( reply ) ;
2016-06-21 00:31:53 +02:00
devEvtNotificationControl . phoneNumber = null ;
if ( buf . remaining ( ) > 1 & & buf . get ( ) = = 0x0c ) {
short phoneNumberLength = buf . getShort ( ) ;
byte [ ] phoneNumberBytes = new byte [ phoneNumberLength ] ;
buf . get ( phoneNumberBytes ) ;
devEvtNotificationControl . phoneNumber = new String ( phoneNumberBytes ) ;
}
2016-02-03 23:27:35 +01:00
devEvtNotificationControl . event = GBDeviceEventNotificationControl . Event . REPLY ;
devEvtNotificationControl . reply = new String ( reply ) ;
caption = "SENT" ;
icon_id = PebbleIconID . RESULT_SENT ;
failed = false ;
2015-12-13 22:43:53 +01:00
}
2015-12-31 01:43:00 +01:00
}
if ( failed ) {
2015-12-15 00:26:06 +01:00
caption = "FAILED" ;
icon_id = PebbleIconID . RESULT_FAILED ;
2015-12-13 22:43:53 +01:00
devEvtNotificationControl = null ; // error
}
2015-12-13 12:03:57 +01:00
break ;
2015-09-01 21:58:36 +02:00
}
2015-12-20 21:23:39 +01:00
GBDeviceEventSendBytes sendBytesAck = null ;
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 | | needsAck2x ) {
2015-12-20 21:23:39 +01:00
sendBytesAck = new GBDeviceEventSendBytes ( ) ;
2016-06-16 00:24:27 +02:00
if ( mFwMajor > = 3 ) {
2016-12-04 17:21:29 +01:00
sendBytesAck . encodedBytes = encodeActionResponse ( uuid , icon_id , caption ) ;
2015-12-20 21:23:39 +01:00
} else {
sendBytesAck . encodedBytes = encodeActionResponse2x ( id , action , 6 , caption ) ;
}
}
2015-12-13 22:43:53 +01:00
return new GBDeviceEvent [ ] { sendBytesAck , devEvtNotificationControl } ;
2015-07-21 01:33:13 +02:00
}
2015-08-27 18:01:19 +02:00
LOG . info ( "unexpected action: " + action ) ;
2015-07-21 01:33:13 +02:00
}
return null ;
}
2015-08-26 23:17:32 +02:00
private GBDeviceEventSendBytes decodePing ( ByteBuffer buf ) {
2015-08-09 21:42:27 +02:00
byte command = buf . get ( ) ;
if ( command = = PING_PING ) {
int cookie = buf . getInt ( ) ;
LOG . info ( "Received PING - will reply" ) ;
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes ( ) ;
sendBytes . encodedBytes = encodePing ( PING_PONG , cookie ) ;
return sendBytes ;
}
return null ;
}
2016-12-04 16:54:47 +01:00
private void decodeAppLogs ( ByteBuffer buf ) {
2016-12-04 17:21:29 +01:00
UUID uuid = getUUID ( buf ) ;
2016-12-04 16:54:47 +01:00
int timestamp = buf . getInt ( ) ;
int logLevel = buf . get ( ) & 0xff ;
int messageLength = buf . get ( ) & 0xff ;
int lineNumber = buf . getShort ( ) & 0xffff ;
String fileName = getFixedString ( buf , 16 ) ;
String message = getFixedString ( buf , messageLength ) ;
2016-12-08 23:20:00 +01:00
LOG . debug ( "APP_LOGS (" + logLevel + ") from uuid " + uuid . toString ( ) + " in " + fileName + ":" + lineNumber + " " + message ) ;
2016-12-04 16:54:47 +01:00
}
2015-12-17 23:09:52 +01:00
private GBDeviceEvent decodeSystemMessage ( ByteBuffer buf ) {
buf . get ( ) ; // unknown;
byte command = buf . get ( ) ;
final String ENDPOINT_NAME = "SYSTEM MESSAGE" ;
switch ( command ) {
case SYSTEMMESSAGE_STOPRECONNECTING :
LOG . info ( ENDPOINT_NAME + ": stop reconnecting" ) ;
break ;
case SYSTEMMESSAGE_STARTRECONNECTING :
LOG . info ( ENDPOINT_NAME + ": start reconnecting" ) ;
break ;
default :
LOG . info ( ENDPOINT_NAME + ": " + command ) ;
break ;
}
return null ;
}
2016-03-08 12:02:00 +01:00
private GBDeviceEvent [ ] decodeAppRunState ( ByteBuffer buf ) {
2015-12-17 23:09:52 +01:00
byte command = buf . get ( ) ;
2016-12-04 17:21:29 +01:00
UUID uuid = getUUID ( buf ) ;
2015-12-17 23:09:52 +01:00
final String ENDPOINT_NAME = "APPRUNSTATE" ;
switch ( command ) {
case APPRUNSTATE_START :
LOG . info ( ENDPOINT_NAME + ": started " + uuid ) ;
2016-12-31 18:56:24 +01:00
currentRunningApp = uuid ;
2016-03-08 12:02:00 +01:00
AppMessageHandler handler = mAppMessageHandlers . get ( uuid ) ;
if ( handler ! = null ) {
2016-12-31 19:04:05 +01:00
return handler . onAppStart ( ) ;
2015-12-23 14:22:28 +01:00
}
2016-12-11 20:25:46 +01:00
else {
GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement ( ) ;
gbDeviceEventAppManagement . uuid = uuid ;
gbDeviceEventAppManagement . type = GBDeviceEventAppManagement . EventType . START ;
gbDeviceEventAppManagement . event = GBDeviceEventAppManagement . Event . SUCCESS ;
return new GBDeviceEvent [ ] { gbDeviceEventAppManagement } ;
}
2015-12-17 23:09:52 +01:00
case APPRUNSTATE_STOP :
LOG . info ( ENDPOINT_NAME + ": stopped " + uuid ) ;
break ;
default :
LOG . info ( ENDPOINT_NAME + ": (cmd:" + command + ")" + uuid ) ;
break ;
}
2016-05-31 14:18:45 +02:00
return new GBDeviceEvent [ ] { null } ;
2015-12-17 23:09:52 +01:00
}
2015-12-18 12:41:01 +01:00
private GBDeviceEvent decodeBlobDb ( ByteBuffer buf ) {
final String ENDPOINT_NAME = "BLOBDB" ;
final String statusString [ ] = {
"unknown" ,
"success" ,
"general failure" ,
"invalid operation" ,
"invalid database id" ,
"invalid data" ,
"key does not exist" ,
"database full" ,
"data stale" ,
} ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
short token = buf . getShort ( ) ;
byte status = buf . get ( ) ;
if ( status > = 0 & & status < statusString . length ) {
LOG . info ( ENDPOINT_NAME + ": " + statusString [ status ] + " (token " + ( token & 0xffff ) + ")" ) ;
} else {
LOG . warn ( ENDPOINT_NAME + ": unknown status " + status + " (token " + ( token & 0xffff ) + ")" ) ;
}
return null ;
}
2015-08-16 00:32:36 +02:00
private GBDeviceEventAppManagement decodeAppFetch ( ByteBuffer buf ) {
2015-08-11 13:21:29 +02:00
byte command = buf . get ( ) ;
if ( command = = 0x01 ) {
2016-12-04 17:21:29 +01:00
UUID uuid = getUUID ( buf ) ;
2015-08-17 12:55:17 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2015-08-11 13:21:29 +02:00
int app_id = buf . getInt ( ) ;
2015-08-16 00:32:36 +02:00
GBDeviceEventAppManagement fetchRequest = new GBDeviceEventAppManagement ( ) ;
fetchRequest . type = GBDeviceEventAppManagement . EventType . INSTALL ;
fetchRequest . event = GBDeviceEventAppManagement . Event . REQUEST ;
fetchRequest . token = app_id ;
fetchRequest . uuid = uuid ;
return fetchRequest ;
2015-08-11 13:21:29 +02:00
}
return null ;
}
2017-02-19 22:59:37 +01:00
private GBDeviceEvent [ ] decodeDatalog ( ByteBuffer buf , short length ) {
2015-08-26 23:17:32 +02:00
byte command = buf . get ( ) ;
byte id = buf . get ( ) ;
2017-03-28 09:51:06 +02:00
GBDeviceEvent [ ] devEvtsDataLogging = null ;
2015-08-27 15:02:29 +02:00
switch ( command ) {
case DATALOG_TIMEOUT :
LOG . info ( "DATALOG TIMEOUT. id=" + ( id & 0xff ) + " - ignoring" ) ;
return null ;
case DATALOG_SENDDATA :
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
int items_left = buf . getInt ( ) ;
int crc = buf . getInt ( ) ;
2016-02-03 23:27:35 +01:00
DatalogSession datalogSession = mDatalogSessions . get ( id ) ;
2016-02-03 20:23:56 +01:00
LOG . info ( "DATALOG SENDDATA. id=" + ( id & 0xff ) + ", items_left=" + items_left + ", total length=" + ( length - 10 ) ) ;
2016-02-03 23:27:35 +01:00
if ( datalogSession ! = null ) {
2016-02-09 01:24:22 +01:00
LOG . info ( "DATALOG UUID=" + datalogSession . uuid + ", tag=" + datalogSession . tag + datalogSession . getTaginfo ( ) + ", itemSize=" + datalogSession . itemSize + ", itemType=" + datalogSession . itemType ) ;
2017-02-19 22:59:37 +01:00
if ( ! datalogSession . uuid . equals ( UUID_ZERO ) & & datalogSession . getClass ( ) . equals ( DatalogSession . class ) & & mEnablePebbleKit ) {
2017-03-28 09:51:06 +02:00
devEvtsDataLogging = datalogSession . handleMessageForPebbleKit ( buf , length - 10 ) ;
2017-02-19 22:59:37 +01:00
} else {
2017-03-28 09:51:06 +02:00
devEvtsDataLogging = datalogSession . handleMessage ( buf , length - 10 ) ;
2017-02-19 22:59:37 +01:00
}
2016-02-03 23:27:35 +01:00
}
2015-08-27 15:02:29 +02:00
break ;
case DATALOG_OPENSESSION :
2016-12-04 17:21:29 +01:00
UUID uuid = getUUID ( buf ) ;
2015-08-27 15:02:29 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
int timestamp = buf . getInt ( ) ;
int log_tag = buf . getInt ( ) ;
byte item_type = buf . get ( ) ;
2016-04-04 23:13:57 +02:00
short item_size = buf . getShort ( ) ;
2016-02-09 01:24:22 +01:00
LOG . info ( "DATALOG OPENSESSION. id=" + ( id & 0xff ) + ", App UUID=" + uuid . toString ( ) + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size ) ;
2016-02-03 23:27:35 +01:00
if ( ! mDatalogSessions . containsKey ( id ) ) {
2017-03-31 18:23:02 +02:00
if ( uuid . equals ( UUID_ZERO ) & & log_tag = = 78 ) {
mDatalogSessions . put ( id , new DatalogSessionAnalytics ( id , uuid , timestamp , log_tag , item_type , item_size , getDevice ( ) ) ) ;
} else if ( uuid . equals ( UUID_ZERO ) & & log_tag = = 81 ) {
2017-02-20 08:47:42 +01:00
mDatalogSessions . put ( id , new DatalogSessionHealthSteps ( id , uuid , timestamp , log_tag , item_type , item_size , getDevice ( ) ) ) ;
2016-05-23 21:13:12 +02:00
} else if ( uuid . equals ( UUID_ZERO ) & & log_tag = = 83 ) {
2017-02-20 08:47:42 +01:00
mDatalogSessions . put ( id , new DatalogSessionHealthSleep ( id , uuid , timestamp , log_tag , item_type , item_size , getDevice ( ) ) ) ;
2016-05-23 21:13:12 +02:00
} else if ( uuid . equals ( UUID_ZERO ) & & log_tag = = 84 ) {
2017-02-20 08:47:42 +01:00
mDatalogSessions . put ( id , new DatalogSessionHealthOverlayData ( id , uuid , timestamp , log_tag , item_type , item_size , getDevice ( ) ) ) ;
2016-11-15 11:56:14 +01:00
} else if ( uuid . equals ( UUID_ZERO ) & & log_tag = = 85 ) {
2017-02-20 08:47:42 +01:00
mDatalogSessions . put ( id , new DatalogSessionHealthHR ( id , uuid , timestamp , log_tag , item_type , item_size , getDevice ( ) ) ) ;
2016-02-09 01:24:22 +01:00
} else {
2017-02-20 08:47:42 +01:00
mDatalogSessions . put ( id , new DatalogSession ( id , uuid , timestamp , log_tag , item_type , item_size ) ) ;
2016-02-09 00:49:42 +01:00
}
2016-02-03 23:27:35 +01:00
}
2017-03-30 23:01:40 +02:00
devEvtsDataLogging = new GBDeviceEvent [ ] { null } ;
2016-02-03 20:23:56 +01:00
break ;
case DATALOG_CLOSE :
LOG . info ( "DATALOG_CLOSE. id=" + ( id & 0xff ) ) ;
2017-02-19 22:59:37 +01:00
datalogSession = mDatalogSessions . get ( id ) ;
if ( datalogSession ! = null ) {
if ( ! datalogSession . uuid . equals ( UUID_ZERO ) & & datalogSession . getClass ( ) . equals ( DatalogSession . class ) & & mEnablePebbleKit ) {
GBDeviceEventDataLogging dataLogging = new GBDeviceEventDataLogging ( ) ;
dataLogging . command = GBDeviceEventDataLogging . COMMAND_FINISH_SESSION ;
dataLogging . appUUID = datalogSession . uuid ;
dataLogging . tag = datalogSession . tag ;
2017-03-28 09:51:06 +02:00
devEvtsDataLogging = new GBDeviceEvent [ ] { dataLogging , null } ;
2017-02-19 22:59:37 +01:00
}
2016-02-03 23:27:35 +01:00
mDatalogSessions . remove ( id ) ;
}
2015-08-27 15:02:29 +02:00
break ;
default :
LOG . info ( "unknown DATALOG command: " + ( command & 0xff ) ) ;
break ;
2015-08-26 23:17:32 +02:00
}
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes ( ) ;
2017-03-28 09:51:06 +02:00
if ( devEvtsDataLogging ! = null ) {
// append ack
2016-02-07 09:27:51 +01:00
LOG . info ( "sending ACK (0x85)" ) ;
sendBytes . encodedBytes = encodeDatalog ( id , DATALOG_ACK ) ;
2017-03-28 09:51:06 +02:00
devEvtsDataLogging [ devEvtsDataLogging . length - 1 ] = sendBytes ;
2016-02-07 09:27:51 +01:00
} else {
LOG . info ( "sending NACK (0x86)" ) ;
sendBytes . encodedBytes = encodeDatalog ( id , DATALOG_NACK ) ;
2017-03-28 09:51:06 +02:00
devEvtsDataLogging = new GBDeviceEvent [ ] { sendBytes } ;
2016-02-07 09:27:51 +01:00
}
2017-03-28 09:51:06 +02:00
return devEvtsDataLogging ;
2015-08-26 23:17:32 +02:00
}
2016-06-09 19:55:36 +02:00
private GBDeviceEvent decodeAppReorder ( ByteBuffer buf ) {
byte status = buf . get ( ) ;
if ( status = = 1 ) {
LOG . info ( "app reordering successful" ) ;
} else {
LOG . info ( "app reordering returned status " + status ) ;
}
return null ;
}
2016-12-08 16:38:31 +01:00
private GBDeviceEvent decodeVoiceControl ( ByteBuffer buf ) {
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
byte command = buf . get ( ) ;
int flags = buf . getInt ( ) ;
byte session_type = buf . get ( ) ; //0x01 dictation 0x02 command
short session_id = buf . getShort ( ) ;
//attributes
byte count = buf . get ( ) ;
byte type = buf . get ( ) ;
short length = buf . getShort ( ) ;
byte [ ] version = new byte [ 20 ] ;
buf . get ( version ) ; //it's a string like "1.2rc1"
int sample_rate = buf . getInt ( ) ;
short bit_rate = buf . getShort ( ) ;
byte bitstream_version = buf . get ( ) ;
short frame_size = buf . getShort ( ) ;
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes ( ) ;
if ( command = = 0x01 ) { //session setup
sendBytes . encodedBytes = null ;
} else if ( command = = 0x02 ) { //dictation result
sendBytes . encodedBytes = null ;
}
return sendBytes ;
}
2015-05-12 06:28:11 +02:00
@Override
2015-08-27 15:02:29 +02:00
public GBDeviceEvent [ ] decodeResponse ( byte [ ] responseData ) {
2015-01-07 14:00:18 +01:00
ByteBuffer buf = ByteBuffer . wrap ( responseData ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
short length = buf . getShort ( ) ;
short endpoint = buf . getShort ( ) ;
2015-08-27 15:02:29 +02:00
GBDeviceEvent devEvts [ ] = null ;
2016-12-04 17:21:29 +01:00
byte pebbleCmd ;
2015-01-07 14:00:18 +01:00
switch ( endpoint ) {
2015-02-12 16:00:45 +01:00
case ENDPOINT_MUSICCONTROL :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-06-23 11:54:33 +02:00
GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl ( ) ;
2015-02-12 16:00:45 +01:00
switch ( pebbleCmd ) {
case MUSICCONTROL_NEXT :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . NEXT ;
2015-02-12 16:00:45 +01:00
break ;
case MUSICCONTROL_PREVIOUS :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . PREVIOUS ;
2015-02-12 16:00:45 +01:00
break ;
case MUSICCONTROL_PLAY :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . PLAY ;
2015-02-12 16:00:45 +01:00
break ;
case MUSICCONTROL_PAUSE :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . PAUSE ;
2015-02-12 16:00:45 +01:00
break ;
case MUSICCONTROL_PLAYPAUSE :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . PLAYPAUSE ;
2015-02-12 16:00:45 +01:00
break ;
2015-04-13 22:25:23 +02:00
case MUSICCONTROL_VOLUMEUP :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . VOLUMEUP ;
2015-04-13 22:25:23 +02:00
break ;
case MUSICCONTROL_VOLUMEDOWN :
2015-06-23 11:54:33 +02:00
musicCmd . event = GBDeviceEventMusicControl . Event . VOLUMEDOWN ;
2015-04-13 22:25:23 +02:00
break ;
2015-02-12 16:00:45 +01:00
default :
break ;
}
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { musicCmd } ;
2015-01-07 14:00:18 +01:00
break ;
2015-03-07 17:44:39 +01:00
case ENDPOINT_PHONECONTROL :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-06-23 11:54:33 +02:00
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl ( ) ;
2015-03-07 17:44:39 +01:00
switch ( pebbleCmd ) {
case PHONECONTROL_HANGUP :
2015-06-23 11:54:33 +02:00
callCmd . event = GBDeviceEventCallControl . Event . END ;
2015-03-07 17:44:39 +01:00
break ;
default :
2015-06-23 11:54:33 +02:00
LOG . info ( "Unknown PHONECONTROL event" + pebbleCmd ) ;
2015-03-07 17:44:39 +01:00
break ;
}
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { callCmd } ;
2015-03-07 17:44:39 +01:00
break ;
2015-03-22 00:34:54 +01:00
case ENDPOINT_FIRMWAREVERSION :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-06-23 11:54:33 +02:00
GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo ( ) ;
2015-03-26 12:06:26 +01:00
2015-04-20 12:48:32 +02:00
buf . getInt ( ) ; // skip
2016-12-04 16:54:47 +01:00
versionCmd . fwVersion = getFixedString ( buf , 32 ) ;
2015-03-22 00:34:54 +01:00
2016-06-24 12:07:44 +02:00
mFwMajor = versionCmd . fwVersion . charAt ( 1 ) - 48 ;
LOG . info ( "Pebble firmware major detected as " + mFwMajor ) ;
2015-06-11 20:40:31 +02:00
2016-12-04 16:54:47 +01:00
byte [ ] tmp = new byte [ 9 ] ;
2015-04-20 12:48:32 +02:00
buf . get ( tmp , 0 , 9 ) ;
2016-06-15 22:53:05 +02:00
int hwRev = buf . get ( ) + 8 ;
2015-09-23 23:19:38 +02:00
if ( hwRev > = 0 & & hwRev < hwRevisions . length ) {
versionCmd . hwVersion = hwRevisions [ hwRev ] ;
2015-04-20 12:48:32 +02:00
}
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { versionCmd } ;
2015-03-22 00:34:54 +01:00
break ;
2015-03-25 22:23:45 +01:00
case ENDPOINT_APPMANAGER :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-03-25 22:23:45 +01:00
switch ( pebbleCmd ) {
case APPMANAGER_GETAPPBANKSTATUS :
2015-06-23 11:54:33 +02:00
GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo ( ) ;
2015-04-06 20:58:35 +02:00
int slotCount = buf . getInt ( ) ;
int slotsUsed = buf . getInt ( ) ;
appInfoCmd . apps = new GBDeviceApp [ slotsUsed ] ;
boolean [ ] slotInUse = new boolean [ slotCount ] ;
2015-03-25 22:23:45 +01:00
2015-04-06 20:58:35 +02:00
for ( int i = 0 ; i < slotsUsed ; i + + ) {
2015-03-26 18:11:47 +01:00
int id = buf . getInt ( ) ;
int index = buf . getInt ( ) ;
2015-04-06 20:58:35 +02:00
slotInUse [ index ] = true ;
2016-12-04 16:54:47 +01:00
String appName = getFixedString ( buf , 32 ) ;
String appCreator = getFixedString ( buf , 32 ) ;
2015-03-25 22:23:45 +01:00
int flags = buf . getInt ( ) ;
2015-03-31 23:34:19 +02:00
GBDeviceApp . Type appType ;
2015-04-10 22:26:52 +02:00
if ( ( flags & 16 ) = = 16 ) { // FIXME: verify this assumption
appType = GBDeviceApp . Type . APP_ACTIVITYTRACKER ;
} else if ( ( flags & 1 ) = = 1 ) { // FIXME: verify this assumption
appType = GBDeviceApp . Type . WATCHFACE ;
} else {
appType = GBDeviceApp . Type . APP_GENERIC ;
2015-03-31 23:34:19 +02:00
}
2015-03-26 12:06:26 +01:00
Short appVersion = buf . getShort ( ) ;
2016-12-04 16:54:47 +01:00
appInfoCmd . apps [ i ] = new GBDeviceApp ( tmpUUIDS . get ( i ) , appName , appCreator , appVersion . toString ( ) , appType ) ;
2015-03-25 22:23:45 +01:00
}
2015-04-06 20:58:35 +02:00
for ( int i = 0 ; i < slotCount ; i + + ) {
if ( ! slotInUse [ i ] ) {
appInfoCmd . freeSlot = ( byte ) i ;
2015-05-12 06:28:11 +02:00
LOG . info ( "found free slot " + i ) ;
2015-04-06 20:58:35 +02:00
break ;
}
}
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { appInfoCmd } ;
2015-03-25 22:23:45 +01:00
break ;
2015-05-18 20:56:19 +02:00
case APPMANAGER_GETUUIDS :
2015-06-23 11:54:33 +02:00
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes ( ) ;
2015-06-19 23:54:31 +02:00
sendBytes . encodedBytes = encodeSimpleMessage ( ENDPOINT_APPMANAGER , APPMANAGER_GETAPPBANKSTATUS ) ;
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { sendBytes } ;
2015-05-18 20:56:19 +02:00
tmpUUIDS . clear ( ) ;
slotsUsed = buf . getInt ( ) ;
for ( int i = 0 ; i < slotsUsed ; i + + ) {
2016-12-04 17:21:29 +01:00
UUID uuid = getUUID ( buf ) ;
2015-05-18 20:56:19 +02:00
LOG . info ( "found uuid: " + uuid ) ;
tmpUUIDS . add ( uuid ) ;
}
break ;
2015-03-26 18:11:47 +01:00
case APPMANAGER_REMOVEAPP :
2015-08-16 00:32:36 +02:00
GBDeviceEventAppManagement deleteRes = new GBDeviceEventAppManagement ( ) ;
deleteRes . type = GBDeviceEventAppManagement . EventType . DELETE ;
2015-03-26 18:11:47 +01:00
int result = buf . getInt ( ) ;
switch ( result ) {
case APPMANAGER_RES_SUCCESS :
2015-08-16 00:32:36 +02:00
deleteRes . event = GBDeviceEventAppManagement . Event . SUCCESS ;
2015-03-26 18:11:47 +01:00
break ;
default :
2015-08-16 00:32:36 +02:00
deleteRes . event = GBDeviceEventAppManagement . Event . FAILURE ;
2015-03-26 18:11:47 +01:00
break ;
}
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { deleteRes } ;
2015-03-26 18:11:47 +01:00
break ;
2015-03-25 22:23:45 +01:00
default :
2015-06-23 11:54:33 +02:00
LOG . info ( "Unknown APPMANAGER event" + pebbleCmd ) ;
2015-03-25 22:23:45 +01:00
break ;
}
break ;
2015-04-06 20:58:35 +02:00
case ENDPOINT_PUTBYTES :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-08-16 00:32:36 +02:00
GBDeviceEventAppManagement installRes = new GBDeviceEventAppManagement ( ) ;
installRes . type = GBDeviceEventAppManagement . EventType . INSTALL ;
2015-04-06 20:58:35 +02:00
switch ( pebbleCmd ) {
case PUTBYTES_INIT :
installRes . token = buf . getInt ( ) ;
2015-08-16 00:32:36 +02:00
installRes . event = GBDeviceEventAppManagement . Event . SUCCESS ;
2015-04-06 20:58:35 +02:00
break ;
default :
installRes . token = buf . getInt ( ) ;
2015-08-16 00:32:36 +02:00
installRes . event = GBDeviceEventAppManagement . Event . FAILURE ;
2015-04-06 20:58:35 +02:00
break ;
}
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { installRes } ;
2015-04-06 20:58:35 +02:00
break ;
2015-05-05 14:41:10 +02:00
case ENDPOINT_APPLICATIONMESSAGE :
2015-12-19 20:30:46 +01:00
case ENDPOINT_LAUNCHER :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-05-05 14:41:10 +02:00
last_id = buf . get ( ) ;
2016-12-04 17:21:29 +01:00
UUID uuid = getUUID ( buf ) ;
2015-05-21 18:17:39 +02:00
2015-05-05 14:41:10 +02:00
switch ( pebbleCmd ) {
case APPLICATIONMESSAGE_PUSH :
2016-12-09 23:21:51 +01:00
LOG . info ( ( endpoint = = ENDPOINT_LAUNCHER ? "got LAUNCHER PUSH from UUID : " : "got APPLICATIONMESSAGE PUSH from UUID : " ) + uuid ) ;
2015-10-04 15:53:11 +02:00
AppMessageHandler handler = mAppMessageHandlers . get ( uuid ) ;
if ( handler ! = null ) {
2016-06-04 21:50:26 +02:00
if ( handler . isEnabled ( ) ) {
2016-12-09 23:21:51 +01:00
if ( endpoint = = ENDPOINT_APPLICATIONMESSAGE ) {
ArrayList < Pair < Integer , Object > > dict = decodeDict ( buf ) ;
devEvts = handler . handleMessage ( dict ) ;
}
else {
2016-12-31 18:56:24 +01:00
currentRunningApp = uuid ;
2016-12-31 19:04:05 +01:00
devEvts = handler . onAppStart ( ) ;
2016-12-09 23:21:51 +01:00
}
2016-06-04 21:50:26 +02:00
} else {
devEvts = new GBDeviceEvent [ ] { null } ;
}
2015-09-17 19:21:22 +02:00
} else {
try {
2016-12-11 20:25:46 +01:00
if ( endpoint = = ENDPOINT_APPLICATIONMESSAGE ) {
devEvts = decodeDictToJSONAppMessage ( uuid , buf ) ;
}
else {
2016-12-31 18:56:24 +01:00
currentRunningApp = uuid ;
2016-12-11 20:25:46 +01:00
GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement ( ) ;
gbDeviceEventAppManagement . uuid = uuid ;
gbDeviceEventAppManagement . type = GBDeviceEventAppManagement . EventType . START ;
gbDeviceEventAppManagement . event = GBDeviceEventAppManagement . Event . SUCCESS ;
devEvts = new GBDeviceEvent [ ] { gbDeviceEventAppManagement } ;
}
2015-09-17 19:21:22 +02:00
} catch ( JSONException e ) {
2016-12-11 20:25:46 +01:00
LOG . error ( e . getMessage ( ) ) ;
2015-09-17 19:21:22 +02:00
return null ;
}
2015-05-06 11:41:38 +02:00
}
2015-05-05 14:41:10 +02:00
break ;
case APPLICATIONMESSAGE_ACK :
2015-12-19 20:30:46 +01:00
LOG . info ( "got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") ACK" ) ;
devEvts = new GBDeviceEvent [ ] { null } ;
2015-05-05 14:41:10 +02:00
break ;
case APPLICATIONMESSAGE_NACK :
2015-12-19 20:30:46 +01:00
LOG . info ( "got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") NACK" ) ;
devEvts = new GBDeviceEvent [ ] { null } ;
2015-05-05 14:41:10 +02:00
break ;
case APPLICATIONMESSAGE_REQUEST :
2015-12-19 20:30:46 +01:00
LOG . info ( "got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") REQUEST" ) ;
devEvts = new GBDeviceEvent [ ] { null } ;
2015-05-05 14:41:10 +02:00
break ;
default :
break ;
}
break ;
2015-05-06 11:41:38 +02:00
case ENDPOINT_PHONEVERSION :
2015-06-24 23:55:51 +02:00
pebbleCmd = buf . get ( ) ;
2015-05-06 11:41:38 +02:00
switch ( pebbleCmd ) {
case PHONEVERSION_REQUEST :
2015-05-12 06:28:11 +02:00
LOG . info ( "Pebble asked for Phone/App Version - repLYING!" ) ;
2015-06-23 11:54:33 +02:00
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes ( ) ;
2015-05-06 11:41:38 +02:00
sendBytes . encodedBytes = encodePhoneVersion ( PHONEVERSION_REMOTE_OS_ANDROID ) ;
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { sendBytes } ;
2015-05-06 11:41:38 +02:00
break ;
default :
break ;
}
break ;
2015-08-26 23:17:32 +02:00
case ENDPOINT_DATALOG :
2017-02-19 22:59:37 +01:00
devEvts = decodeDatalog ( buf , length ) ;
2015-08-26 23:17:32 +02:00
break ;
2015-06-24 23:55:51 +02:00
case ENDPOINT_SCREENSHOT :
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { decodeScreenshot ( buf , length ) } ;
2015-06-24 23:55:51 +02:00
break ;
2015-07-21 01:33:13 +02:00
case ENDPOINT_EXTENSIBLENOTIFS :
2015-07-22 20:53:18 +02:00
case ENDPOINT_NOTIFICATIONACTION :
2015-12-20 21:23:39 +01:00
devEvts = decodeAction ( buf ) ;
2015-07-21 01:33:13 +02:00
break ;
2015-08-09 21:42:27 +02:00
case ENDPOINT_PING :
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { decodePing ( buf ) } ;
2015-08-11 13:21:29 +02:00
break ;
case ENDPOINT_APPFETCH :
2015-08-27 15:02:29 +02:00
devEvts = new GBDeviceEvent [ ] { decodeAppFetch ( buf ) } ;
2015-08-09 21:42:27 +02:00
break ;
2015-12-17 23:09:52 +01:00
case ENDPOINT_SYSTEMMESSAGE :
devEvts = new GBDeviceEvent [ ] { decodeSystemMessage ( buf ) } ;
break ;
case ENDPOINT_APPRUNSTATE :
2016-03-08 12:02:00 +01:00
devEvts = decodeAppRunState ( buf ) ;
2015-12-17 23:09:52 +01:00
break ;
2015-12-18 12:41:01 +01:00
case ENDPOINT_BLOBDB :
devEvts = new GBDeviceEvent [ ] { decodeBlobDb ( buf ) } ;
break ;
2016-06-09 19:55:36 +02:00
case ENDPOINT_APPREORDER :
devEvts = new GBDeviceEvent [ ] { decodeAppReorder ( buf ) } ;
2016-12-04 16:54:47 +01:00
break ;
case ENDPOINT_APPLOGS :
decodeAppLogs ( buf ) ;
break ;
2016-12-08 16:38:31 +01:00
// case ENDPOINT_VOICECONTROL:
// devEvts = new GBDeviceEvent[]{decodeVoiceControl(buf)};
// case ENDPOINT_AUDIOSTREAM:
// LOG.debug(GB.hexdump(responseData, 0, responseData.length));
// break;
2015-01-07 14:00:18 +01:00
default :
2015-03-26 18:11:47 +01:00
break ;
2015-01-07 14:00:18 +01:00
}
2015-08-27 15:02:29 +02:00
return devEvts ;
2015-01-07 14:00:18 +01:00
}
2015-06-19 12:34:33 +02:00
2016-12-04 16:54:47 +01:00
void setForceProtocol ( boolean force ) {
2015-06-19 12:34:33 +02:00
LOG . info ( "setting force protocol to " + force ) ;
mForceProtocol = force ;
}
2016-12-04 16:54:47 +01:00
2017-02-10 23:06:34 +01:00
void setAlwaysACKPebbleKit ( boolean alwaysACKPebbleKit ) {
2017-02-19 22:59:37 +01:00
LOG . info ( "setting always ACK PebbleKit to " + alwaysACKPebbleKit ) ;
2017-02-10 23:06:34 +01:00
mAlwaysACKPebbleKit = alwaysACKPebbleKit ;
}
2017-02-19 22:59:37 +01:00
void setEnablePebbleKit ( boolean enablePebbleKit ) {
LOG . info ( "setting enable PebbleKit support to " + enablePebbleKit ) ;
mEnablePebbleKit = enablePebbleKit ;
}
2016-12-04 16:54:47 +01:00
private String getFixedString ( ByteBuffer buf , int length ) {
byte [ ] tmp = new byte [ length ] ;
buf . get ( tmp , 0 , length ) ;
return new String ( tmp ) . trim ( ) ;
}
2016-12-04 17:21:29 +01:00
private UUID getUUID ( ByteBuffer buf ) {
ByteOrder byteOrder = buf . order ( ) ;
buf . order ( ByteOrder . BIG_ENDIAN ) ;
long uuid_high = buf . getLong ( ) ;
long uuid_low = buf . getLong ( ) ;
buf . order ( byteOrder ) ;
return new UUID ( uuid_high , uuid_low ) ;
}
2015-01-07 14:00:18 +01:00
}