Merge remote-tracking branch 'origin/master' into low_battery_notification

Conflicts:
	app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
live-activity-data
Daniele Gobbetti 2015-08-24 17:48:17 +02:00
commit 6ebc727f97
21 changed files with 1092 additions and 52 deletions

View File

@ -1,5 +1,9 @@
###Changelog
####Version 0.5.1
* Pebble: support taking screenshot from Pebble Time
* Fix broken "find lost device" which was broken in 0.5.0
####Version 0.5.0
* Mi Band: fix setting wear location
* Pebble: experimental watchapp installation support for FW 3.x/Pebble Time

View File

@ -11,6 +11,8 @@ need to create an account and transmit any of your data to the vendor's servers.
[![Gadgetbridge on F-Droid](/Get_it_on_F-Droid.svg.png?raw=true "Download from F-Droid")](https://f-droid.org/repository/browse/?fdid=nodomain.freeyourgadget.gadgetbridge)
[List of changes](CHANGELOG.md)
## Features (Pebble)
* Incoming calls notification and display (caller, phone number)

View File

@ -12,8 +12,8 @@ android {
applicationId "nodomain.freeyourgadget.gadgetbridge"
minSdkVersion 19
targetSdkVersion 21
versionCode 21
versionName "0.5.0"
versionCode 22
versionName "0.5.1"
}
buildTypes {
release {
@ -31,9 +31,16 @@ android {
// optional path to report (default will be lint-results.html in the builddir)
htmlOutput file("$project.buildDir/reports/lint/lint.html")
}
testOptions {
unitTests.returnDefaultValues = true
}
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.9.5"
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:support-v4:21.0.3'
@ -41,9 +48,6 @@ dependencies {
compile 'org.slf4j:slf4j-api:1.7.7'
compile 'com.github.PhilJay:MPAndroidChart:2.1.0'
compile 'com.github.pfichtner:durationformatter:0.1.1'
testCompile 'junit:junit:4.12'
// testCompile "org.mockito:mockito-core:1.9.5"
}
check.dependsOn 'findbugs', 'pmd', 'lint'

View File

@ -25,6 +25,10 @@
android:name="android.hardware.bluetooth_le"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<application
android:name=".GBApplication"
android:allowBackup="true"

View File

@ -21,8 +21,8 @@ import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class GBApplication extends Application {
// Since this class must not log to slf4j, we use plain android.util.Log
@ -39,7 +39,7 @@ public class GBApplication extends Application {
}
protected DeviceService createDeviceService() {
return new GBDeviceService(this, DeviceCommunicationService.class);
return new GBDeviceService(this);
}
@Override
@ -55,6 +55,7 @@ public class GBApplication extends Application {
// StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(GBApplication.class);
GB.environment = GBEnvironment.createDeviceEnvironment();
mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
// for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();

View File

@ -0,0 +1,26 @@
package nodomain.freeyourgadget.gadgetbridge;
public class GBEnvironment {
private boolean localTest;
private boolean deviceTest;
public static GBEnvironment createLocalTestEnvironment() {
GBEnvironment env = new GBEnvironment();
env.localTest = true;
return env;
}
public static GBEnvironment createDeviceEnvironment() {
GBEnvironment env = new GBEnvironment();
return env;
}
public final boolean isTest() {
return localTest || deviceTest;
}
public boolean isLocalTest() {
return localTest;
}
}

View File

@ -12,17 +12,18 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class GBDeviceService implements DeviceService {
protected final Context mContext;
protected final Class<? extends Service> mServiceClass;
public GBDeviceService(Context context, Class<? extends Service> serviceClass) {
public GBDeviceService(Context context) {
mContext = context;
mServiceClass = serviceClass;
mServiceClass = DeviceCommunicationService.class;
}
private Intent createIntent() {
protected Intent createIntent() {
Intent startIntent = new Intent(mContext, mServiceClass);
return startIntent;
}
@ -174,7 +175,7 @@ public class GBDeviceService implements DeviceService {
@Override
public void onFindDevice(boolean start) {
Intent intent = createIntent().setAction(ACTION_FIND_DEVICE)
.putExtra(EXTRA_APP_UUID, start);
.putExtra(EXTRA_FIND_START, start);
invokeService(intent);
}

View File

@ -3,5 +3,5 @@ package nodomain.freeyourgadget.gadgetbridge.model;
public enum DeviceType {
UNKNOWN,
PEBBLE,
MIBAND
TEST, MIBAND
}

View File

@ -13,6 +13,7 @@ import android.net.Uri;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
@ -27,13 +28,48 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.*;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION_EMAIL;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION_GENERIC;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION_SMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REBOOT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
public class DeviceCommunicationService extends Service {
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
private boolean mStarted = false;
private DeviceSupportFactory mFactory;
private GBDevice mGBDevice = null;
private DeviceSupport mDeviceSupport;
@ -60,6 +96,7 @@ public class DeviceCommunicationService extends Service {
LOG.debug("DeviceCommunicationService is being created");
super.onCreate();
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
mFactory = new DeviceSupportFactory(this);
}
@Override
@ -107,32 +144,29 @@ public class DeviceCommunicationService extends Service {
start(); // ensure started
String btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (btDeviceAddress == null) {
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
} else {
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
if (sharedPrefs != null) { // may be null in test cases
if (btDeviceAddress == null) {
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
} else {
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
}
}
if (btDeviceAddress != null && !isConnecting() && !isConnected()) {
if (mDeviceSupport != null) {
mDeviceSupport.dispose();
mDeviceSupport = null;
}
setDeviceSupport(null);
try {
DeviceSupportFactory factory = new DeviceSupportFactory(this);
mDeviceSupport = factory.createDeviceSupport(btDeviceAddress);
if (mDeviceSupport != null) {
mGBDevice = mDeviceSupport.getDevice();
DeviceSupport deviceSupport = mFactory.createDeviceSupport(btDeviceAddress);
if (deviceSupport != null) {
setDeviceSupport(deviceSupport);
if (pair) {
mDeviceSupport.pair();
deviceSupport.pair();
} else {
mDeviceSupport.connect();
deviceSupport.connect();
}
}
} catch (Exception e) {
GB.toast(this, getString(R.string.cannot_connect, e.getMessage()), Toast.LENGTH_SHORT, GB.ERROR);
mDeviceSupport = null;
mGBDevice = null;
setDeviceSupport(null);
}
} else if (mGBDevice != null) {
// send an update at least
@ -231,6 +265,29 @@ public class DeviceCommunicationService extends Service {
return START_STICKY;
}
/**
* For testing!
* @param factory
*/
public void setDeviceSupportFactory(DeviceSupportFactory factory) {
mFactory = factory;
}
/**
* Disposes the current DeviceSupport instance (if any) and sets a new device support instance
* (if not null).
* @param deviceSupport
*/
private void setDeviceSupport(@Nullable DeviceSupport deviceSupport) {
if (deviceSupport != mDeviceSupport && mDeviceSupport != null) {
mDeviceSupport.dispose();
mDeviceSupport = null;
mGBDevice = null;
}
mDeviceSupport = deviceSupport;
mGBDevice = mDeviceSupport != null ? mDeviceSupport.getDevice() : null;
}
private void start() {
if (!mStarted) {
startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this));
@ -238,6 +295,10 @@ public class DeviceCommunicationService extends Service {
}
}
public boolean isStarted() {
return mStarted;
}
private boolean isConnected() {
return mGBDevice != null && mGBDevice.isConnected();
}
@ -258,9 +319,7 @@ public class DeviceCommunicationService extends Service {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
GB.setReceiversEnableState(false, this); // disable BroadcastReceivers
if (mDeviceSupport != null) {
mDeviceSupport.dispose();
}
setDeviceSupport(null);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(GB.NOTIFICATION_ID); // need to do this because the updated notification wont be cancelled when service stops
}

View File

@ -5,6 +5,7 @@ import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.widget.Toast;
import java.lang.reflect.Constructor;
import java.util.EnumSet;
import nodomain.freeyourgadget.gadgetbridge.GBException;
@ -26,10 +27,17 @@ public class DeviceSupportFactory {
public synchronized DeviceSupport createDeviceSupport(String deviceAddress) throws GBException {
DeviceSupport deviceSupport;
if (deviceAddress.indexOf(":") == deviceAddress.lastIndexOf(":")) { // only one colon
deviceSupport = createTCPDeviceSupport(deviceAddress);
int indexFirstColon = deviceAddress.indexOf(":");
if (indexFirstColon > 0) {
if (indexFirstColon == deviceAddress.lastIndexOf(":")) { // only one colon
deviceSupport = createTCPDeviceSupport(deviceAddress);
} else {
// multiple colons -- bt?
deviceSupport = createBTDeviceSupport(deviceAddress);
}
} else {
deviceSupport = createBTDeviceSupport(deviceAddress);
// no colon at all, maybe a class name?
deviceSupport = createClassNameDeviceSupport(deviceAddress);
}
if (deviceSupport != null) {
@ -41,6 +49,21 @@ public class DeviceSupportFactory {
return null;
}
private DeviceSupport createClassNameDeviceSupport(String className) throws GBException {
try {
Class<?> deviceSupportClass = Class.forName(className);
Constructor<?> constructor = deviceSupportClass.getConstructor();
DeviceSupport support = (DeviceSupport) constructor.newInstance();
// has to create the device itself
support.setContext(null, null, mContext);
return support;
} catch (ClassNotFoundException e) {
return null; // not a class, or not known at least
} catch (Exception e) {
throw new GBException("Error creating DeviceSupport instance for " + className, e);
}
}
private void checkBtAvailability() {
if (mBtAdapter == null) {
GB.toast(mContext.getString(R.string.bluetooth_is_not_supported_), Toast.LENGTH_SHORT, GB.WARN);

View File

@ -202,6 +202,97 @@ public class PebbleProtocol extends GBDeviceProtocol {
GBDeviceEventScreenshot mDevEventScreenshot = null;
int mScreenshotRemaining = -1;
//monochrome black + white
static final byte[] clut_pebble = {
0x00, 0x00, 0x00, 0x00,
(byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00
};
// linear BGR222 (6 bit, 64 entries)
static final byte[] clut_pebbletime = new byte[]{
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,
};
byte last_id = -1;
private ArrayList<UUID> tmpUUIDS = new ArrayList<>();
@ -921,34 +1012,38 @@ public class PebbleProtocol extends GBDeviceProtocol {
byte result = buf.get();
mDevEventScreenshot = new GBDeviceEventScreenshot();
int version = buf.getInt();
if (result != 0 || version != 1) { // pebble time not yet
if (result != 0) {
return null;
}
mDevEventScreenshot.width = buf.getInt();
mDevEventScreenshot.height = buf.getInt();
mDevEventScreenshot.bpp = 1;
mDevEventScreenshot.clut = new byte[]{
0x00, 0x00, 0x00, 0x00, (byte) 0xff,
(byte) 0xff, (byte) 0xff, 0x00
};
mScreenshotRemaining = (mDevEventScreenshot.width * mDevEventScreenshot.height) / 8;
if (mScreenshotRemaining > 50000) {
mScreenshotRemaining = -1; // ignore too big values
return null;
if (version == 1) {
mDevEventScreenshot.bpp = 1;
mDevEventScreenshot.clut = clut_pebble;
} else {
mDevEventScreenshot.bpp = 8;
mDevEventScreenshot.clut = clut_pebbletime;
}
mScreenshotRemaining = (mDevEventScreenshot.width * mDevEventScreenshot.height * mDevEventScreenshot.bpp) / 8;
mDevEventScreenshot.data = new byte[mScreenshotRemaining];
length -= 13;
}
if (mScreenshotRemaining == -1) {
return null;
}
for (int i = 0; i < length; i++) {
byte corrected = reverseBits(buf.get());
byte corrected = buf.get();
if (mDevEventScreenshot.bpp == 1) {
corrected = reverseBits(corrected);
} else {
corrected = (byte) (corrected & 0b00111111);
}
mDevEventScreenshot.data[mDevEventScreenshot.data.length - mScreenshotRemaining + i] = corrected;
}
mScreenshotRemaining -= length;
LOG.info("Screenshot remaining bytes " + mScreenshotRemaining);
if (mScreenshotRemaining == 0) {

View File

@ -24,6 +24,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
@ -43,6 +44,7 @@ public class GB {
public static final int INFO = 1;
public static final int WARN = 2;
public static final int ERROR = 3;
public static GBEnvironment environment;
public static Notification createNotification(String text, Context context) {
Intent notificationIntent = new Intent(context, ControlCenter.class);
@ -152,16 +154,16 @@ public class GB {
headerbuf.putInt(screenshot.width);
headerbuf.putInt(-screenshot.height);
headerbuf.putShort((short) 1); // planes
headerbuf.putShort((short) 1); // bit count
headerbuf.putShort((short) screenshot.bpp);
headerbuf.putInt(0); // compression
headerbuf.putInt(0); // length of pixeldata in byte (uncompressed=0)
headerbuf.putInt(0); // pixels per meter (x)
headerbuf.putInt(0); // pixels per meter (y)
headerbuf.putInt(2); // number of colors in CLUT
headerbuf.putInt(2); // numbers of used colors
headerbuf.putInt(screenshot.clut.length / 4); // number of colors in CLUT
headerbuf.putInt(0); // numbers of used colors
headerbuf.put(screenshot.clut);
fos.write(headerbuf.array());
int rowbytes = screenshot.width / 8;
int rowbytes = (screenshot.width * screenshot.bpp) / 8;
byte[] pad = new byte[rowbytes % 4];
for (int i = 0; i < screenshot.height; i++) {
fos.write(screenshot.data, rowbytes * i, rowbytes);
@ -225,6 +227,9 @@ public class GB {
* @param ex optional exception to be logged
*/
public static void toast(final Context context, final String message, final int displayTime, final int severity, final Throwable ex) {
if (env().isLocalTest()) {
return;
}
Looper mainLooper = Looper.getMainLooper();
if (Thread.currentThread() == mainLooper.getThread()) {
log(message, severity, ex);
@ -321,4 +326,8 @@ public class GB {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID_LOW_BATTERY, notification);
}
public static GBEnvironment env() {
return environment;
}
}

View File

@ -0,0 +1,97 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.app.Application;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockApplication;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockContext;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockPackageManager;
import nodomain.freeyourgadget.gadgetbridge.test.MockHelper;
public abstract class AbstractServiceTestCase<T extends Service> {
private static final int ID = -1; // currently not supported
private Class<T> mServiceClass;
private T mServiceInstance;
private Context mContext;
private Application mApplication;
private boolean wasStarted;
private PackageManager mPackageManager;
private NotificationManager mNotificationManager;
private MockHelper mMockHelper;
protected AbstractServiceTestCase(Class<T> serviceClass) {
mServiceClass = serviceClass;
Assert.assertNotNull(serviceClass);
}
public Context getContext() {
return mContext;
}
public T getServiceInstance() {
return mServiceInstance;
}
@Before
public void setUp() throws Exception {
mMockHelper = new MockHelper();
mPackageManager = createPackageManager();
mApplication = createApplication(mPackageManager);
mContext = createContext(mApplication);
mNotificationManager = mMockHelper.createNotificationManager(mContext);
mServiceInstance = createService(mServiceClass, mApplication, mNotificationManager);
mServiceInstance.onCreate();
}
@After
public void tearDown() throws Exception {
if (mServiceInstance != null) {
stopService();
}
}
public void startService(Intent intent) {
wasStarted = true;
mServiceInstance.onStartCommand(intent, Service.START_FLAG_REDELIVERY, ID);
}
public void stopService() {
mServiceInstance.onDestroy();
mServiceInstance = null;
}
protected Application createApplication(PackageManager packageManager) {
return new GBMockApplication(packageManager);
}
protected PackageManager createPackageManager() {
return new GBMockPackageManager();
}
protected Application getApplication() {
return mApplication;
}
protected Context createContext(final Application application) {
return new GBMockContext(application);
}
private T createService(Class<T> serviceClass, Application application, NotificationManager notificationManager) throws Exception {
T service = mMockHelper.createService(serviceClass, application);
mMockHelper.addSystemServiceTo(service, Context.NOTIFICATION_SERVICE, getNotificationService());
return service;
}
private NotificationManager getNotificationService() {
return mNotificationManager;
}
}

View File

@ -0,0 +1,82 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.content.Context;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class DeviceCommunicationServiceTestCase extends AbstractServiceTestCase<DeviceCommunicationService> {
private static final java.lang.String TEST_DEVICE_ADDRESS = TestDeviceSupport.class.getName();
/**
* Factory that always returns the mockSupport instance
*/
private class TestDeviceSupportFactory extends DeviceSupportFactory {
public TestDeviceSupportFactory(Context context) {
super(context);
}
@Override
public synchronized DeviceSupport createDeviceSupport(String deviceAddress) throws GBException {
return mockSupport;
}
}
private TestDeviceService mDeviceService;
@Mock
private TestDeviceSupport realSupport;
private TestDeviceSupport mockSupport;
public DeviceCommunicationServiceTestCase() {
super(DeviceCommunicationService.class);
}
@Before
public void setUp() throws Exception {
super.setUp();
mockSupport = null;
realSupport = new TestDeviceSupport();
realSupport.setContext(new GBDevice(TEST_DEVICE_ADDRESS, "Test Device", DeviceType.TEST), null, getContext());
mockSupport = Mockito.spy(realSupport);
getServiceInstance().setDeviceSupportFactory(new TestDeviceSupportFactory(getContext()));
mDeviceService = new TestDeviceService(this);
}
@Test
public void testStart() {
assertFalse("Service was already", getServiceInstance().isStarted());
mDeviceService.start();
assertTrue("Service should be started", getServiceInstance().isStarted());
}
@Test
public void ensureConnected() {
mDeviceService.connect(TEST_DEVICE_ADDRESS);
Mockito.verify(mockSupport, Mockito.times(1)).connect();
assertTrue(realSupport.getDevice().isInitialized());
}
@Test
public void testFindDevice() {
ensureConnected();
InOrder inOrder = Mockito.inOrder(mockSupport);
mDeviceService.onFindDevice(true);
mDeviceService.onFindDevice(false);
inOrder.verify(mockSupport, Mockito.times(1)).onFindDevice(true);
inOrder.verify(mockSupport, Mockito.times(1)).onFindDevice(false);
inOrder.verifyNoMoreInteractions();
}
}

View File

@ -0,0 +1,25 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.content.Intent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockIntent;
public class TestDeviceService extends GBDeviceService {
private final AbstractServiceTestCase<?> mTestCase;
public TestDeviceService(AbstractServiceTestCase<?> testCase) throws Exception {
super(testCase.getContext());
mTestCase = testCase;
}
@Override
protected Intent createIntent() {
return new GBMockIntent();
}
@Override
protected void invokeService(Intent intent) {
mTestCase.startService(intent);
}
}

View File

@ -0,0 +1,124 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
public class TestDeviceSupport extends AbstractDeviceSupport {
public TestDeviceSupport() {
}
@Override
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
gbDevice = new GBDevice(getClass().getName(), "Test Device", DeviceType.TEST);
super.setContext(gbDevice, btAdapter, context);
}
@Override
public boolean connect() {
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
return true;
}
@Override
public void dispose() {
}
@Override
public boolean useAutoConnect() {
return false;
}
@Override
public void pair() {
}
@Override
public void onSMS(String from, String body) {
}
@Override
public void onEmail(String from, String subject, String body) {
}
@Override
public void onGenericNotification(String title, String details) {
}
@Override
public void onSetTime() {
}
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
}
@Override
public void onSetCallState(@Nullable String number, @Nullable String name, ServiceCommand command) {
}
@Override
public void onSetMusicInfo(String artist, String album, String track) {
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public void onAppInfoReq() {
}
@Override
public void onAppStart(UUID uuid) {
}
@Override
public void onAppDelete(UUID uuid) {
}
@Override
public void onFetchActivityData() {
}
@Override
public void onReboot() {
}
@Override
public void onFindDevice(boolean start) {
}
@Override
public void onScreenshotReq() {
}
}

View File

@ -0,0 +1,27 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.content.Context;
import android.content.pm.PackageManager;
import android.test.mock.MockApplication;
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class GBMockApplication extends MockApplication {
private final PackageManager mPackageManager;
public GBMockApplication(PackageManager packageManager) {
GB.environment = GBEnvironment.createDeviceEnvironment().createLocalTestEnvironment();
mPackageManager = packageManager;
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
}

View File

@ -0,0 +1,24 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.test.mock.MockContext;
public class GBMockContext extends MockContext {
private final Application mApplication;
public GBMockContext(Application application) {
mApplication = application;
}
@Override
public Context getApplicationContext() {
return mApplication;
}
@Override
public PackageManager getPackageManager() {
return mApplication.getPackageManager();
}
}

View File

@ -0,0 +1,387 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class GBMockIntent extends Intent {
private String mAction;
private Map<String,Object> extras = new HashMap<>();
@NonNull
@Override
public Intent setAction(String action) {
mAction = action;
return this;
}
@Override
public String getAction() {
return mAction;
}
@NonNull
@Override
public Intent putExtra(String name, boolean value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, byte value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, char value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, short value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, int value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, long value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, float value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, double value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, String value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, CharSequence value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Parcelable value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Parcelable[] value) {
extras.put(name, value);
return this;
}
@Override
public Intent putParcelableArrayListExtra(String name, ArrayList<? extends Parcelable> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putIntegerArrayListExtra(String name, ArrayList<Integer> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putStringArrayListExtra(String name, ArrayList<String> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putCharSequenceArrayListExtra(String name, ArrayList<CharSequence> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Serializable value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, boolean[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, byte[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, short[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, char[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, int[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, long[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, float[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, double[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, String[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, CharSequence[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Bundle value) {
extras.put(name, value);
return this;
}
@Override
public boolean getBooleanExtra(String name, boolean defaultValue) {
if (extras.containsKey(name)) {
return (boolean) extras.get(name);
}
return defaultValue;
}
@Override
public byte getByteExtra(String name, byte defaultValue) {
if (extras.containsKey(name)) {
return (byte) extras.get(name);
}
return defaultValue;
}
@Override
public short getShortExtra(String name, short defaultValue) {
if (extras.containsKey(name)) {
return (short) extras.get(name);
}
return defaultValue;
}
@Override
public char getCharExtra(String name, char defaultValue) {
if (extras.containsKey(name)) {
return (char) extras.get(name);
}
return defaultValue;
}
@Override
public int getIntExtra(String name, int defaultValue) {
if (extras.containsKey(name)) {
return (int) extras.get(name);
}
return defaultValue;
}
@Override
public long getLongExtra(String name, long defaultValue) {
if (extras.containsKey(name)) {
return (long) extras.get(name);
}
return defaultValue;
}
@Override
public float getFloatExtra(String name, float defaultValue) {
if (extras.containsKey(name)) {
return (float) extras.get(name);
}
return defaultValue;
}
@Override
public double getDoubleExtra(String name, double defaultValue) {
if (extras.containsKey(name)) {
return (double) extras.get(name);
}
return defaultValue;
}
@Override
public CharSequence getCharSequenceExtra(String name) {
return (CharSequence) extras.get(name);
}
@Override
public <T extends Parcelable> T getParcelableExtra(String name) {
return (T) extras.get(name);
}
@Override
public Parcelable[] getParcelableArrayExtra(String name) {
return (Parcelable[]) extras.get(name);
}
@Override
public <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
return (ArrayList<T>) extras.get(name);
}
@Override
public Serializable getSerializableExtra(String name) {
return (Serializable) extras.get(name);
}
@Override
public ArrayList<Integer> getIntegerArrayListExtra(String name) {
return (ArrayList<Integer>) extras.get(name);
}
@Override
public ArrayList<String> getStringArrayListExtra(String name) {
return (ArrayList<String>) extras.get(name);
}
@Override
public ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
return (ArrayList<CharSequence>) extras.get(name);
}
@Override
public boolean[] getBooleanArrayExtra(String name) {
return (boolean[]) extras.get(name);
}
@Override
public byte[] getByteArrayExtra(String name) {
return (byte[]) extras.get(name);
}
@Override
public short[] getShortArrayExtra(String name) {
return (short[]) extras.get(name);
}
@Override
public char[] getCharArrayExtra(String name) {
return (char[]) extras.get(name);
}
@Override
public int[] getIntArrayExtra(String name) {
return (int[]) extras.get(name);
}
@Override
public long[] getLongArrayExtra(String name) {
return (long[]) extras.get(name);
}
@Override
public float[] getFloatArrayExtra(String name) {
return (float[]) extras.get(name);
}
@Override
public double[] getDoubleArrayExtra(String name) {
return (double[]) extras.get(name);
}
@Override
public String[] getStringArrayExtra(String name) {
return (String[]) extras.get(name);
}
@Override
public CharSequence[] getCharSequenceArrayExtra(String name) {
return (CharSequence[]) extras.get(name);
}
@Override
public String getStringExtra(String name) {
return (String) extras.get(name);
}
@Override
public String toString() {
return "GBMockIntent: " + mAction;
}
}

View File

@ -0,0 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.content.ComponentName;
import android.test.mock.MockPackageManager;
public class GBMockPackageManager extends MockPackageManager {
@Override
public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
// do nothing
}
}

View File

@ -0,0 +1,35 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.app.Application;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import junit.framework.Assert;
import org.mockito.Mockito;
import java.lang.reflect.Constructor;
public class MockHelper {
public <T extends Service> NotificationManager createNotificationManager(Context mContext) throws Exception {
Constructor<?>[] constructors = NotificationManager.class.getDeclaredConstructors();
constructors[0].setAccessible(true);
Class<?>[] parameterTypes = constructors[0].getParameterTypes();
return (NotificationManager) constructors[0].newInstance();
}
public <T extends Service> T createService(Class<T> serviceClass, Application application) throws Exception {
Constructor<T> constructor = serviceClass.getConstructor();
Assert.assertNotNull(constructor);
T realService = constructor.newInstance();
T mockedService = Mockito.spy(realService);
Mockito.when(mockedService.getApplicationContext()).thenReturn(application);
Mockito.when(mockedService.getPackageManager()).thenReturn(application.getPackageManager());
return mockedService;
}
public void addSystemServiceTo(Context context, String serviceName, Object service) {
Mockito.when(context.getSystemService(serviceName)).thenReturn(service);
}
}