mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 20:36:51 +01:00
Pebble: working on app installation (WIP, do not use)
This commit is contained in:
parent
1766c82ab8
commit
2c1cacedb3
@ -40,7 +40,7 @@
|
|||||||
android:value=".ControlCenter" />
|
android:value=".ControlCenter" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".AppInstallerActivity"
|
android:name=".pebble.PebbleAppInstallerActivity"
|
||||||
android:label="@string/title_activity_appinstaller">
|
android:label="@string/title_activity_appinstaller">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.NavUtils;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
public class AppInstallerActivity extends Activity {
|
|
||||||
|
|
||||||
private final String TAG = this.getClass().getSimpleName();
|
|
||||||
|
|
||||||
TextView debugTextView;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_appinstaller);
|
|
||||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
debugTextView = (TextView) findViewById(R.id.debugTextView);
|
|
||||||
debugTextView.setText("contents:\n");
|
|
||||||
Uri uri = getIntent().getData();
|
|
||||||
|
|
||||||
ContentResolver cr = getContentResolver();
|
|
||||||
InputStream fin = null;
|
|
||||||
try {
|
|
||||||
fin = cr.openInputStream(uri);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ZipInputStream zis = new ZipInputStream(fin);
|
|
||||||
ZipEntry ze = null;
|
|
||||||
GBDeviceApp app;
|
|
||||||
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) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,7 +29,9 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
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.GBDeviceCommand;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo;
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult;
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult;
|
||||||
@ -63,8 +65,10 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.request_appinfo";
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.request_appinfo";
|
||||||
public static final String ACTION_DELETEAPP
|
public static final String ACTION_DELETEAPP
|
||||||
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.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 static final int NOTIFICATION_ID = 1;
|
||||||
private BluetoothAdapter mBtAdapter = null;
|
private BluetoothAdapter mBtAdapter = null;
|
||||||
private BluetoothSocket mBtSocket = null;
|
private BluetoothSocket mBtSocket = null;
|
||||||
@ -161,6 +165,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
case APP_INFO:
|
case APP_INFO:
|
||||||
Log.i(TAG, "Got command for APP_INFO");
|
Log.i(TAG, "Got command for APP_INFO");
|
||||||
GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd;
|
GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd;
|
||||||
|
mGBDevice.setFreeAppSlot(appInfoCmd.freeSlot);
|
||||||
Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST);
|
Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST);
|
||||||
int appCount = appInfoCmd.apps.length;
|
int appCount = appInfoCmd.apps.length;
|
||||||
appInfoIntent.putExtra("app_count", appCount);
|
appInfoIntent.putExtra("app_count", appCount);
|
||||||
@ -189,6 +194,20 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -329,6 +348,12 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
int id = intent.getIntExtra("app_id", -1);
|
int id = intent.getIntExtra("app_id", -1);
|
||||||
int index = intent.getIntExtra("app_index", -1);
|
int index = intent.getIntExtra("app_index", -1);
|
||||||
mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppDelete(id, index));
|
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)) {
|
} else if (action.equals(ACTION_START)) {
|
||||||
startForeground(NOTIFICATION_ID, createNotification("Gadgetbridge running"));
|
startForeground(NOTIFICATION_ID, createNotification("Gadgetbridge running"));
|
||||||
mStarted = true;
|
mStarted = true;
|
||||||
@ -415,15 +440,37 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
// implement connect() run() write() and quit() here
|
// 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 class PebbleIoThread extends GBDeviceIoThread {
|
||||||
|
private final PebbleProtocol mmPebbleProtocol;
|
||||||
private InputStream mmInStream = null;
|
private InputStream mmInStream = null;
|
||||||
private OutputStream mmOutStream = null;
|
private OutputStream mmOutStream = null;
|
||||||
private boolean mQuit = false;
|
private boolean mmQuit = false;
|
||||||
private boolean mmIsConnected = false;
|
private boolean mmIsConnected = false;
|
||||||
|
private boolean mmIsInstalling = false;
|
||||||
private int mmConnectionAttempts = 0;
|
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) {
|
public PebbleIoThread(String btDeviceAddress) {
|
||||||
super(btDeviceAddress);
|
super(btDeviceAddress);
|
||||||
|
mmPebbleProtocol = (PebbleProtocol) mGBDeviceProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean connect(String btDeviceAddress) {
|
private boolean connect(String btDeviceAddress) {
|
||||||
@ -451,13 +498,48 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
public void run() {
|
public void run() {
|
||||||
mmIsConnected = connect(mmBtDeviceAddress);
|
mmIsConnected = connect(mmBtDeviceAddress);
|
||||||
setReceiversEnableState(mmIsConnected); // enable/disable BroadcastReceivers
|
setReceiversEnableState(mmIsConnected); // enable/disable BroadcastReceivers
|
||||||
mQuit = !mmIsConnected; // quit if not connected
|
mmQuit = !mmIsConnected; // quit if not connected
|
||||||
|
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
int bytes;
|
int bytes;
|
||||||
|
|
||||||
while (!mQuit) {
|
while (!mmQuit) {
|
||||||
try {
|
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);
|
bytes = mmInStream.read(buffer, 0, 4);
|
||||||
if (bytes < 4)
|
if (bytes < 4)
|
||||||
continue;
|
continue;
|
||||||
@ -488,17 +570,17 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
|
|
||||||
if (length == 1 && endpoint == PebbleProtocol.ENDPOINT_PHONEVERSION) {
|
if (length == 1 && endpoint == PebbleProtocol.ENDPOINT_PHONEVERSION) {
|
||||||
Log.i(TAG, "Pebble asked for Phone/App Version - repLYING!");
|
Log.i(TAG, "Pebble asked for Phone/App Version - repLYING!");
|
||||||
write(mGBDeviceProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID));
|
write(mmPebbleProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID));
|
||||||
write(mGBDeviceProtocol.encodeFirmwareVersionReq());
|
write(mmPebbleProtocol.encodeFirmwareVersionReq());
|
||||||
|
|
||||||
// this does not really belong here, but since the pebble only asks for our version once it should do the job
|
// 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);
|
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(BluetoothCommunicationService.this);
|
||||||
if (sharedPrefs.getBoolean("datetime_synconconnect", true)) {
|
if (sharedPrefs.getBoolean("datetime_synconconnect", true)) {
|
||||||
Log.i(TAG, "syncing time");
|
Log.i(TAG, "syncing time");
|
||||||
write(mGBDeviceProtocol.encodeSetTime(-1));
|
write(mmPebbleProtocol.encodeSetTime(-1));
|
||||||
}
|
}
|
||||||
} else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) {
|
} else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) {
|
||||||
GBDeviceCommand deviceCmd = mGBDeviceProtocol.decodeResponse(buffer);
|
GBDeviceCommand deviceCmd = mmPebbleProtocol.decodeResponse(buffer);
|
||||||
if (deviceCmd == null) {
|
if (deviceCmd == null) {
|
||||||
Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)");
|
Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)");
|
||||||
} else {
|
} else {
|
||||||
@ -512,6 +594,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right
|
if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right
|
||||||
|
Log.i(TAG, e.getMessage());
|
||||||
mGBDevice.setState(GBDevice.State.CONNECTING);
|
mGBDevice.setState(GBDevice.State.CONNECTING);
|
||||||
sendDeviceUpdateIntent();
|
sendDeviceUpdateIntent();
|
||||||
updateNotification("connection lost, trying to reconnect");
|
updateNotification("connection lost, trying to reconnect");
|
||||||
@ -527,7 +610,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
mBtSocket = null;
|
mBtSocket = null;
|
||||||
setReceiversEnableState(false);
|
setReceiversEnableState(false);
|
||||||
Log.i(TAG, "Bluetooth socket closed, will quit IO Thread");
|
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) {
|
synchronized public void write(byte[] bytes) {
|
||||||
if (mmIsConnected) {
|
// block writes if app installation in in progress
|
||||||
|
if (mmIsConnected && !mmIsInstalling) {
|
||||||
try {
|
try {
|
||||||
mmOutStream.write(bytes);
|
mmOutStream.write(bytes);
|
||||||
mmOutStream.flush();
|
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() {
|
public void quit() {
|
||||||
mQuit = true;
|
mmQuit = true;
|
||||||
if (mBtSocket != null) {
|
if (mBtSocket != null) {
|
||||||
try {
|
try {
|
||||||
mBtSocket.close();
|
mBtSocket.close();
|
||||||
|
@ -6,6 +6,7 @@ public class GBDevice {
|
|||||||
private final Type type;
|
private final Type type;
|
||||||
private String firmwareVersion = null;
|
private String firmwareVersion = null;
|
||||||
private State state = State.NOT_CONNECTED;
|
private State state = State.NOT_CONNECTED;
|
||||||
|
private byte freeAppSlot = -1;
|
||||||
|
|
||||||
public GBDevice(String address, String name, Type type) {
|
public GBDevice(String address, String name, Type type) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
@ -61,6 +62,14 @@ public class GBDevice {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFreeAppSlot(byte freeAppSlot) {
|
||||||
|
this.freeAppSlot = freeAppSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getFreeAppSlot() {
|
||||||
|
return freeAppSlot;
|
||||||
|
}
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
NOT_CONNECTED,
|
NOT_CONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp;
|
|||||||
|
|
||||||
public class GBDeviceCommandAppInfo extends GBDeviceCommand {
|
public class GBDeviceCommandAppInfo extends GBDeviceCommand {
|
||||||
public GBDeviceApp apps[];
|
public GBDeviceApp apps[];
|
||||||
|
public byte freeSlot = -1;
|
||||||
|
|
||||||
public GBDeviceCommandAppInfo() {
|
public GBDeviceCommandAppInfo() {
|
||||||
commandClass = CommandClass.APP_INFO;
|
commandClass = CommandClass.APP_INFO;
|
||||||
|
@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.protocol;
|
|||||||
public class GBDeviceCommandAppManagementResult extends GBDeviceCommand {
|
public class GBDeviceCommandAppManagementResult extends GBDeviceCommand {
|
||||||
public Result result = Result.UNKNOWN;
|
public Result result = Result.UNKNOWN;
|
||||||
public CommandType type = CommandType.UNKNOWN;
|
public CommandType type = CommandType.UNKNOWN;
|
||||||
|
public int token = -1;
|
||||||
|
|
||||||
public GBDeviceCommandAppManagementResult() {
|
public GBDeviceCommandAppManagementResult() {
|
||||||
commandClass = CommandClass.APP_MANAGEMENT_RES;
|
commandClass = CommandClass.APP_MANAGEMENT_RES;
|
||||||
@ -10,12 +11,14 @@ public class GBDeviceCommandAppManagementResult extends GBDeviceCommand {
|
|||||||
|
|
||||||
public enum CommandType {
|
public enum CommandType {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
INSTALL,
|
||||||
DELETE,
|
DELETE,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Result {
|
public enum Result {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
|
ACKNOLEDGE,
|
||||||
FAILURE,
|
FAILURE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,11 +74,19 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
|
|
||||||
static final int APPMANAGER_RES_SUCCESS = 1;
|
static final int APPMANAGER_RES_SUCCESS = 1;
|
||||||
|
|
||||||
static final short LENGTH_PREFIX = 4;
|
static final byte PUTBYTES_INIT = 1;
|
||||||
static final short LENGTH_SETTIME = 5;
|
static final byte PUTBYTES_SEND = 2;
|
||||||
static final short LENGTH_REMOVEAPP = 9;
|
static final byte PUTBYTES_COMMIT = 3;
|
||||||
static final short LENGTH_PHONEVERSION = 17;
|
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_MAGIC = 2; // increase this if pebble complains
|
||||||
static final byte PHONEVERSION_APPVERSION_MAJOR = 2;
|
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_LINUX = 4;
|
||||||
static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5;
|
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) {
|
private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) {
|
||||||
// Calculate length first
|
// Calculate length first
|
||||||
int length = LENGTH_PREFIX + 1;
|
int length = LENGTH_PREFIX + 1;
|
||||||
@ -265,6 +281,31 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
return buf.array();
|
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) {
|
public GBDeviceCommand decodeResponse(byte[] responseData) {
|
||||||
ByteBuffer buf = ByteBuffer.wrap(responseData);
|
ByteBuffer buf = ByteBuffer.wrap(responseData);
|
||||||
buf.order(ByteOrder.BIG_ENDIAN);
|
buf.order(ByteOrder.BIG_ENDIAN);
|
||||||
@ -322,15 +363,17 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
switch (pebbleCmd) {
|
switch (pebbleCmd) {
|
||||||
case APPMANAGER_GETAPPBANKSTATUS:
|
case APPMANAGER_GETAPPBANKSTATUS:
|
||||||
GBDeviceCommandAppInfo appInfoCmd = new GBDeviceCommandAppInfo();
|
GBDeviceCommandAppInfo appInfoCmd = new GBDeviceCommandAppInfo();
|
||||||
int banks = buf.getInt();
|
int slotCount = buf.getInt();
|
||||||
int banksUsed = buf.getInt();
|
int slotsUsed = buf.getInt();
|
||||||
byte[] appName = new byte[32];
|
byte[] appName = new byte[32];
|
||||||
byte[] appCreator = 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 id = buf.getInt();
|
||||||
int index = buf.getInt();
|
int index = buf.getInt();
|
||||||
|
slotInUse[index] = true;
|
||||||
buf.get(appName, 0, 32);
|
buf.get(appName, 0, 32);
|
||||||
buf.get(appCreator, 0, 32);
|
buf.get(appCreator, 0, 32);
|
||||||
int flags = buf.getInt();
|
int flags = buf.getInt();
|
||||||
@ -347,6 +390,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
Short appVersion = buf.getShort();
|
Short appVersion = buf.getShort();
|
||||||
appInfoCmd.apps[i] = new GBDeviceApp(id, index, new String(appName).trim(), new String(appCreator).trim(), appVersion.toString(), appType);
|
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;
|
cmd = appInfoCmd;
|
||||||
break;
|
break;
|
||||||
case APPMANAGER_REMOVEAPP:
|
case APPMANAGER_REMOVEAPP:
|
||||||
@ -369,6 +419,21 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,20 @@
|
|||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context="nodomain.freeyourgadget.gadgetbridge.AppInstallerActivity">
|
tools:context="nodomain.freeyourgadget.gadgetbridge.pebble.PebbleAppInstallerActivity">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/debugTextView"
|
android:id="@+id/debugTextView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Install"
|
||||||
|
android:id="@+id/installButton"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:enabled="false" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
Loading…
Reference in New Issue
Block a user