Pebble: Emulator support

NOTE:
- supports aplite and basalt emulator
- needs recompilation of Gadgetbridge with INTERNET permission

TODO:
- fix disconnect issues
- emulator special packet support
- string localization
- ...
master
Andreas Shimokawa 2015-08-19 00:03:52 +02:00
parent 121baa19ec
commit a7796ecbc6
6 changed files with 130 additions and 24 deletions

View File

@ -5,7 +5,9 @@
<uses-sdk <uses-sdk
android:minSdkVersion="19" android:minSdkVersion="19"
android:targetSdkVersion="21" /> android:targetSdkVersion="21" />
<!--
<uses-permission android:name="android.permission.INTERNET" />
-->
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />

View File

@ -340,13 +340,23 @@ public class ControlCenter extends Activity {
} }
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
String miAddr = sharedPrefs.getString(MiBandConst.PREF_MIBAND_ADDRESS, null); String miAddr = sharedPrefs.getString(MiBandConst.PREF_MIBAND_ADDRESS, "");
if (miAddr != null && miAddr.length() > 0) { if (miAddr.length() > 0) {
GBDevice miDevice = new GBDevice(miAddr, "MI", DeviceType.MIBAND); GBDevice miDevice = new GBDevice(miAddr, "MI", DeviceType.MIBAND);
if (!availableDevices.contains(miDevice)) { if (!availableDevices.contains(miDevice)) {
availableDevices.add(miDevice); availableDevices.add(miDevice);
} }
} }
String pebbleEmuAddr = sharedPrefs.getString("pebble_emu_addr", "");
String pebbleEmuPort = sharedPrefs.getString("pebble_emu_port", "");
if (pebbleEmuAddr.length() >= 7 && pebbleEmuPort.length() > 0) {
GBDevice pebbleEmuDevice = new GBDevice(pebbleEmuAddr + ":" + pebbleEmuPort, "Pebble qemu", DeviceType.PEBBLE);
if (!availableDevices.contains(pebbleEmuDevice)) {
availableDevices.add(pebbleEmuDevice);
}
}
deviceList.retainAll(availableDevices); deviceList.retainAll(availableDevices);
for (GBDevice dev : availableDevices) { for (GBDevice dev : availableDevices) {
if (!deviceList.contains(dev)) { if (!deviceList.contains(dev)) {

View File

@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
@ -35,5 +36,37 @@ public class SettingsActivity extends AbstractSettingsActivity {
} }
}); });
final Preference pebbleEmuAddr = findPreference("pebble_emu_addr");
pebbleEmuAddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
preference.setSummary(newVal.toString());
return true;
} }
});
final Preference pebbleEmuPort = findPreference("pebble_emu_port");
pebbleEmuPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
preference.setSummary(newVal.toString());
return true;
}
});
}
@Override
protected String[] getPreferenceKeysWithSummary() {
return new String[]{
"pebble_emu_addr",
"pebble_emu_port"
};
}
} }

View File

@ -25,11 +25,16 @@ public class DeviceSupportFactory {
} }
public synchronized DeviceSupport createDeviceSupport(String deviceAddress) throws GBException { public synchronized DeviceSupport createDeviceSupport(String deviceAddress) throws GBException {
DeviceSupport deviceSupport = createBTDeviceSupport(deviceAddress); DeviceSupport deviceSupport;
if (deviceAddress.indexOf(":") == deviceAddress.lastIndexOf(":")) { // only one colon
deviceSupport = createTCPDeviceSupport(deviceAddress);
} else {
deviceSupport = createBTDeviceSupport(deviceAddress);
}
if (deviceSupport != null) { if (deviceSupport != null) {
return deviceSupport; return deviceSupport;
} }
// support for other kinds of transports
// no device found, check transport availability and warn // no device found, check transport availability and warn
checkBtAvailability(); checkBtAvailability();
@ -68,4 +73,16 @@ public class DeviceSupportFactory {
} }
return null; return null;
} }
private DeviceSupport createTCPDeviceSupport(String deviceAddress) throws GBException {
try {
GBDevice gbDevice = new GBDevice(deviceAddress, "Pebble qemu", DeviceType.PEBBLE); //FIXME, do not hardcode
DeviceSupport deviceSupport = new ServiceDeviceSupport(new PebbleSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
return deviceSupport;
} catch (Exception e) {
throw new GBException("cannot connect to " + deviceAddress, e); // FIXME: localize
}
}
} }

View File

