diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 57b5181a2..f0793b545 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,7 +40,7 @@ android:value=".ControlCenter" /> 8192) // that should be too much - break; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; - while ((count = zis.read(buffer)) != -1) { - baos.write(buffer, 0, count); - } - - String jsonString = baos.toString(); - try { - JSONObject json = new JSONObject(jsonString); - String appName = json.getString("shortName"); - String appCreator = json.getString("companyName"); - String appVersion = json.getString("versionLabel"); - if (appName != null && appCreator != null && appVersion != null) { - debugTextView.setText("This is just a test, you cant install anything yet \n\n" + appName + " Version " + appVersion + " by " + appCreator + "\n"); - } - } catch (JSONException e) { - e.printStackTrace(); - break; - } - } - } - zis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java index 71ba8054a..b74e99cba 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java @@ -29,7 +29,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.zip.ZipInputStream; +import nodomain.freeyourgadget.gadgetbridge.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommand; import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo; import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult; @@ -63,8 +65,10 @@ public class BluetoothCommunicationService extends Service { = "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.request_appinfo"; public static final String ACTION_DELETEAPP = "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.deleteapp"; + public static final String ACTION_INSTALL_PEBBLEAPP + = "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.install_pebbbleapp"; - private static final String TAG = "BluetoothCommunicationService"; + private static final String TAG = "CommunicationService"; private static final int NOTIFICATION_ID = 1; private BluetoothAdapter mBtAdapter = null; private BluetoothSocket mBtSocket = null; @@ -161,6 +165,7 @@ public class BluetoothCommunicationService extends Service { case APP_INFO: Log.i(TAG, "Got command for APP_INFO"); GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd; + mGBDevice.setFreeAppSlot(appInfoCmd.freeSlot); Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST); int appCount = appInfoCmd.apps.length; appInfoIntent.putExtra("app_count", appCount); @@ -189,6 +194,20 @@ public class BluetoothCommunicationService extends Service { break; } break; + case INSTALL: + switch (appMgmtRes.result) { + case FAILURE: + Log.i(TAG, "failure installing app"); // TODO: report to Installer + break; + case SUCCESS: + if (mGBDevice.getType() == GBDevice.Type.PEBBLE) { + ((PebbleIoThread) mGBDeviceIoThread).setToken(appMgmtRes.token); + } + break; + default: + break; + } + break; default: break; } @@ -329,6 +348,12 @@ public class BluetoothCommunicationService extends Service { int id = intent.getIntExtra("app_id", -1); int index = intent.getIntExtra("app_index", -1); mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppDelete(id, index)); + } else if (action.equals(ACTION_INSTALL_PEBBLEAPP)) { + String uriString = intent.getStringExtra("app_uri"); + if (uriString != null && mGBDevice.getFreeAppSlot() != -1) { + Log.i(TAG, "will try to install app in slot " + mGBDevice.getFreeAppSlot()); + ((PebbleIoThread) mGBDeviceIoThread).installApp(Uri.parse(uriString)); + } } else if (action.equals(ACTION_START)) { startForeground(NOTIFICATION_ID, createNotification("Gadgetbridge running")); mStarted = true; @@ -415,15 +440,37 @@ public class BluetoothCommunicationService extends Service { // implement connect() run() write() and quit() here } + private enum PebbleAppInstallState { + UNKNOWN, + APP_START_INSTALL, + APP_WAIT_TOKEN, + APP_UPLOAD_CHUNK, + APP_UPLOAD_COMPLETE, + RES_START_INSTALL, + RES_WAIT_TOKEN, + RES_UPLOAD_CHUNK, + RES_UPLOAD_COMPLETE, + } + private class PebbleIoThread extends GBDeviceIoThread { + private final PebbleProtocol mmPebbleProtocol; private InputStream mmInStream = null; private OutputStream mmOutStream = null; - private boolean mQuit = false; + private boolean mmQuit = false; private boolean mmIsConnected = false; + private boolean mmIsInstalling = false; private int mmConnectionAttempts = 0; + /* app installation */ + private Uri mmInstallURI = null; + private PBWReader mmPBWReader = null; + private int mmAppInstallToken = -1; + private ZipInputStream mmZis = null; + private PebbleAppInstallState mmInstallState = PebbleAppInstallState.UNKNOWN; + public PebbleIoThread(String btDeviceAddress) { super(btDeviceAddress); + mmPebbleProtocol = (PebbleProtocol) mGBDeviceProtocol; } private boolean connect(String btDeviceAddress) { @@ -451,13 +498,48 @@ public class BluetoothCommunicationService extends Service { public void run() { mmIsConnected = connect(mmBtDeviceAddress); setReceiversEnableState(mmIsConnected); // enable/disable BroadcastReceivers - mQuit = !mmIsConnected; // quit if not connected + mmQuit = !mmIsConnected; // quit if not connected byte[] buffer = new byte[8192]; int bytes; - while (!mQuit) { + while (!mmQuit) { try { + if (mmIsInstalling) { + switch (mmInstallState) { + case APP_START_INSTALL: + Log.i(TAG, "start installing app binary"); + mmPBWReader = new PBWReader(mmInstallURI, getApplicationContext()); + mmZis = mmPBWReader.getInputStreamAppBinary(); + int binarySize = mmPBWReader.getAppBinarySize(); + writeInstallApp(mmPebbleProtocol.encodeUploadStart(PebbleProtocol.PUTBYTES_TYPE_BINARY, mGBDevice.getFreeAppSlot(), binarySize), -1); + mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN; + break; + case APP_WAIT_TOKEN: + if (mmAppInstallToken != -1) { + Log.i(TAG, "got token " + mmAppInstallToken); + mmInstallState = PebbleAppInstallState.APP_UPLOAD_CHUNK; + continue; + } + break; + case APP_UPLOAD_CHUNK: + bytes = mmZis.read(buffer); + + if (bytes != -1) { + writeInstallApp(mmPebbleProtocol.encodeUploadChunk(mmAppInstallToken, buffer, bytes), -1); + mmAppInstallToken = -1; + mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN; + } else { + mmInstallState = PebbleAppInstallState.UNKNOWN; + mmIsInstalling = false; + mmZis = null; + mmAppInstallToken = -1; + } + break; + default: + break; + } + } bytes = mmInStream.read(buffer, 0, 4); if (bytes < 4) continue; @@ -488,17 +570,17 @@ public class BluetoothCommunicationService extends Service { if (length == 1 && endpoint == PebbleProtocol.ENDPOINT_PHONEVERSION) { Log.i(TAG, "Pebble asked for Phone/App Version - repLYING!"); - write(mGBDeviceProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID)); - write(mGBDeviceProtocol.encodeFirmwareVersionReq()); + write(mmPebbleProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID)); + write(mmPebbleProtocol.encodeFirmwareVersionReq()); // this does not really belong here, but since the pebble only asks for our version once it should do the job SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(BluetoothCommunicationService.this); if (sharedPrefs.getBoolean("datetime_synconconnect", true)) { Log.i(TAG, "syncing time"); - write(mGBDeviceProtocol.encodeSetTime(-1)); + write(mmPebbleProtocol.encodeSetTime(-1)); } } else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) { - GBDeviceCommand deviceCmd = mGBDeviceProtocol.decodeResponse(buffer); + GBDeviceCommand deviceCmd = mmPebbleProtocol.decodeResponse(buffer); if (deviceCmd == null) { Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)"); } else { @@ -512,6 +594,7 @@ public class BluetoothCommunicationService extends Service { } } catch (IOException e) { if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right + Log.i(TAG, e.getMessage()); mGBDevice.setState(GBDevice.State.CONNECTING); sendDeviceUpdateIntent(); updateNotification("connection lost, trying to reconnect"); @@ -527,7 +610,7 @@ public class BluetoothCommunicationService extends Service { mBtSocket = null; setReceiversEnableState(false); Log.i(TAG, "Bluetooth socket closed, will quit IO Thread"); - mQuit = true; + mmQuit = true; } } } @@ -547,7 +630,8 @@ public class BluetoothCommunicationService extends Service { } synchronized public void write(byte[] bytes) { - if (mmIsConnected) { + // block writes if app installation in in progress + if (mmIsConnected && !mmIsInstalling) { try { mmOutStream.write(bytes); mmOutStream.flush(); @@ -556,8 +640,41 @@ public class BluetoothCommunicationService extends Service { } } + public void setToken(int token) { + mmAppInstallToken = token; + } + + private void writeInstallApp(byte[] bytes, int length) { + if (!mmIsInstalling) { + return; + } + if (length == -1) { + length = bytes.length; + } + Log.i(TAG, "got bytes for writeInstallApp()" + length); + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[length * 2]; + for (int j = 0; j < length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + Log.i(TAG, new String(hexChars)); + try { + mmOutStream.write(bytes); + mmOutStream.flush(); + } catch (IOException e) { + } + } + + public void installApp(Uri uri) { + mmInstallState = PebbleAppInstallState.APP_START_INSTALL; + mmIsInstalling = true; + mmInstallURI = uri; + } + public void quit() { - mQuit = true; + mmQuit = true; if (mBtSocket != null) { try { mBtSocket.close(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java index fb7d22e8a..120303611 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java @@ -6,6 +6,7 @@ public class GBDevice { private final Type type; private String firmwareVersion = null; private State state = State.NOT_CONNECTED; + private byte freeAppSlot = -1; public GBDevice(String address, String name, Type type) { this.address = address; @@ -61,6 +62,14 @@ public class GBDevice { return type; } + public void setFreeAppSlot(byte freeAppSlot) { + this.freeAppSlot = freeAppSlot; + } + + public byte getFreeAppSlot() { + return freeAppSlot; + } + public enum State { NOT_CONNECTED, CONNECTING, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java new file mode 100644 index 000000000..1838ca419 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java @@ -0,0 +1,154 @@ +package nodomain.freeyourgadget.gadgetbridge.pebble; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp; + +public class PBWReader { + private GBDeviceApp app; + private final Uri uri; + private final ContentResolver cr; + + public PBWReader(Uri uri, Context context) { + this.uri = uri; + cr = context.getContentResolver(); + + InputStream fin = null; + try { + fin = new BufferedInputStream(cr.openInputStream(uri)); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + return; + } + ZipInputStream zis = new ZipInputStream(fin); + ZipEntry ze = null; + try { + while ((ze = zis.getNextEntry()) != null) { + if (ze.getName().equals("appinfo.json")) { + long bytes = ze.getSize(); + if (bytes > 8192) // that should be too much + break; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = zis.read(buffer)) != -1) { + baos.write(buffer, 0, count); + } + + String jsonString = baos.toString(); + try { + JSONObject json = new JSONObject(jsonString); + String appName = json.getString("shortName"); + String appCreator = json.getString("companyName"); + String appVersion = json.getString("versionLabel"); + if (appName != null && appCreator != null && appVersion != null) { + // FIXME: dont assume WATCHFACE + app = new GBDeviceApp(-1, -1, appName, appCreator, appVersion, GBDeviceApp.Type.WATCHFACE); + } + + } catch (JSONException e) { + e.printStackTrace(); + break; + } + } + } + zis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public GBDeviceApp getGBDeviceApp() { + return app; + } + + public ZipInputStream getInputStreamFile(String filename) { + InputStream fin = null; + try { + fin = new BufferedInputStream(cr.openInputStream(uri)); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + ZipInputStream zis = new ZipInputStream(fin); + ZipEntry ze = null; + try { + while ((ze = zis.getNextEntry()) != null) { + if (ze.getName().equals(filename)) { + return zis; + } + } + zis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public int getFileSize(String filename) { + InputStream fin = null; + try { + fin = new BufferedInputStream(cr.openInputStream(uri)); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + return -1; + } + + ZipInputStream zis = new ZipInputStream(fin); + ZipEntry ze = null; + try { + while ((ze = zis.getNextEntry()) != null) { + if (ze.getName().equals(filename)) { + return (int) ze.getSize(); + } + } + zis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return -1; + } + + public ZipInputStream getInputStreamAppBinary() { + return getInputStreamFile("pebble-app.bin"); + } + + public int getAppBinarySize() { + return getFileSize("pebble-app.bin"); + } + + public ZipInputStream getInputStreamAppWorker() { + return getInputStreamFile("pebble-worker.bin"); + } + + public int getAppWorkerSize() { + return getFileSize("pebble-worker.bin"); + } + + public ZipInputStream getInputStreamAppResources() { + return getInputStreamFile("app_resources.pbpack"); + } + + public int getAppResourcesSize() { + return getFileSize("pebble-resources.pbpack"); + } + +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java new file mode 100644 index 000000000..f6f34d9ab --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java @@ -0,0 +1,85 @@ +package nodomain.freeyourgadget.gadgetbridge.pebble; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.NavUtils; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import java.util.zip.ZipInputStream; + +import nodomain.freeyourgadget.gadgetbridge.BluetoothCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp; +import nodomain.freeyourgadget.gadgetbridge.R; + + +public class PebbleAppInstallerActivity extends Activity { + + private final String TAG = this.getClass().getSimpleName(); + + TextView debugTextView; + Button installButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_appinstaller); + getActionBar().setDisplayHomeAsUpEnabled(true); + + debugTextView = (TextView) findViewById(R.id.debugTextView); + installButton = (Button) findViewById(R.id.installButton); + + debugTextView.setText("contents:\n"); + final Uri uri = getIntent().getData(); + PBWReader pbwReader = new PBWReader(uri, getApplicationContext()); + + GBDeviceApp app = pbwReader.getGBDeviceApp(); + ZipInputStream zis = pbwReader.getInputStreamAppBinary(); +/* + STM32CRC stm32crc = new STM32CRC(); + byte[] buffer = new byte[1000]; + int count = 0; + try { + while ((count = zis.read(buffer)) != -1) { + stm32crc.addData(buffer, count); + } + } catch (IOException e) { + e.printStackTrace(); + } + + Log.i("TEST", "STMCRC32: " + stm32crc.getResult()); +*/ + if (pbwReader != null && app != null) { + debugTextView.setText("This is just a test, you cant install anything yet \n\n" + app.getName() + " Version " + app.getVersion() + " by " + app.getCreator() + "\n"); + installButton.setEnabled(true); + installButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent startIntent = new Intent(PebbleAppInstallerActivity.this, BluetoothCommunicationService.class); + startIntent.setAction(BluetoothCommunicationService.ACTION_INSTALL_PEBBLEAPP); + startIntent.putExtra("app_uri", uri.toString()); + startService(startIntent); + } + }); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppInfo.java index 696f9b208..fe2ec16dd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppInfo.java @@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp; public class GBDeviceCommandAppInfo extends GBDeviceCommand { public GBDeviceApp apps[]; + public byte freeSlot = -1; public GBDeviceCommandAppInfo() { commandClass = CommandClass.APP_INFO; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppManagementResult.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppManagementResult.java index 1cd7202c8..52f4d9312 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppManagementResult.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceCommandAppManagementResult.java @@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.protocol; public class GBDeviceCommandAppManagementResult extends GBDeviceCommand { public Result result = Result.UNKNOWN; public CommandType type = CommandType.UNKNOWN; + public int token = -1; public GBDeviceCommandAppManagementResult() { commandClass = CommandClass.APP_MANAGEMENT_RES; @@ -10,12 +11,14 @@ public class GBDeviceCommandAppManagementResult extends GBDeviceCommand { public enum CommandType { UNKNOWN, + INSTALL, DELETE, } public enum Result { UNKNOWN, SUCCESS, + ACKNOLEDGE, FAILURE, } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java index ebc9d3c99..cabc939c6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java @@ -74,11 +74,19 @@ public class PebbleProtocol extends GBDeviceProtocol { static final int APPMANAGER_RES_SUCCESS = 1; - static final short LENGTH_PREFIX = 4; - static final short LENGTH_SETTIME = 5; - static final short LENGTH_REMOVEAPP = 9; - static final short LENGTH_PHONEVERSION = 17; + static final byte PUTBYTES_INIT = 1; + static final byte PUTBYTES_SEND = 2; + static final byte PUTBYTES_COMMIT = 3; + static final byte PUTBYTES_ABORT = 4; + static final byte PUTBYTES_COMPLETE = 5; + static final byte PUTBYTES_TYPE_FIRMWARE = 1; + static final byte PUTBYTES_TYPE_RECOVERY = 2; + static final byte PUTBYTES_TYPE_SYSRESOURCES = 3; + static final byte PUTBYTES_TYPE_RESOURCES = 4; + public static final byte PUTBYTES_TYPE_BINARY = 5; + static final byte PUTBYTES_TYPE_FILE = 6; + static final byte PUTBYTES_TYPE_WORKER = 7; static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains static final byte PHONEVERSION_APPVERSION_MAJOR = 2; @@ -104,6 +112,14 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte PHONEVERSION_REMOTE_OS_LINUX = 4; static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5; + + static final short LENGTH_PREFIX = 4; + static final short LENGTH_SETTIME = 5; + static final short LENGTH_REMOVEAPP = 9; + static final short LENGTH_PHONEVERSION = 17; + static final short LENGTH_UPLOADSTART = 7; + static final short LENGTH_UPLOADCHUNK = 9; + private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) { // Calculate length first int length = LENGTH_PREFIX + 1; @@ -265,6 +281,31 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } + /* pebble specific install methods */ + public byte[] encodeUploadStart(byte type, byte index, int size) { + ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADSTART); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort(LENGTH_UPLOADSTART); + buf.putShort(ENDPOINT_PUTBYTES); + buf.put(PUTBYTES_INIT); + buf.putInt(size); + buf.put(type); + buf.put(index); + return buf.array(); + } + + public byte[] encodeUploadChunk(int token, byte[] buffer, int size) { + 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(); + } + public GBDeviceCommand decodeResponse(byte[] responseData) { ByteBuffer buf = ByteBuffer.wrap(responseData); buf.order(ByteOrder.BIG_ENDIAN); @@ -322,15 +363,17 @@ public class PebbleProtocol extends GBDeviceProtocol { switch (pebbleCmd) { case APPMANAGER_GETAPPBANKSTATUS: GBDeviceCommandAppInfo appInfoCmd = new GBDeviceCommandAppInfo(); - int banks = buf.getInt(); - int banksUsed = buf.getInt(); + int slotCount = buf.getInt(); + int slotsUsed = buf.getInt(); byte[] appName = new byte[32]; byte[] appCreator = new byte[32]; - appInfoCmd.apps = new GBDeviceApp[banksUsed]; + appInfoCmd.apps = new GBDeviceApp[slotsUsed]; + boolean[] slotInUse = new boolean[slotCount]; - for (int i = 0; i < banksUsed; i++) { + for (int i = 0; i < slotsUsed; i++) { int id = buf.getInt(); int index = buf.getInt(); + slotInUse[index] = true; buf.get(appName, 0, 32); buf.get(appCreator, 0, 32); int flags = buf.getInt(); @@ -347,6 +390,13 @@ public class PebbleProtocol extends GBDeviceProtocol { Short appVersion = buf.getShort(); appInfoCmd.apps[i] = new GBDeviceApp(id, index, new String(appName).trim(), new String(appCreator).trim(), appVersion.toString(), appType); } + for (int i = 0; i < slotCount; i++) { + if (!slotInUse[i]) { + appInfoCmd.freeSlot = (byte) i; + Log.i(TAG, "found free slot " + i); + break; + } + } cmd = appInfoCmd; break; case APPMANAGER_REMOVEAPP: @@ -369,6 +419,21 @@ public class PebbleProtocol extends GBDeviceProtocol { break; } break; + case ENDPOINT_PUTBYTES: + GBDeviceCommandAppManagementResult installRes = new GBDeviceCommandAppManagementResult(); + installRes.type = GBDeviceCommandAppManagementResult.CommandType.INSTALL; + switch (pebbleCmd) { + case PUTBYTES_INIT: + installRes.token = buf.getInt(); + installRes.result = GBDeviceCommandAppManagementResult.Result.SUCCESS; + break; + default: + installRes.token = buf.getInt(); + installRes.result = GBDeviceCommandAppManagementResult.Result.FAILURE; + break; + } + cmd = installRes; + break; default: break; } diff --git a/app/src/main/res/layout/activity_appinstaller.xml b/app/src/main/res/layout/activity_appinstaller.xml index 5964e3d39..35bf22c3c 100644 --- a/app/src/main/res/layout/activity_appinstaller.xml +++ b/app/src/main/res/layout/activity_appinstaller.xml @@ -6,11 +6,20 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" - tools:context="nodomain.freeyourgadget.gadgetbridge.AppInstallerActivity"> + tools:context="nodomain.freeyourgadget.gadgetbridge.pebble.PebbleAppInstallerActivity"> +