@ -16,6 +16,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -37,14 +39,17 @@ public class PebbleIoThread extends GBDeviceIoThread {
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class); private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
private final PebbleProtocol mPebbleProtocol; private final PebbleProtocol mPebbleProtocol;
private final PebbleSupport mPebbleSupport; private final PebbleSupport mPebbleSupport;
private boolean mIsTCP = false;
private BluetoothAdapter mBtAdapter = null; private BluetoothAdapter mBtAdapter = null;
private BluetoothSocket mBtSocket = null; private BluetoothSocket mBtSocket = null;
private Socket mTCPSocket = null; // for emulator
private InputStream mInStream = null; private InputStream mInStream = null;
private OutputStream mOutStream = null; private OutputStream mOutStream = null;
private boolean mQuit = false; private boolean mQuit = false;
private boolean mIsConnected = false; private boolean mIsConnected = false;
private boolean mIsInstalling = false; private boolean mIsInstalling = false;
private int mConnectionAttempts = 0; private int mConnectionAttempts = 0;
private PBWReader mPBWReader = null; private PBWReader mPBWReader = null;
private int mAppInstallToken = -1; private int mAppInstallToken = -1;
private ZipInputStream mZis = null; private ZipInputStream mZis = null;
@ -66,14 +71,25 @@ public class PebbleIoThread extends GBDeviceIoThread {
@Override @Override
protected boolean connect(String btDeviceAddress) { protected boolean connect(String btDeviceAddress) {
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
ParcelUuid uuids[] = btDevice.getUuids();
GBDevice.State originalState = gbDevice.getState(); GBDevice.State originalState = gbDevice.getState();
try { try {
// contains only one ":"? then it is addr:port
int firstColon = btDeviceAddress.indexOf(":");
if (firstColon == btDeviceAddress.lastIndexOf(":")) {
mIsTCP = true;
InetAddress serverAddr = InetAddress.getByName(btDeviceAddress.substring(0, firstColon));
mTCPSocket = new Socket(serverAddr, Integer.parseInt(btDeviceAddress.substring(firstColon + 1)));
mInStream = mTCPSocket.getInputStream();
mOutStream = mTCPSocket.getOutputStream();
} else {
mIsTCP = false;
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
ParcelUuid uuids[] = btDevice.getUuids();
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid()); mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
mBtSocket.connect(); mBtSocket.connect();
mInStream = mBtSocket.getInputStream(); mInStream = mBtSocket.getInputStream();
mOutStream = mBtSocket.getOutputStream(); mOutStream = mBtSocket.getOutputStream();
}
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
gbDevice.setState(originalState); gbDevice.setState(originalState);
@ -187,11 +203,14 @@ public class PebbleIoThread extends GBDeviceIoThread {
break; break;
} }
} }
if (mIsTCP) {
mInStream.skip(6);
}
int bytes = mInStream.read(buffer, 0, 4); int bytes = mInStream.read(buffer, 0, 4);
if (bytes < 4) { if (bytes < 4) {
continue; continue;
} }
ByteBuffer buf = ByteBuffer.wrap(buffer); ByteBuffer buf = ByteBuffer.wrap(buffer);
buf.order(ByteOrder.BIG_ENDIAN); buf.order(ByteOrder.BIG_ENDIAN);
short length = buf.getShort(); short length = buf.getShort();
@ -214,6 +233,10 @@ public class PebbleIoThread extends GBDeviceIoThread {
bytes += mInStream.read(buffer, bytes + 4, length - bytes); bytes += mInStream.read(buffer, bytes + 4, length - bytes);
} }
if (mIsTCP) {
mInStream.skip(2);
}
GBDeviceEvent deviceEvent = mPebbleProtocol.decodeResponse(buffer); GBDeviceEvent deviceEvent = mPebbleProtocol.decodeResponse(buffer);
if (deviceEvent == null) { if (deviceEvent == null) {
LOG.info("unhandled message to endpoint " + endpoint + " (" + length + " bytes)"); LOG.info("unhandled message to endpoint " + endpoint + " (" + length + " bytes)");
@ -261,15 +284,31 @@ public class PebbleIoThread extends GBDeviceIoThread {
gbDevice.sendDeviceUpdateIntent(getContext()); gbDevice.sendDeviceUpdateIntent(getContext());
} }
private void write_real(byte[] bytes) {
try {
if (mIsTCP) {
ByteBuffer buf = ByteBuffer.allocate(bytes.length + 8);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) 0xfeed);
buf.putShort((short) 1);
buf.putShort((short) bytes.length);
buf.put(bytes);
buf.putShort((short) 0xbeef);
mOutStream.write(buf.array());
mOutStream.flush();
} else {
mOutStream.write(bytes);
mOutStream.flush();
}
} catch (IOException e) {
}
}
@Override @Override
synchronized public void write(byte[] bytes) { synchronized public void write(byte[] bytes) {
// block writes if app installation in in progress // block writes if app installation in in progress
if (mIsConnected && (!mIsInstalling || mInstallState == PebbleAppInstallState.WAIT_SLOT)) { if (mIsConnected && (!mIsInstalling || mInstallState == PebbleAppInstallState.WAIT_SLOT)) {
try { write_real(bytes);
mOutStream.write(bytes);
mOutStream.flush();
} catch (IOException e) {
}
} }
} }
@ -371,13 +410,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
if (!mIsInstalling) { if (!mIsInstalling) {
return; return;
} }
int length = bytes.length; LOG.info("got " + bytes.length + "bytes for writeInstallApp()");
LOG.info("got " + length + "bytes for writeInstallApp()"); write_real(bytes);
try {
mOutStream.write(bytes);
mOutStream.flush();
} catch (IOException e) {
}
} }
public void installApp(Uri uri, int appId) { public void installApp(Uri uri, int appId) {

View File

@ -77,6 +77,16 @@
android:key="pebble_force_untested" android:key="pebble_force_untested"
android:title="@string/pref_title_pebble_forceuntested" android:title="@string/pref_title_pebble_forceuntested"
android:summary="@string/pref_summary_pebble_forceuntested" /> android:summary="@string/pref_summary_pebble_forceuntested" />
<EditTextPreference
android:digits="0123456789."
android:key="pebble_emu_addr"
android:maxLength="15"
android:title="Emulator IP" />
<EditTextPreference
android:inputType="number"
android:key="pebble_emu_port"
android:maxLength="5"
android:title="Emulator Port" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
</PreferenceCategory> </PreferenceCategory>