Merge branch 'master' into live-activity-data

live-activity-data
cpfeiffer 2015-09-27 00:16:04 +02:00
commit 4250a002b4
104 changed files with 1873 additions and 615 deletions

View File

@ -1,19 +1,38 @@
###Changelog
####Version next
* Miband: allow the transfer of activity data without clearing MiBand's memory
####Version 0.6.1
* Pebble: Allow muting (blacklisting) Apps from within generic notifications on the watch
* Pebble: Detect all known Pebble Versions including new "chalk" platform (Pebble Time Round)
* Option to ignore phone calls (useful for Pebble Dialer)
* Mi Band: Added progressbar for activity data transfer and fixes for firmware transfer progressbar
* Bugfix for app blacklist (some checkboxes where wrongly drawn as checked)
####Version 0.6.0
* Pebble: WIP implementantion of PebbleKit Intents to make some 3rd party Android apps work with the Pebble (eg. Ventoo)
* Pebble: Option to set reconnection attempts in settings (one attempt usually takes about 5 seconds)
* Support contolling all audio players that react to media buttons (can be chosen in settings)
* Treat SMS as generic notification if set to "never" (can be blacklisted there also if desired)
* Treat Conversations messagess as chat messages, even if arrived via Pebble Intents (nice icon for Pebble FW 3.x)
* Allow opening firmware / app files from the download manager "app" (technically a content provider)
* Mi Band: whitelisted a few firmware versions
####Version 0.5.4
* Mi Band: allow the transfer of activity data without clearing MiBand's memory
* Pebble: for generic notifications use generic icon instead of SMS icons on FW 3.x (thanks @roidelapluie)
* Pebble: use different icons and background colors for specific groups of applications (chat, mail, etc) (thanks @roidelapluie)
* In settings, support blacklisting apps for generic notifications
####Version 0.5.3
* Pebble: For generic notifications, support dismissing individual notficications and "Open on Phone" feature (OG & PT)
* Pebble: Allow to treat K9 notifcations as generic notifications (if notification mode is set to never)
* Ignore QKSMS notificaions to avoid double notification for incoming SMS
* Pebble: For generic notifications, support dismissing individual notifications and "Open on Phone" feature (OG & PT)
* Pebble: Allow to treat K9 notifications as generic notifications (if notification mode is set to never)
* Ignore QKSMS notifications to avoid double notification for incoming SMS
* Improved UI of Firmware/App installer
* Device state again visible on lockscreen
* Date display and navigation now working properly for all charts
####Version 0.5.2
* Pebble: support "dismiss all" action also on Pebble Time/FW 3.x notifications
* Miband: show a notification when the battery is below 10%
* Mi Band: show a notification when the battery is below 10%
* Graphs are now using the same theme as the rest of the application
* Graphs now show when the device was not worn by the user (for devices that send this information)
* Remove unused settings option in charts view
@ -24,12 +43,12 @@
* Fix broken "find lost device" which was broken in 0.5.0
####Version 0.5.0
* Mi Band: fix setting wear location
* Mi Band: fix setting wear location
* Pebble: experimental watchapp installation support for FW 3.x/Pebble Time
* Pebble: support Pebble emulator via TCP connection (needs rebuild with INTERNET permission)
* Pebble: use SMS/EMAIL icons for FW 3.x/Pebble Time
* Pebble: do not throttle notifications
* Support going forward/backwards in time in the activy charts
* Support going forward/backwards in time in the activity charts
* Various small bugfixes to the App/Fw Installation Activity
####Version 0.4.6
@ -66,7 +85,7 @@
####Version 0.4.2
* Material style for Lollipop
* Support for finding a lost device (vibrate until cacelled)
* Support for finding a lost device (vibrate until cancelled)
* Mi Band: Support for vibration profiles, configurable for notifications
* Pebble: Support taking screenshots from the device context menu (Pebble Time not supported yet)
@ -74,7 +93,7 @@
* New icons, thanks xphnx!
* Improvements to Sleep Monitor charts
* Pebble: use new Sleep Monitor for Morpheuz (previously Mi Band only)
* Pebble: expermimental support for FW 3.x notification protocol
* Pebble: experimental support for FW 3.x notification protocol
* Pebble: dev option to force latest notification protocol
####Version 0.4.0
@ -82,13 +101,13 @@
* Pebble: Support launching of watchapps though the AppManager Activity
* Pebble: Support CM 12.1 default music app (Eleven)
* Pebble: Fix firmware installation when all 8 app slots are in use
* Pebble: Fix firmware installation when Pebble is in recovery mode
* Pebble: Fix firmware installation when Pebble is in recovery mode
* Pebble: Fix error when reinstalling apps, useful for upgrading/downgrading
* Mi Band: Make vibration count configurable for different kinds of Notifications
* Mi Band: Initial support for fetching activity data
* Support rebooting Mi Band/Pebble through the Debug Activity
* Add highly experimental sleep monitor (accessible via long press on a device)
* Fix Debug activity (SMS and E-Mail buttons were broken)
* Fix Debug activity (SMS and E-Mail buttons were broken)
* Add Turkish translation contributed by Tarik Sekmen
####Version 0.3.5
@ -100,7 +119,7 @@
####Version 0.3.4
* Pebble: Huge speedup for app/firmware installation.
* Pebble: Use a separate notification with progress bar for installation procedure
* Pebble: Bugfix for beeing stuck while waiting for a slot, when none is available
* Pebble: Bugfix for being stuck while waiting for a slot, when none is available
* Mi Band: Display connection status in notification (previously Pebble only)
####Version 0.3.3
@ -115,7 +134,7 @@
* Pebble: Check if firmware is compatible before allowing installation
####Version 0.3.1
* Mi Band: Fix for notifications only woking in Debug
* Mi Band: Fix for notifications only working in Debug
####Version 0.3.0
* Mi Band: Initial support (see README.md)
@ -129,7 +148,7 @@
* Experimental pbw installation support (watchfaces/apps)
* New icons for device and app lists
* Fix for device list not refreshing when bluetooth gets turned on
* Filter out annyoing low battery notifications
* Filter out annoying low battery notifications
* Fix for crash on some devices when creating a debug notification
* Lots of internal changes preparing multi device support
@ -137,16 +156,16 @@
* Fix for DST (summer time)
* Option to sync time on connect (enabled by default)
* Opening .pbw files with Gadgetbridge prints some package information
(This was not meant to be released yet, but the DST fix made a new release neccessary)
(This was not meant to be released yet, but the DST fix made a new release necessary)
####Version 0.1.4
* New AppManager shows installed Apps/Watchfaces (removal possible via context menu)
* Allow back navigation in ActionBar (Debug and AppMananger Activities)
* Make sure Intent broadcasts do not leave Gadgetbridge
* Show hint in the Main Activiy (tap to connect etc)
* Show hint in the Main Activity (tap to connect etc)
####Version 0.1.3
* Remove the connect button, list all suported devices and connect on tap instead
* Remove the connect button, list all supported devices and connect on tap instead
* Display connection status and firmware of connected devices in the device list
* Remove quit button from the service notification, put a quit item in the context menu instead
@ -157,7 +176,7 @@
####Version 0.1.1
* Fixed various bugs regarding K-9 Mail notifications.
* "Generic notification support" in Setting now opens Androids "Notifcaion access" dialog.
* "Generic notification support" in Setting now opens Androids "Notification access" dialog.
####Version 0.1.0
* Initial release

View File

@ -23,12 +23,12 @@ need to create an account and transmit any of your data to the vendor's servers.
* Support for generic notifications (above filtered out)
* Dismiss individial notifications or open corresponding app on phone from the action menu (generic notifications)
* Dismiss all notifications from the action menu (non-generic notifications)
* Music playback info (artist, album, track). Apollo and CM 12.1 Music App supported.
* Music playback info (artist, album, track)
* Music control: play/pause, next track, previous track, volume up, volume down
* List and remove installed apps/watchfaces
* Install .pbw files
* Install firmware from .pbz files
* Install watchfaces and firmware files (.pbw and .pbz)
* Take and share screenshots from the Pebble's screen
* PebbleKit support for 3rd Party Android Apps support (experimental).
* Morpheuz sleep data syncronization (experimental)
## Notes about the Pebble Time

View File

@ -12,8 +12,8 @@ android {
applicationId "nodomain.freeyourgadget.gadgetbridge"
minSdkVersion 19
targetSdkVersion 23
versionCode 24
versionName "0.5.3"
versionCode 27
versionName "0.6.1"
}
buildTypes {
release {
@ -46,7 +46,7 @@ dependencies {
compile 'com.android.support:support-v4:23.0.0'
compile 'com.github.tony19:logback-android-classic:1.1.1-3'
compile 'org.slf4j:slf4j-api:1.7.7'
compile 'com.github.PhilJay:MPAndroidChart:2.1.0'
compile 'com.github.PhilJay:MPAndroidChart:v2.1.4'
compile 'com.github.pfichtner:durationformatter:0.1.1'
}

View File

@ -66,6 +66,13 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
</activity>
<activity
android:name=".activities.AppBlacklistActivity"
android:label="@string/title_activity_appblacklist">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.SettingsActivity" />
</activity>
<activity
android:name=".activities.FwAppInstallerActivity"
android:label="@string/title_activity_fw_app_insaller">
@ -164,6 +171,14 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
</intent-filter>
<!-- to receive the firmwares from the donwload content provider -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/octet-stream" />
</intent-filter>
</activity>
<service
@ -221,8 +236,7 @@
android:name=".externalevents.MusicPlaybackReceiver"
android:enabled="false">
<intent-filter>
<action android:name="com.andrew.apollo.metachanged" />
<action android:name="com.cyanogenmod.eleven.metachanged" />
<action android:name="com.android.music.metachanged"/>
</intent-filter>
</receiver>
<receiver

View File

@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -31,6 +32,7 @@ public class GBApplication extends Application {
private static ActivityDatabaseHandler mActivityDatabaseHandler;
private static final Lock dbLock = new ReentrantLock();
private static DeviceService deviceService;
private static SharedPreferences sharedPrefs;
public GBApplication() {
context = this;
@ -46,6 +48,8 @@ public class GBApplication extends Application {
public void onCreate() {
super.onCreate();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// don't do anything here before we set up logging, otherwise
// slf4j may be implicitly initialized before we properly configured it.
setupLogging();
@ -57,14 +61,14 @@ public class GBApplication extends Application {
GB.environment = GBEnvironment.createDeviceEnvironment();
mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
loadBlackList();
// for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();
// db.close();
}
public static boolean isFileLoggingEnabled() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
return prefs.getBoolean("log_to_file", false);
return sharedPrefs.getBoolean("log_to_file", false);
}
private void setupLogging() {
@ -101,9 +105,10 @@ public class GBApplication extends Application {
* when that was not successful
* If acquiring was successful, callers must call #releaseDB when they
* are done (from the same thread that acquired the lock!
*
* @return the DBHandler
* @see #releaseDB()
* @throws GBException
* @see #releaseDB()
*/
public static DBHandler acquireDB() throws GBException {
try {
@ -118,6 +123,7 @@ public class GBApplication extends Application {
/**
* Releases the database lock.
*
* @throws IllegalMonitorStateException if the current thread is not owning the lock
* @see #acquireDB()
*/
@ -128,4 +134,36 @@ public class GBApplication extends Application {
public static boolean isRunningLollipopOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static HashSet<String> blacklist = null;
public static void loadBlackList() {
blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
if (blacklist == null) {
blacklist = new HashSet<>();
}
}
public static void saveBlackList() {
SharedPreferences.Editor editor = sharedPrefs.edit();
if (blacklist.isEmpty()) {
editor.putStringSet("package_blacklist", null);
} else {
editor.putStringSet("package_blacklist", blacklist);
}
editor.apply();
}
public static void addToBlacklist(String packageName) {
if (!blacklist.contains(packageName)) {
blacklist.add(packageName);
saveBlackList();
}
}
public static synchronized void removeFromBlacklist(String packageName) {
blacklist.remove(packageName);
saveBlackList();
}
}

View File

@ -4,12 +4,15 @@ public class GBException extends Exception {
public GBException(String message, Throwable cause) {
super(message, cause);
}
public GBException(String message) {
super(message);
}
public GBException(Throwable cause) {
super(cause);
}
public GBException() {
super();
}

View File

@ -1,13 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.support.v4.app.Fragment;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
/**
* Abstract base class for fragments. Provides hooks that are called when
* the fragment is made visible and invisible in the activity. also allows
* the fragment to define the title to be shown in the activity.
*
* @see AbstractGBFragmentActivity
*/
public abstract class AbstractGBFragment extends Fragment {
@ -25,6 +26,7 @@ public abstract class AbstractGBFragment extends Fragment {
/**
* Called when this fragment has been scrolled out of the activity.
*
* @see #isVisibleInActivity()
* @see #onMadeVisibleInActivity()
*/
@ -54,6 +56,7 @@ public abstract class AbstractGBFragment extends Fragment {
/**
* Internal
*
* @hide
*/
public void onMadeVisibleInActivityInternal() {

View File

@ -1,20 +1,15 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Context;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.util.AttributeSet;
import android.view.View;
/**
* A base activity that supports paging through fragments by swiping.
* Subclasses will have to add a ViewPager to their layout and add something
* like this to hook it to the fragments:
*
* <p/>
* <pre>
* // Set up the ViewPager with the sections adapter.
* ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
@ -50,6 +45,7 @@ public abstract class AbstractGBFragmentActivity extends FragmentActivity {
/**
* Creates a PagerAdapter that will create the fragments to be used with this
* activity. The fragments should typically extend AbstractGBFragment
*
* @param fragmentManager
* @return
*/

View File

@ -8,9 +8,9 @@ import android.view.MenuItem;
import android.widget.CheckBox;
import android.widget.TimePicker;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
public class AlarmDetails extends Activity {

View File

@ -0,0 +1,122 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class AppBlacklistActivity extends Activity {
private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ControlCenter.ACTION_QUIT)) {
finish();
}
}
};
private SharedPreferences sharedPrefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appblacklist);
getActionBar().setDisplayHomeAsUpEnabled(true);
final PackageManager pm = getPackageManager();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
ListView appListView = (ListView) findViewById(R.id.appListView);
final ArrayAdapter<ApplicationInfo> adapter = new ArrayAdapter<ApplicationInfo>(this, R.layout.item_with_checkbox, packageList) {
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_with_checkbox, parent, false);
}
ApplicationInfo appInfo = packageList.get(position);
TextView deviceAppVersionAuthorLabel = (TextView) view.findViewById(R.id.item_details);
TextView deviceAppNameLabel = (TextView) view.findViewById(R.id.item_name);
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
CheckBox checkbox = (CheckBox) view.findViewById(R.id.item_checkbox);
deviceAppVersionAuthorLabel.setText(appInfo.packageName);
deviceAppNameLabel.setText(appInfo.loadLabel(pm));
deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName));
return view;
}
};
appListView.setAdapter(adapter);
appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View v, int position, long id) {
String packageName = packageList.get(position).packageName;
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
checkBox.toggle();
if (checkBox.isChecked()) {
GBApplication.addToBlacklist(packageName);
} else {
GBApplication.removeFromBlacklist(packageName);
}
}
});
IntentFilter filter = new IntentFilter();
filter.addAction(ControlCenter.ACTION_QUIT);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
}
@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() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
}

View File

@ -5,7 +5,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.view.ContextMenu;
@ -27,8 +29,6 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@ -54,14 +54,30 @@ public class AppManagerActivity extends Activity {
appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType));
}
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps());
}
mGBDeviceAppAdapter.notifyDataSetChanged();
}
}
};
final List<GBDeviceApp> appList = new ArrayList<>();
private SharedPreferences sharedPrefs;
private final List<GBDeviceApp> appList = new ArrayList<>();
private GBDeviceAppAdapter mGBDeviceAppAdapter;
private GBDeviceApp selectedApp = null;
private List<GBDeviceApp> getSystemApps() {
List<GBDeviceApp> systemApps = new ArrayList<>();
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.UNKNOWN));
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.UNKNOWN));
return systemApps;
}
private List<GBDeviceApp> getCachedApps() {
List<GBDeviceApp> cachedAppList = new ArrayList<>();
try {
@ -84,6 +100,9 @@ public class AppManagerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_appmanager);
getActionBar().setDisplayHomeAsUpEnabled(true);
@ -95,15 +114,16 @@ public class AppManagerActivity extends Activity {
@Override
public void onItemClick(AdapterView parent, View v, int position, long id) {
UUID uuid = appList.get(position).getUUID();
GBApplication.deviceService().onAppStart(uuid);
GBApplication.deviceService().onAppStart(uuid, true);
}
});
registerForContextMenu(appListView);
List<GBDeviceApp> cachedApps = getCachedApps();
for (GBDeviceApp app : cachedApps) {
appList.add(app);
appList.addAll(getCachedApps());
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps());
}
IntentFilter filter = new IntentFilter();

View File

@ -12,10 +12,9 @@ import java.util.HashSet;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;

View File

@ -37,7 +37,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;

View File

@ -26,6 +26,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -70,17 +72,25 @@ public class DebugActivity extends Activity {
sendSMSButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onSMS(getResources().getText(R.string.app_name).toString(), editContent.getText().toString());
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.sender = getResources().getText(R.string.app_name).toString();
notificationSpec.body = editContent.getText().toString();
notificationSpec.type = NotificationType.SMS;
notificationSpec.id = -1;
GBApplication.deviceService().onNotification(notificationSpec);
}
});
sendEmailButton = (Button) findViewById(R.id.sendEmailButton);
sendEmailButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onEmail(
getResources().getText(R.string.app_name).toString(),
getResources().getText(R.string.test).toString(),
editContent.getText().toString());
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.sender = getResources().getText(R.string.app_name).toString();
notificationSpec.subject = editContent.getText().toString();
notificationSpec.body = editContent.getText().toString();
notificationSpec.type = NotificationType.EMAIL;
notificationSpec.id = -1;
GBApplication.deviceService().onNotification(notificationSpec);
}
});

View File

@ -24,13 +24,13 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
public class DiscoveryActivity extends Activity implements AdapterView.OnItemClickListener {
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);

View File

@ -12,7 +12,6 @@ import android.support.v4.content.LocalBroadcastManager;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

View File

@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
public interface InstallActivity {
void setInfoText(String text);
void setInstallEnabled(boolean enable);
void clearInstallItems();

View File

@ -1,10 +1,15 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.support.v4.content.LocalBroadcastManager;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
@ -36,6 +41,15 @@ public class SettingsActivity extends AbstractSettingsActivity {
}
});
pref = findPreference("pref_key_blacklist");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent enableIntent = new Intent(SettingsActivity.this, AppBlacklistActivity.class);
startActivity(enableIntent);
return true;
}
});
final Preference pebbleEmuAddr = findPreference("pebble_emu_addr");
pebbleEmuAddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
@ -59,13 +73,43 @@ public class SettingsActivity extends AbstractSettingsActivity {
}
});
// Get all receivers of Media Buttons
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
PackageManager pm = getPackageManager();
List<ResolveInfo> mediaReceivers = pm.queryBroadcastReceivers(mediaButtonIntent,
PackageManager.GET_INTENT_FILTERS | PackageManager.GET_RESOLVED_FILTER);
CharSequence[] newEntries = new CharSequence[mediaReceivers.size() + 1];
CharSequence[] newValues = new CharSequence[mediaReceivers.size() + 1];
newEntries[0] = getString(R.string.pref_default);
newValues[0] = "default";
int i = 1;
for (ResolveInfo resolveInfo : mediaReceivers) {
newEntries[i] = resolveInfo.activityInfo.loadLabel(pm);
newValues[i] = resolveInfo.activityInfo.packageName;
i++;
}
final ListPreference audioPlayer = (ListPreference) findPreference("audio_player");
audioPlayer.setEntries(newEntries);
audioPlayer.setEntryValues(newValues);
audioPlayer.setDefaultValue(newValues[0]);
}
@Override
protected String[] getPreferenceKeysWithSummary() {
return new String[]{
"audio_player",
"notification_mode_calls",
"notification_mode_sms",
"notification_mode_k9mail",
"pebble_emu_addr",
"pebble_emu_port"
"pebble_emu_port",
"pebble_reconnect_attempts",
};
}

View File

@ -376,7 +376,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
String dateStringFrom = "";
String dateStringTo = "";
LOG.info("number of samples:" + samples.size());
LOG.info("" + getTitle() + ": number of samples:" + samples.size());
if (samples.size() > 1) {
float movement_divisor;
boolean annotate = true;

View File

@ -125,8 +125,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
legendLabels.add(akDeepSleep.label);
legendColors.add(akNotWorn.color);
legendLabels.add(akNotWorn.label);
chart.getLegend().setColors(legendColors);
chart.getLegend().setLabels(legendLabels);
chart.getLegend().setCustom(legendColors, legendLabels);
}
@Override

View File

@ -14,10 +14,13 @@ public interface ChartsHost {
GBDevice getDevice();
void setStartDate(Date startDate);
void setEndDate(Date endDate);
Date getStartDate();
Date getEndDate();
void setDateInfo(String dateInfo);
ViewGroup getDateBar();

View File

@ -1,31 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.charts.BarChart;
/**
* A BarChart with some specific customization, like
* <li>using a custom legend renderer that always uses fixed labels and colors</li>
*/
public class CustomBarChart extends BarChart {
public CustomBarChart(Context context) {
super(context);
}
public CustomBarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomBarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mLegendRenderer = new CustomLegendRenderer(getViewPortHandler(), getLegend());
}
}

View File

@ -1,42 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.graphics.Typeface;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.renderer.LegendRenderer;
import com.github.mikephil.charting.utils.ViewPortHandler;
/**
* A legend renderer that does *not* calculate the labels and colors automatically
* from the data sets or the data entries.
* <p/>
* Instead, they have to be provided manually, because otherwise the legend will
* be empty.
*/
public class CustomLegendRenderer extends LegendRenderer {
public CustomLegendRenderer(ViewPortHandler viewPortHandler, Legend legend) {
super(viewPortHandler, legend);
}
@Override
public void computeLegend(ChartData<?> data) {
if (!mLegend.isEnabled()) {
return;
}
// don't call super to avoid computing colors and labels
// super.computeLegend(data);
Typeface tf = mLegend.getTypeface();
if (tf != null)
mLegendLabelPaint.setTypeface(tf);
mLegendLabelPaint.setTextSize(mLegend.getTextSize());
mLegendLabelPaint.setColor(mLegend.getTextColor());
// calculate all dimensions of the mLegend
mLegend.calculateDimensions(mLegendLabelPaint);
}
}

View File

@ -16,7 +16,8 @@ import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.utils.ValueFormatter;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.github.mikephil.charting.utils.ViewPortHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -71,7 +72,7 @@ public class SleepChartFragment extends AbstractChartFragment {
PieDataSet set = new PieDataSet(entries, "");
set.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
}
});
@ -166,8 +167,7 @@ public class SleepChartFragment extends AbstractChartFragment {
legendLabels.add(akLightSleep.label);
legendColors.add(akDeepSleep.color);
legendLabels.add(akDeepSleep.label);
chart.getLegend().setColors(legendColors);
chart.getLegend().setLabels(legendLabels);
chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}

View File

@ -133,6 +133,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
GBDevice device = getChartsHost().getDevice();
if (device != null) {
// TODO: eek, this is device specific!
mTargetSteps = MiBandCoordinator.getFitnessGoal(device.getAddress());
}
@ -195,8 +196,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
List<String> legendLabels = new ArrayList<>(1);
legendColors.add(akActivity.color);
legendLabels.add(getContext().getString(R.string.chart_steps));
chart.getLegend().setColors(legendColors);
chart.getLegend().setLabels(legendLabels);
chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}

View File

@ -10,10 +10,10 @@ import android.widget.TextView;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {

View File

@ -15,9 +15,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;

View File

@ -13,8 +13,8 @@ import android.widget.TextView;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {

View File

@ -10,8 +10,8 @@ import android.widget.TextView;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class GBDeviceAppAdapter extends ArrayAdapter<GBDeviceApp> {

View File

@ -12,13 +12,13 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
@ -179,9 +179,11 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
}
ArrayList<ActivitySample> samples = new ArrayList<ActivitySample>();
final String where = "(provider=" + provider.getID() + " and timestamp>=" + timestamp_from + " and timestamp<=" + timestamp_to + getWhereClauseFor(activityTypes, provider) + ")";
LOG.info("Activity query where: " + where);
final String order = "timestamp";
try (SQLiteDatabase db = this.getReadableDatabase()) {
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) {
LOG.info("Activity query result: " + cursor.getCount() + " samples");
if (cursor.moveToFirst()) {
do {
GBActivitySample sample = new GBActivitySample(

View File

@ -4,9 +4,9 @@ import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public abstract class DBAccess extends AsyncTask {
private final String mTask;

View File

@ -6,8 +6,8 @@ import android.database.sqlite.SQLiteOpenHelper;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
public interface DBHandler {
public SQLiteOpenHelper getHelper();

View File

@ -0,0 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import java.util.UUID;
public class GBDeviceEventAppMessage extends GBDeviceEvent {
public UUID appUUID;
public int id;
public String message;
}

View File

@ -9,6 +9,7 @@ public class GBDeviceEventNotificationControl extends GBDeviceEvent {
UNKNOWN,
DISMISS,
DISMISS_ALL,
OPEN
OPEN,
MUTE
}
}

View File

@ -6,8 +6,9 @@ import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
/**
* Specifies all events that GadgetBridge intends to send to the gadget device.
@ -15,11 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
* Implementations need to send/encode event to the connected device.
*/
public interface EventHandler {
void onSMS(String from, String body);
void onEmail(String from, String subject, String body);
void onGenericNotification(String title, String details, int handle);
void onNotification(NotificationSpec notificationSpec);
void onSetTime();
@ -35,7 +32,7 @@ public interface EventHandler {
void onAppInfoReq();
void onAppStart(UUID uuid);
void onAppStart(UUID uuid, boolean start);
void onAppDelete(UUID uuid);

View File

@ -4,11 +4,11 @@ import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class UnknownDeviceCoordinator implements DeviceCoordinator {
private final UnknownSampleProvider sampleProvider;

View File

@ -9,18 +9,16 @@ import android.preference.PreferenceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class MiBandCoordinator implements DeviceCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class);

View File

@ -4,28 +4,51 @@ import java.util.Calendar;
import java.util.GregorianCalendar;
public class MiBandDateConverter {
/**
* Creates a calendar object representing the current date and time.
*/
public static GregorianCalendar createCalendar() {
return new GregorianCalendar();
}
/**
* uses the standard algorithm to convert bytes received from the MiBand to a Calendar object
*
* @param value
* @return
*/
public static GregorianCalendar rawBytesToCalendar(byte[] value) {
GregorianCalendar timestamp = new GregorianCalendar();
if (value.length == 6) {
timestamp.set(Calendar.YEAR, (2000 + value[0]));
timestamp.set(Calendar.MONTH, value[1]);
timestamp.set(Calendar.DATE, value[2]);
timestamp.set(Calendar.HOUR_OF_DAY, value[3]);
timestamp.set(Calendar.MINUTE, value[4]);
timestamp.set(Calendar.SECOND, value[5]);
return rawBytesToCalendar(value, 0);
}
return createCalendar();
}
/**
* uses the standard algorithm to convert bytes received from the MiBand to a Calendar object
*
* @param value
* @return
*/
public static GregorianCalendar rawBytesToCalendar(byte[] value, int offset) {
if (value.length - offset >= 6) {
GregorianCalendar timestamp = new GregorianCalendar(
value[offset] + 2000,
value[offset + 1],
value[offset + 2],
value[offset + 3],
value[offset + 4],
value[offset + 5]);
return timestamp;
}
return timestamp;
return createCalendar();
}
/**
* uses the standard algorithm to convert a Calendar object to a byte array to send to MiBand
*
* @param timestamp
* @return
*/

View File

@ -12,7 +12,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class MiBandFWHelper {
@ -32,6 +31,10 @@ public class MiBandFWHelper {
16779547, //1.0.9.27 tested by developer
16779568, //1.0.9.48 tested by developer
16779585, //1.0.9.65 tested by developer
16779779, //1.0.10.3 reported on the wiki
16779782, //1.0.10.6 reported on the wikiew
16779787, //1.0.10.11 tested by developer
//16779790, //1.0.10.14 reported on the wiki (vibration does not work currently)
};
public MiBandFWHelper(Uri uri, Context context) throws IOException {
@ -44,7 +47,7 @@ public class MiBandFWHelper {
throw new IOException("Firmware has a filename that looks like a Pebble app/firmware.");
}
try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))){
try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))) {
this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB
if (fw.length <= firmwareVersionMajor || fw[firmwareVersionMajor] != 1) {
throw new IOException("Firmware major version should be 1, probably this isn't a MiBand firmware.");

View File

@ -13,12 +13,11 @@ import android.widget.TextView;
import android.widget.Toast;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
public class MiBandPairingActivity extends Activity {

View File

@ -5,9 +5,9 @@ import android.os.Bundle;
import android.preference.Preference;
import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_GENERIC;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
@ -15,8 +15,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.OR
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_PEBBLEMSG;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_SMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_WEARSIDE;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_GENDER;

View File

@ -1,7 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class MiBandSampleProvider implements SampleProvider {
public static final byte TYPE_DEEP_SLEEP = 5;

View File

@ -179,12 +179,13 @@ public class MiBandService {
*/
/* MODES: unknown
/* MODES: probably related to the sample data structure
*/
public static final MODE_REGULAR_DATA_LEN_BYTE = 0x0t;
public static final byte MODE_REGULAR_DATA_LEN_BYTE = 0x0;
public static final MODE_REGULAR_DATA_LEN_MINITE = 0x1t
*/
// was MODE_REGULAR_DATA_LEN_MINITE
public static final byte MODE_REGULAR_DATA_LEN_MINUTE = 0x1;
/* PROFILE: unknown

View File

@ -1,7 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class MorpheuzSampleProvider implements SampleProvider {
// raw types

View File

@ -39,7 +39,17 @@ public class PBWInstallHandler implements InstallHandler {
return;
}
mPBWReader = new PBWReader(mUri, mContext, device.getHardwareVersion().equals("dvt") ? "basalt" : "aplite");
String hwRev = device.getHardwareVersion();
String platformName;
if (hwRev.startsWith("snowy")) {
platformName = "basalt";
} else if (hwRev.startsWith("spalding")) {
platformName = "chalk";
} else {
platformName = "aplite";
}
mPBWReader = new PBWReader(mUri, mContext, platformName);
if (!mPBWReader.isValid()) {
installActivity.setInfoText("pbw/pbz is broken or incompatible with your Hardware or Firmware.");
installActivity.setInstallEnabled(false);

View File

@ -59,7 +59,7 @@ public class PBWReader {
public PBWReader(Uri uri, Context context, String platform) {
String platformDir = "";
if (!uri.toString().endsWith(".pbz") && platform.equals("basalt")) {
if (!uri.toString().endsWith(".pbz") && !platform.equals("aplite")) {
platformDir = platform + "/";
}

View File

@ -0,0 +1,72 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
public final class PebbleColor {
public static final byte Black = (byte) 0b11000000;
public static final byte OxfordBlue = (byte) 0b11000001;
public static final byte DukeBlue = (byte) 0b11000010;
public static final byte Blue = (byte) 0b11000011;
public static final byte DarkGreen = (byte) 0b11000100;
public static final byte MidnightGreen = (byte) 0b11000101;
public static final byte CobaltBlue = (byte) 0b11000110;
public static final byte BlueMoon = (byte) 0b11000111;
public static final byte IslamicGreen = (byte) 0b11001000;
public static final byte JaegerGreen = (byte) 0b11001001;
public static final byte TiffanyBlue = (byte) 0b11001010;
public static final byte VividCerulean = (byte) 0b11001011;
public static final byte Green = (byte) 0b11001100;
public static final byte Malachite = (byte) 0b11001101;
public static final byte MediumSpringGreen = (byte) 0b11001110;
public static final byte Cyan = (byte) 0b11001111;
public static final byte BulgarianRose = (byte) 0b11010000;
public static final byte ImperialPurple = (byte) 0b11010001;
public static final byte Indigo = (byte) 0b11010010;
public static final byte ElectricUltramarine = (byte) 0b11010011;
public static final byte ArmyGreen = (byte) 0b11010100;
public static final byte DarkGray = (byte) 0b11010101;
public static final byte Liberty = (byte) 0b11010110;
public static final byte VeryLightBlue = (byte) 0b11010111;
public static final byte KellyGreen = (byte) 0b11011000;
public static final byte MayGreen = (byte) 0b11011001;
public static final byte CadetBlue = (byte) 0b11011010;
public static final byte PictonBlue = (byte) 0b11011011;
public static final byte BrightGreen = (byte) 0b11011100;
public static final byte ScreaminGreen = (byte) 0b11011101;
public static final byte MediumAquamarine = (byte) 0b11011110;
public static final byte ElectricBlue = (byte) 0b11011111;
public static final byte DarkCandyAppleRed = (byte) 0b11100000;
public static final byte JazzberryJam = (byte) 0b11100001;
public static final byte Purple = (byte) 0b11100010;
public static final byte VividViolet = (byte) 0b11100011;
public static final byte WindsorTan = (byte) 0b11100100;
public static final byte RoseVale = (byte) 0b11100101;
public static final byte Purpureus = (byte) 0b11100110;
public static final byte LavenderIndigo = (byte) 0b11100111;
public static final byte Limerick = (byte) 0b11101000;
public static final byte Brass = (byte) 0b11101001;
public static final byte LightGray = (byte) 0b11101010;
public static final byte BabyBlueEyes = (byte) 0b11101011;
public static final byte SpringBud = (byte) 0b11101100;
public static final byte Inchworm = (byte) 0b11101101;
public static final byte MintGreen = (byte) 0b11101110;
public static final byte Celeste = (byte) 0b11101111;
public static final byte Red = (byte) 0b11110000;
public static final byte Folly = (byte) 0b11110001;
public static final byte FashionMagenta = (byte) 0b11110010;
public static final byte Magenta = (byte) 0b11110011;
public static final byte Orange = (byte) 0b11110100;
public static final byte SunsetOrange = (byte) 0b11110101;
public static final byte BrilliantRose = (byte) 0b11110110;
public static final byte ShockingPink = (byte) 0b11110111;
public static final byte ChromeYellow = (byte) 0b11111000;
public static final byte Rajah = (byte) 0b11111001;
public static final byte Melon = (byte) 0b11111010;
public static final byte RichBrilliantLavender = (byte) 0b11111011;
public static final byte Yellow = (byte) 0b11111100;
public static final byte Icterine = (byte) 0b11111101;
public static final byte PastelYellow = (byte) 0b11111110;
public static final byte White = (byte) 0b11111111;
public static final byte Clear = (byte) 0b00000000;
}

View File

@ -0,0 +1,95 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
public final class PebbleIconID {
public static final int NOTIFICATION_GENERIC = 1;
public static final int TIMELINE_MISSED_CALL = 2;
public static final int NOTIFICATION_REMINDER = 3;
public static final int NOTIFICATION_FLAG = 4;
public static final int NOTIFICATION_WHATSAPP = 5;
public static final int NOTIFICATION_TWITTER = 6;
public static final int NOTIFICATION_TELEGRAM = 7;
public static final int NOTIFICATION_GOOGLE_HANGOUTS = 8;
public static final int NOTIFICATION_GMAIL = 9;
public static final int NOTIFICATION_FACEBOOK_MESSENGER = 10;
public static final int NOTIFICATION_FACEBOOK = 11;
public static final int AUDIO_CASSETTE = 12;
public static final int ALARM_CLOCK = 13;
public static final int TIMELINE_WEATHER = 14;
public static final int TIMELINE_SUN = 16;
public static final int TIMELINE_SPORTS = 17;
public static final int GENERIC_EMAIL = 19;
public static final int AMERICAN_FOOTBALL = 20;
public static final int TIMELINE_CALENDAR = 21;
public static final int TIMELINE_BASEBALL = 22;
public static final int BIRTHDAY_EVENT = 23;
public static final int CAR_RENTAL = 24;
public static final int CLOUDY_DAY = 25;
public static final int CRICKET_GAME = 26;
public static final int DINNER_RESERVATION = 27;
public static final int GENERIC_WARNING = 28;
public static final int GLUCOSE_MONITOR = 29;
public static final int HOCKEY_GAME = 30;
public static final int HOTEL_RESERVATION = 31;
public static final int LIGHT_RAIN = 32;
public static final int LIGHT_SNOW = 33;
public static final int MOVIE_EVENT = 34;
public static final int MUSIC_EVENT = 35;
public static final int NEWS_EVENT = 36;
public static final int PARTLY_CLOUDY = 37;
public static final int PAY_BILL = 38;
public static final int RADIO_SHOW = 39;
public static final int SCHEDULED_EVENT = 40;
public static final int SOCCER_GAME = 41;
public static final int STOCKS_EVENT = 42;
public static final int RESULT_DELETED = 43;
public static final int CHECK_INTERNET_CONNECTION = 44;
public static final int GENERIC_SMS = 45;
public static final int RESULT_MUTE = 46;
public static final int RESULT_SENT = 47;
public static final int WATCH_DISCONNECTED = 48;
public static final int DURING_PHONE_CALL = 49;
public static final int TIDE_IS_HIGH = 50;
public static final int RESULT_DISMISSED = 51;
public static final int HEAVY_RAIN = 52;
public static final int HEAVY_SNOW = 53;
public static final int SCHEDULED_FLIGHT = 54;
public static final int GENERIC_CONFIRMATION = 55;
public static final int DAY_SEPARATOR = 56;
public static final int NO_EVENTS = 57;
public static final int NOTIFICATION_BLACKBERRY_MESSENGER = 58;
public static final int NOTIFICATION_INSTAGRAM = 59;
public static final int NOTIFICATION_MAILBOX = 60;
public static final int NOTIFICATION_GOOGLE_INBOX = 61;
public static final int RESULT_FAILED = 62;
public static final int GENERIC_QUESTION = 63;
public static final int NOTIFICATION_OUTLOOK = 64;
public static final int RAINING_AND_SNOWING = 65;
public static final int REACHED_FITNESS_GOAL = 66;
public static final int NOTIFICATION_LINE = 67;
public static final int NOTIFICATION_SKYPE = 68;
public static final int NOTIFICATION_SNAPCHAT = 69;
public static final int NOTIFICATION_VIBER = 70;
public static final int NOTIFICATION_WECHAT = 71;
public static final int NOTIFICATION_YAHOO_MAIL = 72;
public static final int TV_SHOW = 73;
public static final int BASKETBALL = 74;
public static final int DISMISSED_PHONE_CALL = 75;
public static final int NOTIFICATION_GOOGLE_MESSENGER = 76;
public static final int NOTIFICATION_HIPCHAT = 77;
public static final int INCOMING_PHONE_CALL = 78;
public static final int NOTIFICATION_KAKAOTALK = 79;
public static final int NOTIFICATION_KIK = 80;
public static final int NOTIFICATION_LIGHTHOUSE = 81;
public static final int LOCATION = 82;
public static final int SETTINGS = 83;
public static final int SUNRISE = 84;
public static final int SUNSET = 85;
public static final int FACETIME_DISMISSED = 86;
public static final int FACETIME_INCOMING = 87;
public static final int FACETIME_OUTGOING = 88;
public static final int FACETIME_MISSED = 89;
public static final int FACETIME_DURING = 90;
public static final int BLUESCREEN_OF_DEATH = 91;
public static final int START_MUSIC_PHONE = 92;
}

View File

@ -10,7 +10,6 @@ import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class BluetoothStateChangeReceiver extends BroadcastReceiver {
@Override

View File

@ -13,6 +13,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
public class K9Receiver extends BroadcastReceiver {
@ -42,9 +44,9 @@ public class K9Receiver extends BroadcastReceiver {
"uri"
};
String sender = "";
String subject = "";
String preview = "";
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.id = -1;
notificationSpec.type = NotificationType.EMAIL;
/*
* there seems to be no way to specify the the uri in the where clause.
@ -57,14 +59,14 @@ public class K9Receiver extends BroadcastReceiver {
do {
String uri = c.getString(c.getColumnIndex("uri"));
if (uri.equals(uriWanted)) {
sender = c.getString(c.getColumnIndex("senderAddress"));
subject = c.getString(c.getColumnIndex("subject"));
preview = c.getString(c.getColumnIndex("preview"));
notificationSpec.sender = c.getString(c.getColumnIndex("senderAddress"));
notificationSpec.subject = c.getString(c.getColumnIndex("subject"));
notificationSpec.body = c.getString(c.getColumnIndex("preview"));
break;
}
} while (c.moveToNext());
c.close();
GBApplication.deviceService().onEmail(sender, subject, preview);
GBApplication.deviceService().onNotification(notificationSpec);
}
}

View File

@ -12,6 +12,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
public class MusicPlaybackReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class);
private static String mLastSource;
@Override
public void onReceive(Context context, Intent intent) {
String artist = intent.getStringExtra("artist");

View File

@ -9,6 +9,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
@ -20,6 +22,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class NotificationListener extends NotificationListenerService {
@ -32,45 +36,60 @@ public class NotificationListener extends NotificationListenerService {
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss_all";
public static final String ACTION_OPEN
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open";
public static final String ACTION_MUTE
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute";
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@SuppressLint("NewApi")
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_OPEN)) {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
try {
PendingIntent pi = sbn.getNotification().contentIntent;
if (pi != null) {
pi.send();
switch (action) {
case ACTION_MUTE:
case ACTION_OPEN: {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
if (action.equals(ACTION_OPEN)) {
try {
PendingIntent pi = sbn.getNotification().contentIntent;
if (pi != null) {
pi.send();
}
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
} else {
// ACTION_MUTE
LOG.info("going to mute " + sbn.getPackageName());
GBApplication.addToBlacklist(sbn.getPackageName());
}
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
break;
}
} else if (action.equals(ACTION_DISMISS)) {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
if (GBApplication.isRunningLollipopOrLater()) {
String key = sbn.getKey();
NotificationListener.this.cancelNotification(key);
} else {
int id = sbn.getId();
String pkg = sbn.getPackageName();
String tag = sbn.getTag();
NotificationListener.this.cancelNotification(pkg, tag, id);
case ACTION_DISMISS: {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
if (GBApplication.isRunningLollipopOrLater()) {
String key = sbn.getKey();
NotificationListener.this.cancelNotification(key);
} else {
int id = sbn.getId();
String pkg = sbn.getPackageName();
String tag = sbn.getTag();
NotificationListener.this.cancelNotification(pkg, tag, id);
}
}
}
break;
}
} else if (action.equals(ACTION_DISMISS_ALL)) {
NotificationListener.this.cancelAllNotifications();
case ACTION_DISMISS_ALL:
NotificationListener.this.cancelAllNotifications();
break;
}
}
@ -83,6 +102,7 @@ public class NotificationListener extends NotificationListenerService {
filterLocal.addAction(ACTION_OPEN);
filterLocal.addAction(ACTION_DISMISS);
filterLocal.addAction(ACTION_DISMISS_ALL);
filterLocal.addAction(ACTION_MUTE);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
}
@ -128,8 +148,6 @@ public class NotificationListener extends NotificationListenerService {
if (source.equals("android") ||
source.equals("com.android.systemui") ||
source.equals("com.android.dialer") ||
source.equals("com.android.mms") ||
source.equals("com.moez.QKSMS") ||
source.equals("com.cyanogenmod.eleven")) {
return;
}
@ -146,21 +164,69 @@ public class NotificationListener extends NotificationListenerService {
}
}
LOG.info("Processing notification from source " + source);
Bundle extras = notification.extras;
String title = extras.getCharSequence(Notification.EXTRA_TITLE).toString();
String content = null;
if (extras.containsKey(Notification.EXTRA_TEXT)) {
CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
if (contentCS != null) {
content = contentCS.toString();
if (source.equals("com.moez.QKSMS") || source.equals("com.android.mms")) {
if (!"never".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) {
return;
}
}
if (content != null) {
GBApplication.deviceService().onGenericNotification(title, content, (int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
if (GBApplication.blacklist != null && GBApplication.blacklist.contains(source)) {
return;
}
NotificationSpec notificationSpec = new NotificationSpec();
// determinate Source App Name ("Label")
PackageManager pm = getPackageManager();
ApplicationInfo ai = null;
try {
ai = pm.getApplicationInfo(source, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (ai != null) {
notificationSpec.sourceName = (String) pm.getApplicationLabel(ai);
}
switch (source) {
case "org.mariotaku.twidere":
case "com.twitter.android":
case "org.andstatus.app":
case "org.mustard.android":
notificationSpec.type = NotificationType.TWITTER;
break;
case "com.fsck.k9":
case "com.android.email":
notificationSpec.type = NotificationType.EMAIL;
break;
case "com.moez.QKSMS":
case "com.android.mms":
notificationSpec.type = NotificationType.SMS;
break;
case "eu.siacs.conversations":
notificationSpec.type = NotificationType.CHAT;
break;
case "org.indywidualni.fblite":
notificationSpec.type = NotificationType.FACEBOOK;
break;
default:
notificationSpec.type = NotificationType.UNDEFINED;
break;
}
LOG.info("Processing notification from source " + source);
Bundle extras = notification.extras;
notificationSpec.title = extras.getCharSequence(Notification.EXTRA_TITLE).toString();
if (extras.containsKey(Notification.EXTRA_TEXT)) {
CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
if (contentCS != null) {
notificationSpec.body = contentCS.toString();
}
}
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
GBApplication.deviceService().onNotification(notificationSpec);
}
private boolean isServiceRunning() {
@ -177,4 +243,4 @@ public class NotificationListener extends NotificationListenerService {
public void onNotificationRemoved(StatusBarNotification sbn) {
}
}
}

View File

@ -13,6 +13,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
public class PebbleReceiver extends BroadcastReceiver {
@ -32,9 +34,6 @@ public class PebbleReceiver extends BroadcastReceiver {
}
}
String title;
String body;
String messageType = intent.getStringExtra("messageType");
if (!messageType.equals("PEBBLE_ALERT")) {
LOG.info("non PEBBLE_ALERT message type not supported");
@ -46,18 +45,26 @@ public class PebbleReceiver extends BroadcastReceiver {
return;
}
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.id = -1;
String notificationData = intent.getStringExtra("notificationData");
try {
JSONArray notificationJSON = new JSONArray(notificationData);
title = notificationJSON.getJSONObject(0).getString("title");
body = notificationJSON.getJSONObject(0).getString("body");
notificationSpec.title = notificationJSON.getJSONObject(0).getString("title");
notificationSpec.body = notificationJSON.getJSONObject(0).getString("body");
} catch (JSONException e) {
e.printStackTrace();
return;
}
if (title != null && body != null) {
GBApplication.deviceService().onSMS(title, body);
if (notificationSpec.title != null) {
notificationSpec.type = NotificationType.UNDEFINED;
String sender = intent.getStringExtra("sender");
if ("Conversations".equals(sender)) {
notificationSpec.type = NotificationType.CHAT;
}
GBApplication.deviceService().onNotification(notificationSpec);
}
}
}

View File

@ -3,6 +3,8 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -22,11 +24,11 @@ public class PhoneCallReceiver extends BroadcastReceiver {
String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
int state = 0;
if (stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
if (TelephonyManager.EXTRA_STATE_IDLE.equals(stateStr)) {
state = TelephonyManager.CALL_STATE_IDLE;
} else if (stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(stateStr)) {
state = TelephonyManager.CALL_STATE_OFFHOOK;
} else if (stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
} else if (TelephonyManager.EXTRA_STATE_RINGING.equals(stateStr)) {
state = TelephonyManager.CALL_STATE_RINGING;
}
@ -62,6 +64,10 @@ public class PhoneCallReceiver extends BroadcastReceiver {
break;
}
if (callCommand != null) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
if ("never".equals(sharedPrefs.getString("notification_mode_calls", "always"))) {
return;
}
GBApplication.deviceService().onSetCallState(mSavedNumber, null, callCommand);
}
mLastState = state;

View File

@ -10,6 +10,8 @@ import android.preference.PreferenceManager;
import android.telephony.SmsMessage;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
public class SMSReceiver extends BroadcastReceiver {
@ -27,16 +29,20 @@ public class SMSReceiver extends BroadcastReceiver {
}
}
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.id = -1;
notificationSpec.type = NotificationType.SMS;
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
for (Object pdu1 : pdus) {
byte[] pdu = (byte[]) pdu1;
SmsMessage message = SmsMessage.createFromPdu(pdu);
String body = message.getDisplayMessageBody();
String sender = message.getOriginatingAddress();
if (sender != null && body != null) {
GBApplication.deviceService().onSMS(sender, body);
notificationSpec.body = message.getDisplayMessageBody();
notificationSpec.phoneNumber = message.getOriginatingAddress();
if (notificationSpec.phoneNumber != null) {
GBApplication.deviceService().onNotification(notificationSpec);
}
}
}

View File

@ -16,6 +16,19 @@ public class GBActivitySample implements ActivitySample {
this.intensity = intensity;
this.steps = steps;
this.type = type;
validate();
}
private void validate() {
if (steps < 0) {
throw new IllegalArgumentException("steps must be > 0");
}
if (intensity < 0) {
throw new IllegalArgumentException("intensity must be > 0");
}
if (timestamp < 0) {
throw new IllegalArgumentException("timestamp must be > 0");
}
}
@Override

View File

@ -9,10 +9,10 @@ import android.support.v4.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class GBDevice implements Parcelable {
public static final String ACTION_DEVICE_CHANGED
@ -153,7 +153,7 @@ public class GBDevice implements Parcelable {
if (mBusyTask != null) {
LOG.warn("Attempt to mark device as busy with: " + task + ", but is already busy with: " + mBusyTask);
}
LOG.info("Mark device as busy: " + mBusyTask);
LOG.info("Mark device as busy: " + task);
mBusyTask = task;
}

View File

@ -4,9 +4,9 @@ import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.Parcelable;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
/**
*/

View File

@ -11,6 +11,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
@ -79,28 +80,16 @@ public class GBDeviceService implements DeviceService {
}
@Override
public void onSMS(String from, String body) {
Intent intent = createIntent().setAction(ACTION_NOTIFICATION_SMS)
.putExtra(EXTRA_NOTIFICATION_SENDER, from)
.putExtra(EXTRA_NOTIFICATION_BODY, body);
invokeService(intent);
}
@Override
public void onEmail(String from, String subject, String body) {
Intent intent = createIntent().setAction(ACTION_NOTIFICATION_EMAIL)
.putExtra(EXTRA_NOTIFICATION_SENDER, from)
.putExtra(EXTRA_NOTIFICATION_SUBJECT, subject)
.putExtra(EXTRA_NOTIFICATION_BODY, body);
invokeService(intent);
}
@Override
public void onGenericNotification(String title, String details, int handle) {
Intent intent = createIntent().setAction(ACTION_NOTIFICATION_GENERIC)
.putExtra(EXTRA_NOTIFICATION_TITLE, title)
.putExtra(EXTRA_NOTIFICATION_BODY, details)
.putExtra(EXTRA_NOTIFICATION_HANDLE, handle);
public void onNotification(NotificationSpec notificationSpec) {
Intent intent = createIntent().setAction(ACTION_NOTIFICATION)
.putExtra(EXTRA_NOTIFICATION_PHONENUMBER, notificationSpec.phoneNumber)
.putExtra(EXTRA_NOTIFICATION_SENDER, notificationSpec.sender)
.putExtra(EXTRA_NOTIFICATION_SUBJECT, notificationSpec.subject)
.putExtra(EXTRA_NOTIFICATION_TITLE, notificationSpec.title)
.putExtra(EXTRA_NOTIFICATION_BODY, notificationSpec.body)
.putExtra(EXTRA_NOTIFICATION_ID, notificationSpec.id)
.putExtra(EXTRA_NOTIFICATION_TYPE, notificationSpec.type)
.putExtra(EXTRA_NOTIFICATION_SOURCENAME, notificationSpec.sourceName);
invokeService(intent);
}
@ -148,9 +137,10 @@ public class GBDeviceService implements DeviceService {
}
@Override
public void onAppStart(UUID uuid) {
public void onAppStart(UUID uuid, boolean start) {
Intent intent = createIntent().setAction(ACTION_STARTAPP)
.putExtra(EXTRA_APP_UUID, uuid);
.putExtra(EXTRA_APP_UUID, uuid)
.putExtra(EXTRA_APP_START, start);
invokeService(intent);
}

View File

@ -2,8 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.model;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class ActivityAmount {
private int activityKind;
private short percent;

View File

@ -13,9 +13,8 @@ public interface DeviceService extends EventHandler {
static final String ACTION_START = PREFIX + ".action.start";
static final String ACTION_CONNECT = PREFIX + ".action.connect";
static final String ACTION_NOTIFICATION_GENERIC = PREFIX + ".action.notification_generic";
static final String ACTION_NOTIFICATION = PREFIX + ".action.notification";
static final String ACTION_NOTIFICATION_SMS = PREFIX + ".action.notification_sms";
static final String ACTION_NOTIFICATION_EMAIL = PREFIX + ".action.notification_email";
static final String ACTION_CALLSTATE = PREFIX + ".action.callstate";
static final String ACTION_SETTIME = PREFIX + ".action.settime";
static final String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo";
@ -34,11 +33,14 @@ public interface DeviceService extends EventHandler {
static final String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps";
static final String EXTRA_DEVICE_ADDRESS = "device_address";
static final String EXTRA_NOTIFICATION_TITLE = "notification_title";
static final String EXTRA_NOTIFICATION_BODY = "notification_body";
static final String EXTRA_NOTIFICATION_ID = "notification_id";
static final String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber";
static final String EXTRA_NOTIFICATION_SENDER = "notification_sender";
static final String EXTRA_NOTIFICATION_SOURCENAME = "notification_sourcename";
static final String EXTRA_NOTIFICATION_SUBJECT = "notification_subject";
static final String EXTRA_NOTIFICATION_HANDLE = "notification_handle";
static final String EXTRA_NOTIFICATION_TITLE = "notification_title";
static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
static final String EXTRA_FIND_START = "find_start";
static final String EXTRA_CALL_COMMAND = "call_command";
static final String EXTRA_CALL_PHONENUMBER = "call_phonenumber";
@ -46,6 +48,7 @@ public interface DeviceService extends EventHandler {
static final String EXTRA_MUSIC_ALBUM = "music_album";
static final String EXTRA_MUSIC_TRACK = "music_track";
static final String EXTRA_APP_UUID = "app_uuid";
static final String EXTRA_APP_START = "app_start";
static final String EXTRA_URI = "uri";
static final String EXTRA_ALARMS = "alarms";
static final String EXTRA_PERFORM_PAIR = "perform_pair";
@ -53,15 +56,18 @@ public interface DeviceService extends EventHandler {
static final String EXTRA_REALTIME_STEPS = "realtime_steps";
static final String EXTRA_TIMESTAMP = "timestamp";
void start();
void connect();
void connect(@Nullable String deviceAddress);
void connect(@Nullable String deviceAddress, boolean performPair);
void disconnect();
void quit();
/**
* Requests information from the {@link DeviceCommunicationService} about the connection state,
* firmware info, etc.

View File

@ -2,6 +2,8 @@ package nodomain.freeyourgadget.gadgetbridge.model;
public interface ItemWithDetails {
String getName();
String getDetails();
int getIcon();
}

View File

@ -0,0 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public class NotificationSpec {
public int id;
public String sender;
public String phoneNumber;
public String title;
public String subject;
public String body;
public NotificationType type;
public String sourceName;
}

View File

@ -0,0 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public enum NotificationType {
UNDEFINED,
CHAT,
EMAIL,
FACEBOOK,
SMS,
TWITTER,
}

View File

@ -2,7 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.model;
public enum ServiceCommand {
UNDEFINEND,
UNDEFINED,
CALL_ACCEPT,
CALL_END,

View File

@ -220,6 +220,9 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
case OPEN:
action = NotificationListener.ACTION_OPEN;
break;
case MUTE:
action = NotificationListener.ACTION_MUTE;
break;
}
if (action != null) {
Intent notificationListenerIntent = new Intent(action);

View File

@ -26,7 +26,8 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -38,9 +39,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_EN
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_NOTIFICATION;
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;
@ -51,6 +50,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
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_START;
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;
@ -61,10 +61,13 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUS
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_HANDLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCENAME;
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_NOTIFICATION_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
@ -180,25 +183,20 @@ public class DeviceCommunicationService extends Service {
case ACTION_REQUEST_DEVICEINFO:
mGBDevice.sendDeviceUpdateIntent(this);
break;
case ACTION_NOTIFICATION_GENERIC: {
String title = intent.getStringExtra(EXTRA_NOTIFICATION_TITLE);
String body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
int handle = intent.getIntExtra(EXTRA_NOTIFICATION_HANDLE,-1);
mDeviceSupport.onGenericNotification(title, body, handle);
break;
}
case ACTION_NOTIFICATION_SMS: {
String sender = intent.getStringExtra(EXTRA_NOTIFICATION_SENDER);
String body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
String senderName = getContactDisplayNameByNumber(sender);
mDeviceSupport.onSMS(senderName, body);
break;
}
case ACTION_NOTIFICATION_EMAIL: {
String sender = intent.getStringExtra(EXTRA_NOTIFICATION_SENDER);
String subject = intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT);
String body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
mDeviceSupport.onEmail(sender, subject, body);
case ACTION_NOTIFICATION: {
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.phoneNumber = intent.getStringExtra(EXTRA_NOTIFICATION_PHONENUMBER);
notificationSpec.sender = intent.getStringExtra(EXTRA_NOTIFICATION_SENDER);
notificationSpec.subject = intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT);
notificationSpec.title = intent.getStringExtra(EXTRA_NOTIFICATION_TITLE);
notificationSpec.body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE);
notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME);
if (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null) {
notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber);
}
mDeviceSupport.onNotification(notificationSpec);
break;
}
case ACTION_REBOOT: {
@ -246,7 +244,8 @@ public class DeviceCommunicationService extends Service {
break;
case ACTION_STARTAPP: {
UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID);
mDeviceSupport.onAppStart(uuid);
boolean start = intent.getBooleanExtra(EXTRA_APP_START, true);
mDeviceSupport.onAppStart(uuid, start);
break;
}
case ACTION_DELETEAPP: {
@ -276,6 +275,7 @@ public class DeviceCommunicationService extends Service {
/**
* For testing!
*
* @param factory
*/
public void setDeviceSupportFactory(DeviceSupportFactory factory) {
@ -285,6 +285,7 @@ public class DeviceCommunicationService extends Service {
/**
* 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) {

View File

@ -22,14 +22,16 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public interface DeviceSupport extends EventHandler {
/**
* Sets all context information needed for the instance to operate.
* @param gbDevice the device to operate with
*
* @param gbDevice the device to operate with
* @param btAdapter the bluetooth adapter to use
* @param context the android context, e.g. to look up resources
* @param context the android context, e.g. to look up resources
*/
void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context);
/**
* Returns whether a transport-level connection is established with the device
*
* @return whether the device is connected with the system running this software
*/
boolean isConnected();
@ -40,9 +42,10 @@ public interface DeviceSupport extends EventHandler {
* Returns true if a connection attempt was made. If the implementation is synchronous
* it may also return true if the connection was successfully established, however
* callers shall not rely on that.
*
* <p/>
* The actual connection state change (successful or not) will be reported via the
* #getDevice device as a device change Intent.
*
* @see GBDevice#ACTION_DEVICE_CHANGED
*/
boolean connect();
@ -62,6 +65,7 @@ public interface DeviceSupport extends EventHandler {
/**
* Attempts to pair and connect this device with the gadget device. Success
* will be reported via a device change Intent.
*
* @see GBDevice#ACTION_DEVICE_CHANGED
*/
void pair();

View File

@ -13,6 +13,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
/**
@ -24,6 +25,7 @@ public class ServiceDeviceSupport implements DeviceSupport {
THROTTLING,
BUSY_CHECKING,
}
private static final Logger LOG = LoggerFactory.getLogger(ServiceDeviceSupport.class);
private static final long THROTTLING_THRESHOLD = 1000; // throttle multiple events in between one second
@ -111,27 +113,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
@Override
public void onSMS(String from, String body) {
if (checkBusy("sms") || checkThrottle("sms")) {
return;
}
delegate.onSMS(from, body);
}
@Override
public void onEmail(String from, String subject, String body) {
if (checkBusy("email") || checkThrottle("email")) {
return;
}
delegate.onEmail(from, subject, body);
}
@Override
public void onGenericNotification(String title, String details, int handle) {
public void onNotification(NotificationSpec notificationSpec) {
if (checkBusy("generic notification") || checkThrottle("generic notification")) {
return;
}
delegate.onGenericNotification(title, details, handle);
delegate.onNotification(notificationSpec);
}
@Override
@ -177,11 +163,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
@Override
public void onAppStart(UUID uuid) {
public void onAppStart(UUID uuid, boolean start) {
if (checkBusy("app start")) {
return;
}
delegate.onAppStart(uuid);
delegate.onAppStart(uuid, start);
}
@Override

View File

@ -39,6 +39,8 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
public static final UUID UUID_DESCRIPTOR_CHARACTERISTIC_USER_CONFIGURATION = UUID.fromString(String.format(BASE_UUID, "2901"));
public static final UUID UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION = UUID.fromString(String.format(BASE_UUID, "2902"));
//part of the generic BLE specs see https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.immediate_alert.xml
public static final UUID UUID_SERVICE_IMMEDIATE_ALERT = UUID.fromString((String.format(BASE_UUID, "1802")));
@Override
public boolean connect() {
@ -140,13 +142,13 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
}
private void gattServicesDiscovered(List<BluetoothGattService> discoveredGattServices) {
mAvailableCharacteristics = null;
if (discoveredGattServices == null) {
return;
}
Set<UUID> supportedServices = getSupportedServices();
mAvailableCharacteristics = new HashMap();
for (BluetoothGattService service : discoveredGattServices) {
if (supportedServices.contains(service.getUuid())) {
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
@ -154,10 +156,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
LOG.warn("Supported LE service " + service.getUuid() + "did not return any characteristics");
continue;
}
mAvailableCharacteristics = new HashMap<>(characteristics.size());
HashMap<UUID, BluetoothGattCharacteristic> intmAvailableCharacteristics = new HashMap<>(characteristics.size());
for (BluetoothGattCharacteristic characteristic : characteristics) {
mAvailableCharacteristics.put(characteristic.getUuid(), characteristic);
intmAvailableCharacteristics.put(characteristic.getUuid(), characteristic);
}
mAvailableCharacteristics.putAll(intmAvailableCharacteristics);
}
}
}

View File

@ -14,10 +14,10 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
* Abstract base class for a BTLEOperation, i.e. an operation that does more than
* just sending a few bytes to the device. It typically involves exchanging many messages
* between the mobile and the device.
*
* <p/>
* One operation may execute multiple @{link Transaction transactions} with each
* multiple @{link BTLEAction actions}.
*
* <p/>
* This class implements GattCallback so that subclasses may override those methods
* to handle those events.
* Note: by default all Gatt events are forwarded to AbstractBTLEDeviceSupport, subclasses may override
@ -33,6 +33,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
/**
* Delegates to the DeviceSupport instance and additionally sets this instance as the Gatt
* callback for the transaction.
*
* @param taskName
* @return
* @throws IOException

View File

@ -2,8 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
public interface BTLEOperation {
public void perform() throws IOException;
}

View File

@ -61,10 +61,10 @@ public final class BtLEQueue {
while (!mDisposed && !mCrashed) {
try {
Transaction transaction = mTransactions.take();
internalGattCallback.reset();
if (!isConnected()) {
// TODO: request connection and initialization from the outside and wait until finished
internalGattCallback.reset();
// wait until the connection succeeds before running the actions
// Note that no automatic connection is performed. This has to be triggered
@ -207,6 +207,7 @@ public final class BtLEQueue {
/**
* Depending on certain criteria, connects to the BluetoothGatt.
*
* @return true if a reconnection attempt was made, or false otherwise
*/
private boolean maybeReconnect() {
@ -275,7 +276,9 @@ public final class BtLEQueue {
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final class InternalGattCallback extends BluetoothGattCallback {
private @Nullable GattCallback mTransactionGattCallback;
private
@Nullable
GattCallback mTransactionGattCallback;
private GattCallback mExternalGattCallback;
public InternalGattCallback(GattCallback externalGattCallback) {
@ -403,6 +406,8 @@ public final class BtLEQueue {
}
if (getCallbackToUse() != null) {
getCallbackToUse().onCharacteristicChanged(gatt, characteristic);
} else {
LOG.info("No gattcallback registered, ignoring characteristic change");
}
}
@ -442,7 +447,12 @@ public final class BtLEQueue {
}
public void reset() {
if (LOG.isDebugEnabled()) {
LOG.debug("internal gatt callback set to null");
}
mTransactionGattCallback = null;
}
};
}
;
}

View File

@ -19,7 +19,9 @@ public class Transaction {
private String mName;
private List<BtLEAction> mActions = new ArrayList<>(4);
private long creationTimestamp = System.currentTimeMillis();
private @Nullable GattCallback gattCallback;
private
@Nullable
GattCallback gattCallback;
public Transaction(String taskName) {
this.mName = taskName;
@ -57,7 +59,9 @@ public class Transaction {
/**
* Returns the GattCallback for this transaction, or null if none.
*/
public @Nullable GattCallback getGattCallback() {
public
@Nullable
GattCallback getGattCallback() {
return gattCallback;
}
}

View File

@ -64,13 +64,16 @@ public class TransactionBuilder {
/**
* Sets a GattCallback instance that will be called when the transaction is executed,
* resulting in GattCallback events.
*
* @param callback the callback to set, may be null
*/
public void setGattCallback(@Nullable GattCallback callback) {
mTransaction.setGattCallback(callback);
}
public @Nullable GattCallback getGattCallback() {
public
@Nullable
GattCallback getGattCallback() {
return mTransaction.getGattCallback();
}

View File

@ -14,6 +14,7 @@ public class SetProgressAction extends PlainAction {
/**
* When run, will update the progress notification.
*
* @param text
* @param ongoing
* @param percentage

View File

@ -11,7 +11,7 @@ public class BatteryInfo extends AbstractInfo {
public static final byte DEVICE_BATTERY_CHARGING = 2;
public static final byte DEVICE_BATTERY_CHARGING_FULL = 3;
public static final byte DEVICE_BATTERY_CHARGE_OFF = 4;
public BatteryInfo(byte[] data) {
super(data);
}
@ -43,7 +43,7 @@ public class BatteryInfo extends AbstractInfo {
}
public GregorianCalendar getLastChargeTime() {
GregorianCalendar lastCharge = new GregorianCalendar();
GregorianCalendar lastCharge = MiBandDateConverter.createCalendar();
if (mData.length >= 10) {
lastCharge = MiBandDateConverter.rawBytesToCalendar(new byte[]{

View File

@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.UUID;
@ -29,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
@ -38,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactio
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR;
@ -54,6 +58,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FL
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_ORIGINAL_COLOUR;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_GENERIC;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_K9MAIL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_PEBBLEMSG;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_SMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_DURATION;
@ -404,18 +409,21 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSMS(String from, String body) {
performPreferredNotification("sms received", ORIGIN_SMS, null);
}
@Override
public void onEmail(String from, String subject, String body) {
performPreferredNotification("email received", ORIGIN_K9MAIL, null);
}
@Override
public void onGenericNotification(String title, String details, int handle) {
performPreferredNotification("generic notification received", ORIGIN_GENERIC, null);
public void onNotification(NotificationSpec notificationSpec) {
// FIXME: these ORIGIN contants do not really make sense anymore
switch (notificationSpec.type) {
case SMS:
performPreferredNotification("sms received", ORIGIN_SMS, null);
break;
case EMAIL:
performPreferredNotification("email received", ORIGIN_K9MAIL, null);
break;
case CHAT:
performPreferredNotification("chat message received", ORIGIN_PEBBLEMSG, null);
break;
default:
performPreferredNotification("generic notification received", ORIGIN_GENERIC, null);
}
}
@Override
@ -435,7 +443,10 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
* @param builder
*/
private MiBandSupport setCurrentTime(TransactionBuilder builder) {
byte[] nowBytes = MiBandDateConverter.calendarToRawBytes(GregorianCalendar.getInstance());
Calendar now = GregorianCalendar.getInstance();
Date date = now.getTime();
LOG.info("Sending current time to Mi Band: " + DateTimeUtils.formatDate(date) + " (" + date.toGMTString() + ")");
byte[] nowBytes = MiBandDateConverter.calendarToRawBytes(now);
byte[] time = new byte[]{
nowBytes[0],
nowBytes[1],
@ -588,7 +599,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onAppStart(UUID uuid) {
public void onAppStart(UUID uuid, boolean start) {
// not supported
}
@ -614,6 +625,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
handleNotificationNotif(characteristic.getValue());
} else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) {
handleRealtimeSteps(characteristic.getValue());
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
}
}

View File

@ -31,28 +31,89 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* An operation that fetches activity data. For every fetch, a new operation must
* be created, i.e. an operation may not be reused for multiple fetches.
*/
public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport> {
private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
private static final byte[] fetch = new byte[]{MiBandService.COMMAND_FETCH_DATA};
//temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 bytes)
private static final int activityDataHolderSize = 3 * 60 * 4; // 8h
private static final int activityDataHolderSize = 3 * 60 * 4; // 4h
private static class ActivityStruct {
public byte[] activityDataHolder = new byte[activityDataHolderSize];
private byte[] activityDataHolder = new byte[activityDataHolderSize];
//index of the buffer above
public int activityDataHolderProgress = 0;
private int activityDataHolderProgress = 0;
//number of bytes we will get in a single data transfer, used as counter
public int activityDataRemainingBytes = 0;
private int activityDataRemainingBytes = 0;
//same as above, but remains untouched for the ack message
public int activityDataUntilNextHeader = 0;
private int activityDataUntilNextHeader = 0;
//timestamp of the single data transfer, incremented to store each minute's data
public GregorianCalendar activityDataTimestampProgress = null;
private GregorianCalendar activityDataTimestampProgress = null;
//same as above, but remains untouched for the ack message
public GregorianCalendar activityDataTimestampToAck = null;
private GregorianCalendar activityDataTimestampToAck = null;
public boolean hasRoomFor(byte[] value) {
return activityDataRemainingBytes >= value.length;
}
public boolean isValidData(byte[] value) {
//I don't like this clause, but until we figure out why we get different data sometimes this should work
return value.length == 20 || value.length == activityDataRemainingBytes;
}
public boolean isBufferFull() {
return activityDataHolderSize == activityDataHolderProgress;
}
public void buffer(byte[] value) {
System.arraycopy(value, 0, activityDataHolder, activityDataHolderProgress, value.length);
activityDataHolderProgress += value.length;
activityDataRemainingBytes -= value.length;
validate();
}
private void validate() {
GB.assertThat(activityDataRemainingBytes >= 0, "Illegal state, remaining bytes is negative");
}
public boolean isFirstChunk() {
return activityDataTimestampProgress == null;
}
public void startNewBlock(GregorianCalendar timestamp, int dataUntilNextHeader) {
GB.assertThat(timestamp != null, "Timestamp must not be null");
if (isFirstChunk()) {
activityDataTimestampProgress = timestamp;
} else {
if (timestamp.getTimeInMillis() >= activityDataTimestampProgress.getTimeInMillis()) {
activityDataTimestampProgress = timestamp;
} else {
// something is fishy here... better not trust the given timestamp and simply
// (re)use the current one
// we do accept the timestamp to ack though, so that the bogus data is properly cleared on the band
}
}
activityDataTimestampToAck = (GregorianCalendar) timestamp.clone();
activityDataRemainingBytes = activityDataUntilNextHeader = dataUntilNextHeader;
validate();
}
public boolean isBlockFinished() {
return activityDataRemainingBytes == 0;
}
public void bufferFlushed(int minutes) {
activityDataTimestampProgress.add(Calendar.MINUTE, minutes);
activityDataHolderProgress = 0;
}
}
private ActivityStruct activityStruct;
private ActivityStruct activityStruct = new ActivityStruct();
public FetchActivityOperation(MiBandSupport support) {
super(support);
@ -90,40 +151,34 @@ public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport>
* There are two kind of messages we currently know:
* - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
* - the second one is 20 bytes long and contains the actual activity data
*
* <p/>
* The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
* @see #bufferActivityData(byte[])
*
*
* @param value
* @see #bufferActivityData(byte[])
*/
private void handleActivityNotif(byte[] value) {
boolean firstChunk = activityStruct == null;
if (firstChunk) {
activityStruct = new ActivityStruct();
}
if (value.length == 11) {
// byte 0 is the data type: 1 means that each minute is represented by a triplet of bytes
int dataType = value[0];
// byte 1 to 6 represent a timestamp
GregorianCalendar timestamp = parseTimestamp(value, 1);
GregorianCalendar timestamp = MiBandDateConverter.rawBytesToCalendar(value, 1);
// counter of all data held by the band
int totalDataToRead = (value[7] & 0xff) | ((value[8] & 0xff) << 8);
totalDataToRead *= (dataType == 1) ? 3 : 1;
totalDataToRead *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? 3 : 1;
// counter of this data block
int dataUntilNextHeader = (value[9] & 0xff) | ((value[10] & 0xff) << 8);
dataUntilNextHeader *= (dataType == 1) ? 3 : 1;
dataUntilNextHeader *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? 3 : 1;
// there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1),
// there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1 (MiBandService.MODE_REGULAR_DATA_LEN_MINUTE)),
// these chunks are usually 20 bytes long and grouped in blocks
// after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed
// as we just did
if (firstChunk && dataUntilNextHeader != 0) {
if (activityStruct.isFirstChunk() && dataUntilNextHeader != 0) {
GB.toast(getContext().getString(R.string.user_feedback_miband_activity_data_transfer,
DateTimeUtils.formatDurationHoursMinutes((totalDataToRead / 3), TimeUnit.MINUTES),
DateFormat.getDateTimeInstance().format(timestamp.getTime())), Toast.LENGTH_LONG, GB.INFO);
@ -132,37 +187,34 @@ public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport>
LOG.info("data to read until next header: " + dataUntilNextHeader + " len: " + (dataUntilNextHeader / 3) + " minute(s)");
LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()).toString() + " magic byte: " + dataUntilNextHeader);
activityStruct.activityDataRemainingBytes = activityStruct.activityDataUntilNextHeader = dataUntilNextHeader;
activityStruct.activityDataTimestampToAck = (GregorianCalendar) timestamp.clone();
activityStruct.activityDataTimestampProgress = timestamp;
activityStruct.startNewBlock(timestamp, dataUntilNextHeader);
} else {
bufferActivityData(value);
}
LOG.debug("activity data: length: " + value.length + ", remaining bytes: " + activityStruct.activityDataRemainingBytes);
if (activityStruct.activityDataRemainingBytes == 0) {
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), true, (int)(((float) (activityStruct.activityDataUntilNextHeader - activityStruct.activityDataRemainingBytes)) / activityStruct.activityDataUntilNextHeader * 100), getContext());
if (activityStruct.isBlockFinished()) {
sendAckDataTransfer(activityStruct.activityDataTimestampToAck, activityStruct.activityDataUntilNextHeader);
GB.updateTransferNotification("", false, 100, getContext());
}
}
/**
* Method to store temporarily the activity data values got from the Mi Band.
*
* <p/>
* Since we expect chunks of 20 bytes each, we do not store the received bytes it the length is different.
*
* @param value
*/
private void bufferActivityData(byte[] value) {
if (activityStruct.hasRoomFor(value)) {
if (activityStruct.isValidData(value)) {
activityStruct.buffer(value);
if (activityStruct.activityDataRemainingBytes >= value.length) {
//I don't like this clause, but until we figure out why we get different data sometimes this should work
if (value.length == 20 || value.length == activityStruct.activityDataRemainingBytes) {
System.arraycopy(value, 0, activityStruct.activityDataHolder, activityStruct.activityDataHolderProgress, value.length);
activityStruct.activityDataHolderProgress += value.length;
activityStruct.activityDataRemainingBytes -= value.length;
if (this.activityDataHolderSize == activityStruct.activityDataHolderProgress) {
if (activityStruct.isBufferFull()) {
flushActivityDataHolder();
}
} else {
@ -184,28 +236,32 @@ public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport>
LOG.debug("nothing to flush, struct is already null");
return;
}
LOG.debug("flushing activity data holder");
LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / 3);
byte category, intensity, steps;
DBHandler dbHandler = null;
try {
dbHandler = GBApplication.acquireDB();
int minutes = 0;
try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples
int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000);
for (int i = 0; i < activityStruct.activityDataHolderProgress; i += 3) { //TODO: check if multiple of 3, if not something is wrong
category = activityStruct.activityDataHolder[i];
intensity = activityStruct.activityDataHolder[i + 1];
steps = activityStruct.activityDataHolder[i + 2];
dbHandler.addGBActivitySample(
(int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000),
timestampInSeconds,
SampleProvider.PROVIDER_MIBAND,
(short) (intensity & 0xff),
(short) (steps & 0xff),
category);
activityStruct.activityDataTimestampProgress.add(Calendar.MINUTE, 1);
// next minute
minutes++;
timestampInSeconds += 60;
}
} finally {
activityStruct.activityDataHolderProgress = 0;
activityStruct.bufferFlushed(minutes);
}
} catch (Exception ex) {
GB.toast(getContext(), ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
@ -218,7 +274,7 @@ public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport>
/**
* Acknowledge the transfer of activity data to the Mi Band.
*
* <p/>
* After receiving data from the band, it has to be acknowledged. This way the Mi Band will delete
* the data it has on record.
*
@ -255,7 +311,7 @@ public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport>
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), ack);
builder.queue(getQueue());
// flush to the DB after sending the ACK
// flush to the DB after queueing the ACK
flushActivityDataHolder();
//The last data chunk sent by the miband has always length 0.
@ -273,15 +329,4 @@ public class FetchActivityOperation extends AbstractBTLEOperation<MiBandSupport>
LOG.error("Unable to send ack to MI", ex);
}
}
private GregorianCalendar parseTimestamp(byte[] value, int offset) {
GregorianCalendar timestamp = new GregorianCalendar(
value[offset] + 2000,
value[offset + 1],
value[offset + 2],
value[offset + 3],
value[offset + 4],
value[offset + 5]);
return timestamp;
}
}

View File

@ -72,21 +72,21 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation<MiBandSupport
* characteristic,
* These messages appear to be always 1 byte long, with values that are listed in MiBandService.
* It is not excluded that there are further values which are still unknown.
*
* <p/>
* Upon receiving known values that request further action by GB, the appropriate method is called.
*
* @param value
*/
private void handleNotificationNotif(byte[] value) {
if(value.length != 1) {
if (value.length != 1) {
LOG.error("Notifications should be 1 byte long.");
getSupport().logMessageContent(value);
return;
}
switch (value[0]) {
case MiBandService.NOTIFY_FW_CHECK_SUCCESS:
if(firmwareInfoSent && newFirmware != null) {
if(sendFirmwareData(newFirmware)) {
if (firmwareInfoSent && newFirmware != null) {
if (sendFirmwareData(newFirmware)) {
rebootWhenBandReady = true;
} else {
//TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do?
@ -131,14 +131,14 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation<MiBandSupport
* Prepare the MiBand to receive the new firmware data.
* Some information about the new firmware version have to be pushed to the MiBand before sending
* the actual firmare.
*
* <p/>
* The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it.
* @see MiBandSupport#handleNotificationNotif
*
* @param currentFwVersion
* @param newFwVersion
* @param newFwSize
* @param checksum
* @see MiBandSupport#handleNotificationNotif
*/
private void sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) throws IOException {
byte[] fwInfo = new byte[]{
@ -165,13 +165,13 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation<MiBandSupport
/**
* Method that uploads a firmware (fwbytes) to the MiBand.
* The firmware has to be splitted into chunks of 20 bytes each, and periodically a COMMAND_SYNC comand has to be issued to the MiBand.
*
* <p/>
* The Mi Band will send a notification after receiving these data to confirm if the firmware looks good to it.
* @see MiBandSupport#handleNotificationNotif
*
* @param fwbytes
* @return whether the transfer succeeded or not. Only a BT layer exception will cause the transmission to fail.
* */
* @see MiBandSupport#handleNotificationNotif
*/
private boolean sendFirmwareData(byte fwbytes[]) {
int len = fwbytes.length;
final int packetLength = 20;
@ -190,10 +190,10 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation<MiBandSupport
if ((i > 0) && (i % 50 == 0)) {
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_SYNC});
builder.add(new SetProgressAction("Firmware update in progress", true, (firmwareProgress / len) * 100, getContext()));
builder.add(new SetProgressAction("Firmware update in progress", true, (int)(((float) firmwareProgress) / len * 100), getContext()));
}
LOG.info("Firmware update progress:" + firmwareProgress + " total len:" + len + " progress:" + (firmwareProgress / len));
LOG.info("Firmware update progress:" + firmwareProgress + " total len:" + len + " progress:" + (int)(((float) firmwareProgress) / len * 100));
}
if (!(len % packetLength == 0)) {

View File

@ -3,12 +3,17 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.ParcelUuid;
import android.preference.PreferenceManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -20,12 +25,14 @@ import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;
import java.util.zip.ZipInputStream;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable;
@ -38,8 +45,24 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class PebbleIoThread extends GBDeviceIoThread {
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
public static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED";
public static final String PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED = "com.getpebble.action.PEBBLE_DISCONNECTED";
public static final String PEBBLEKIT_ACTION_APP_ACK = "com.getpebble.action.app.ACK";
public static final String PEBBLEKIT_ACTION_APP_NACK = "com.getpebble.action.app.NACK";
public static final String PEBBLEKIT_ACTION_APP_RECEIVE = "com.getpebble.action.app.RECEIVE";
public static final String PEBBLEKIT_ACTION_APP_RECEIVE_ACK = "com.getpebble.action.app.RECEIVE_ACK";
public static final String PEBBLEKIT_ACTION_APP_RECEIVE_NACK = "com.getpebble.action.app.RECEIVE_NACK";
public static final String PEBBLEKIT_ACTION_APP_SEND = "com.getpebble.action.app.SEND";
public static final String PEBBLEKIT_ACTION_APP_START = "com.getpebble.action.app.START";
public static final String PEBBLEKIT_ACTION_APP_STOP = "com.getpebble.action.app.STOP";
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
private final PebbleProtocol mPebbleProtocol;
private final PebbleSupport mPebbleSupport;
private final boolean mEnablePebblekit;
private boolean mIsTCP = false;
private BluetoothAdapter mBtAdapter = null;
private BluetoothSocket mBtSocket = null;
@ -49,7 +72,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
private boolean mQuit = false;
private boolean mIsConnected = false;
private boolean mIsInstalling = false;
private int mConnectionAttempts = 0;
private PBWReader mPBWReader = null;
private int mAppInstallToken = -1;
@ -62,11 +84,77 @@ public class PebbleIoThread extends GBDeviceIoThread {
private int mBinarySize = -1;
private int mBytesWritten = -1;
private final BroadcastReceiver mPebbleKitReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
LOG.info("Got action: " + action);
UUID uuid;
switch (action) {
case PEBBLEKIT_ACTION_APP_START:
case PEBBLEKIT_ACTION_APP_STOP:
uuid = (UUID) intent.getSerializableExtra("uuid");
if (uuid != null) {
write(mPebbleProtocol.encodeAppStart(uuid, action.equals(PEBBLEKIT_ACTION_APP_START)));
}
break;
case PEBBLEKIT_ACTION_APP_SEND:
int transaction_id = intent.getIntExtra("transaction_id", -1);
uuid = (UUID) intent.getSerializableExtra("uuid");
String jsonString = intent.getStringExtra("msg_data");
LOG.info("json string: " + jsonString);
try {
JSONArray jsonArray = new JSONArray(jsonString);
write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray));
sendAppMessageAck(transaction_id);
} catch (JSONException e) {
e.printStackTrace();
}
break;
case PEBBLEKIT_ACTION_APP_ACK:
// we do not get a uuid and cannot map a transaction id to it, so we ack in PebbleProtocol early
/*
uuid = (UUID) intent.getSerializableExtra("uuid");
int transaction_id = intent.getIntExtra("transaction_id", -1);
if (transaction_id >= 0 && transaction_id <= 255) {
write(mPebbleProtocol.encodeApplicationMessageAck(uuid, (byte) transaction_id));
} else {
LOG.warn("illegal transacktion id " + transaction_id);
}
*/
break;
}
}
};
private void sendAppMessageIntent(GBDeviceEventAppMessage appMessage) {
Intent intent = new Intent();
intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE);
intent.putExtra("uuid", appMessage.appUUID);
intent.putExtra("msg_data", appMessage.message);
intent.putExtra("transaction_id", appMessage.id);
LOG.info("broadcasting to uuid " + appMessage.appUUID + " transaction id: " + appMessage.id + " JSON: " + appMessage.message);
getContext().sendBroadcast(intent);
}
private void sendAppMessageAck(int transactionId) {
if (transactionId > 0 && transactionId <= 255) {
Intent intent = new Intent();
intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE_ACK);
intent.putExtra("transaction_id", transactionId);
LOG.info("broadcasting ACK (transaction id " + transactionId + ")");
getContext().sendBroadcast(intent);
}
}
public PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) {
super(gbDevice, context);
mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol;
mBtAdapter = btAdapter;
mPebbleSupport = pebbleSupport;
mEnablePebblekit = sharedPrefs.getBoolean("pebble_enable_pebblekit", false);
}
@ -100,7 +188,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
return false;
}
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
mPebbleProtocol.setForceProtocol(sharedPrefs.getBoolean("pebble_force_protocol", false));
gbDevice.setState(GBDevice.State.CONNECTED);
gbDevice.sendDeviceUpdateIntent(getContext());
@ -116,6 +203,7 @@ public class PebbleIoThread extends GBDeviceIoThread {
gbDevice.sendDeviceUpdateIntent(getContext());
mIsConnected = connect(gbDevice.getAddress());
enablePebbleKitReceiver(mIsConnected);
mQuit = !mIsConnected; // quit if not connected
byte[] buffer = new byte[8192];
@ -261,14 +349,14 @@ public class PebbleIoThread extends GBDeviceIoThread {
LOG.info(e.getMessage());
gbDevice.setState(GBDevice.State.CONNECTING);
gbDevice.sendDeviceUpdateIntent(getContext());
while (mConnectionAttempts++ < 10 && !mQuit) {
LOG.info("Trying to reconnect (attempt " + mConnectionAttempts + ")");
mIsConnected = false;
int reconnectAttempts = Integer.valueOf(sharedPrefs.getString("pebble_reconnect_attempts", "10"));
while (reconnectAttempts-- > 0 && !mQuit) {
LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")");
mIsConnected = connect(gbDevice.getAddress());
if (mIsConnected)
break;
}
mConnectionAttempts = 0;
if (!mIsConnected) {
mBtSocket = null;
LOG.info("Bluetooth socket closed, will quit IO Thread");
@ -285,11 +373,36 @@ public class PebbleIoThread extends GBDeviceIoThread {
e.printStackTrace();
}
}
enablePebbleKitReceiver(false);
mBtSocket = null;
gbDevice.setState(GBDevice.State.NOT_CONNECTED);
gbDevice.sendDeviceUpdateIntent(getContext());
}
private void enablePebbleKitReceiver(boolean enable) {
if (enable && mEnablePebblekit) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PEBBLEKIT_ACTION_APP_ACK);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_NACK);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_SEND);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_START);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP);
try {
getContext().registerReceiver(mPebbleKitReceiver, intentFilter);
} catch (IllegalArgumentException e) {
// ignore
}
} else {
try {
getContext().unregisterReceiver(mPebbleKitReceiver);
} catch (IllegalArgumentException e) {
// ignore
}
}
}
private void write_real(byte[] bytes) {
try {
if (mIsTCP) {
@ -322,7 +435,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
private boolean evaluateGBDeviceEventPebble(GBDeviceEvent deviceEvent) {
if (deviceEvent instanceof GBDeviceEventVersionInfo) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
if (sharedPrefs.getBoolean("datetime_synconconnect", true)) {
LOG.info("syncing time");
write(mPebbleProtocol.encodeSetTime());
@ -396,7 +508,13 @@ public class PebbleIoThread extends GBDeviceIoThread {
GBDeviceEventAppInfo appInfoEvent = (GBDeviceEventAppInfo) deviceEvent;
setInstallSlot(appInfoEvent.freeSlot);
return false;
} else if (deviceEvent instanceof GBDeviceEventAppMessage) {
if (mEnablePebblekit) {
LOG.info("Got AppMessage event");
sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent);
}
}
return false;
}
@ -423,7 +541,17 @@ public class PebbleIoThread extends GBDeviceIoThread {
return;
}
mPBWReader = new PBWReader(uri, getContext(), gbDevice.getHardwareVersion().equals("dvt") ? "basalt" : "aplite");
String hwRev = gbDevice.getHardwareVersion();
String platformName;
if (hwRev.startsWith("snowy")) {
platformName = "basalt";
} else if (hwRev.startsWith("spalding")) {
platformName = "chalk";
} else {
platformName = "aplite";
}
mPBWReader = new PBWReader(uri, getContext(), platformName);
mPebbleInstallables = mPBWReader.getPebbleInstallables();
mCurrentInstallableIndex = 0;

View File

@ -1,7 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import android.util.Base64;
import android.util.Pair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -16,13 +20,18 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
@ -60,6 +69,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final short ENDPOINT_PUTBYTES = (short) 48879;
static final byte APPRUNSTATE_START = 1;
static final byte APPRUNSTATE_STOP = 2;
static final byte BLOBDB_INSERT = 1;
static final byte BLOBDB_DELETE = 4;
@ -70,6 +80,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte BLOBDB_REMINDER = 3;
static final byte BLOBDB_NOTIFICATION = 4;
// This is not in the Pebble protocol
static final byte NOTIFICATION_UNDEFINED = -1;
static final byte NOTIFICATION_EMAIL = 0;
static final byte NOTIFICATION_SMS = 1;
static final byte NOTIFICATION_TWITTER = 2;
@ -178,8 +191,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte TYPE_BYTEARRAY = 0;
static final byte TYPE_CSTRING = 1;
static final byte TYPE_UINT32 = 2;
static final byte TYPE_INT32 = 3;
static final byte TYPE_UINT = 2;
static final byte TYPE_INT = 3;
static final short LENGTH_PREFIX = 4;
static final short LENGTH_SIMPLEMESSAGE = 1;
@ -202,7 +215,17 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte LENGTH_UUID = 16;
private static final String[] hwRevisions = {"unknown", "ev1", "ev2", "ev2_3", "ev2_4", "v1_5", "v2_0", "evt2", "dvt"};
// base is -5
private static final String[] hwRevisions = {
// Emulator
"spalding_bb2", "snowy_bb2", "snowy_bb", "bb2", "bb",
"unknown",
// Pebble
"ev1", "ev2", "ev2_3", "ev2_4", "v1_5", "v2_0",
// Pebble Time
"snowy_evt2", "snowy_dvt", "spalding_dvt", "snowy_s3", "spalding"
};
private static Random mRandom = new Random();
boolean isFw3x = false;
@ -362,7 +385,21 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
private byte[] encodeNotification(int id, String title, String subtitle, String body, byte type, boolean hasHandle) {
@Override
public byte[] encodeNotification(NotificationSpec notificationSpec) {
boolean hasHandle = notificationSpec.id != -1;
int id = notificationSpec.id != -1 ? notificationSpec.id : mRandom.nextInt();
String title;
String subtitle = null;
// for SMS and EMAIL that came in though SMS or K9 receiver
if (notificationSpec.sender != null) {
title = notificationSpec.sender;
subtitle = notificationSpec.subject;
} else {
title = notificationSpec.title;
}
Long ts = System.currentTimeMillis();
if (!isFw3x) {
ts += (SimpleTimeZone.getDefault().getOffset(ts));
@ -371,32 +408,19 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (isFw3x) {
// 3.x notification
return encodeBlobdbNotification(id, (int) (ts & 0xffffffff), title, subtitle, body, type, hasHandle);
} else if (mForceProtocol || type != NOTIFICATION_EMAIL) {
//return encodeTimelinePin(id, (int) ((ts + 600) & 0xffffffffL), (short) 90, PebbleIconID.TIMELINE_CALENDAR, title); // really, this is just for testing
return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type);
} else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) {
// 2.x notification
return encodeExtensibleNotification(id, (int) (ts & 0xffffffff), title, subtitle, body, type, hasHandle);
return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle);
} else {
// 1.x notification on FW 2.X
String[] parts = {title, body, ts.toString(), subtitle};
return encodeMessage(ENDPOINT_NOTIFICATION, type, 0, parts);
String[] parts = {title, notificationSpec.body, ts.toString(), subtitle};
// be aware that type is at this point always NOTIFICATION_EMAIL
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, 0, parts);
}
}
@Override
public byte[] encodeSMS(String from, String body) {
return encodeNotification(mRandom.nextInt(), from, null, body, NOTIFICATION_SMS, false);
}
@Override
public byte[] encodeEmail(String from, String subject, String body) {
return encodeNotification(mRandom.nextInt(), from, subject, body, NOTIFICATION_EMAIL, false);
}
@Override
public byte[] encodeGenericNotification(String title, String details, int handle) {
return encodeNotification(handle, title, null, details, NOTIFICATION_SMS, true);
}
@Override
public byte[] encodeSetTime() {
long ts = System.currentTimeMillis();
@ -431,7 +455,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return encodeSetCallState("Where are you?", "Gadgetbridge", start ? ServiceCommand.CALL_INCOMING : ServiceCommand.CALL_END);
}
private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, byte type, boolean hasHandle) {
private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle) {
final short ACTION_LENGTH_MIN = 10;
String[] parts = {title, subtitle, body};
@ -441,17 +465,23 @@ public class PebbleProtocol extends GBDeviceProtocol {
short actions_length;
String dismiss_string;
String open_string = "Open on phone";
String mute_string = "Mute";
if (sourceName != null) {
mute_string += " " + sourceName;
}
byte dismiss_action_id;
if (hasHandle) {
actions_count = 2;
actions_count = 3;
dismiss_string = "Dismiss";
dismiss_action_id = 0x02;
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length());
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length + mute_string.getBytes().length);
} else {
actions_count = 1;
dismiss_string = "Dismiss all";
dismiss_action_id = 0x03;
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length());
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length);
}
byte attributes_count = 0;
@ -509,17 +539,25 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.put((byte) 0x04); // dismiss
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) dismiss_string.length());
buf.putShort((short) dismiss_string.getBytes().length);
buf.put(dismiss_string.getBytes());
// open action
if (hasHandle) {
buf.put((byte) 0x01);
buf.put((byte) 0x02); // dissmiss - FIXME: find out how to answer to 2.x generic actions
buf.put((byte) 0x02); // generic
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) open_string.length());
buf.putShort((short) open_string.getBytes().length);
buf.put(open_string.getBytes());
buf.put((byte) 0x04);
buf.put((byte) 0x02); // generic
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) mute_string.getBytes().length);
buf.put(mute_string.getBytes());
}
return buf.array();
@ -556,40 +594,116 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, byte type, boolean hasHandle) {
private byte[] encodeTimelinePin(int id, int timestamp, short duration, int icon_id, String title) {
final short TIMELINE_PIN_LENGTH = 46;
icon_id |= 0x80000000;
UUID uuid = new UUID(mRandom.nextLong(), ((long) mRandom.nextInt() << 32) | id);
byte attributes_count = 2;
byte actions_count = 0;
int attributes_length = 10 + title.getBytes().length;
int pin_length = TIMELINE_PIN_LENGTH + attributes_length;
ByteBuffer buf = ByteBuffer.allocate(pin_length);
// pin - 46 bytes
buf.order(ByteOrder.BIG_ENDIAN);
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
buf.putLong(0); // parent
buf.putLong(0);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(timestamp); // 32-bit timestamp
buf.putShort(duration);
buf.put((byte) 0x02); // type (0x02 = pin)
buf.putShort((short) 0x0001); // flags 0x0001 = ?
buf.put((byte) 0x02); // layout (0x02 = pin?)
buf.putShort((short) attributes_length); // total length of all attributes and actions in bytes
buf.put(attributes_count);
buf.put(actions_count);
buf.put((byte) 4); // icon
buf.putShort((short) 4); // length of int
buf.putInt(icon_id);
buf.put((byte) 1); // title
buf.putShort((short) title.getBytes().length);
buf.put(title.getBytes());
return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array());
}
private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, NotificationType notificationType) {
final short NOTIFICATION_PIN_LENGTH = 46;
final short ACTION_LENGTH_MIN = 10;
String[] parts = {title, subtitle, body};
int icon_id = 0x80000000 | 1;
switch (type) {
case NOTIFICATION_EMAIL:
icon_id = 0x80000000 | 19;
int icon_id;
byte color_id;
switch (notificationType) {
case EMAIL:
icon_id = PebbleIconID.GENERIC_EMAIL;
color_id = PebbleColor.JaegerGreen;
break;
case SMS:
icon_id = PebbleIconID.GENERIC_SMS;
color_id = PebbleColor.VividViolet;
break;
default:
switch (notificationType) {
case TWITTER:
icon_id = PebbleIconID.NOTIFICATION_TWITTER;
color_id = PebbleColor.BlueMoon;
break;
case EMAIL:
icon_id = PebbleIconID.GENERIC_EMAIL;
color_id = PebbleColor.JaegerGreen;
break;
case SMS:
icon_id = PebbleIconID.GENERIC_SMS;
color_id = PebbleColor.VividViolet;
break;
case FACEBOOK:
icon_id = PebbleIconID.NOTIFICATION_FACEBOOK;
color_id = PebbleColor.VeryLightBlue;
break;
case CHAT:
icon_id = PebbleIconID.NOTIFICATION_HIPCHAT;
color_id = PebbleColor.Inchworm;
break;
default:
icon_id = PebbleIconID.NOTIFICATION_GENERIC;
color_id = PebbleColor.Red;
break;
}
break;
case NOTIFICATION_SMS:
icon_id = 0x80000000 | 45;
}
// Calculate length first
byte actions_count;
short actions_length;
String dismiss_string;
String open_string = "Open on phone";
String mute_string = "Mute";
if (sourceName != null) {
mute_string += " " + sourceName;
}
byte dismiss_action_id;
if (hasHandle) {
actions_count = 2;
actions_count = 3;
dismiss_string = "Dismiss";
dismiss_action_id = 0x02;
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length());
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length + mute_string.getBytes().length);
} else {
actions_count = 1;
dismiss_string = "Dismiss all";
dismiss_action_id = 0x03;
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length());
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length);
}
byte attributes_count = 1; // icon
short attributes_length = (short) (7 + actions_length); // icon
byte attributes_count = 2; // icon
short attributes_length = (short) (11 + actions_length);
if (parts != null) {
for (String s : parts) {
if (s == null || s.equals("")) {
@ -642,30 +756,41 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.put((byte) 4); // icon
buf.putShort((short) 4); // length of int
buf.putInt(icon_id);
buf.putInt(0x80000000 | icon_id);
buf.put((byte) 28); // background_color
buf.putShort((short) 1); // length of int
buf.put(color_id);
// dismiss action
buf.put(dismiss_action_id);
buf.put((byte) 0x02); // generic action, dismiss did not do anything
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) dismiss_string.length());
buf.putShort((short) dismiss_string.getBytes().length);
buf.put(dismiss_string.getBytes());
// open action
// open and mute actions
if (hasHandle) {
buf.put((byte) 0x01);
buf.put((byte) 0x02); // generic action
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) open_string.length());
buf.putShort((short) open_string.getBytes().length);
buf.put(open_string.getBytes());
buf.put((byte) 0x04);
buf.put((byte) 0x02); // generic action
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) mute_string.getBytes().length);
buf.put(mute_string.getBytes());
}
return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array());
}
public byte[] encodeActionResponse2x(int id, int iconId, String caption) {
short length = (short) (18 + caption.length());
public byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) {
short length = (short) (18 + caption.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(length);
@ -673,20 +798,20 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(NOTIFICATIONACTION_RESPONSE);
buf.putInt(id);
buf.put((byte) 0x01); // action id?
buf.put(actionId);
buf.put(NOTIFICATIONACTION_ACK);
buf.put((byte) 2); //nr of attributes
buf.put((byte) 6); // icon
buf.putShort((short) 4); // length
buf.putInt(iconId);
buf.put((byte) 2); // title
buf.putShort((short) caption.length());
buf.putShort((short) caption.getBytes().length);
buf.put(caption.getBytes());
return buf.array();
}
public byte[] encodeActionResponse(UUID uuid, int iconId, String caption) {
short length = (short) (29 + caption.length());
short length = (short) (29 + caption.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(length);
@ -701,7 +826,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.putShort((short) 4); // length
buf.putInt(0x80000000 | iconId);
buf.put((byte) 2); // title
buf.putShort((short) caption.length());
buf.putShort((short) caption.getBytes().length);
buf.put(caption.getBytes());
return buf.array();
}
@ -710,7 +835,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
final short METADATA_LENGTH = 126;
byte[] name_buf = new byte[96];
System.arraycopy(appName.getBytes(), 0, name_buf, 0, appName.length());
System.arraycopy(appName.getBytes(), 0, name_buf, 0, appName.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(METADATA_LENGTH);
buf.order(ByteOrder.BIG_ENDIAN);
@ -794,19 +919,20 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
@Override
public byte[] encodeAppStart(UUID uuid) {
public byte[] encodeAppStart(UUID uuid, boolean start) {
if (isFw3x) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPRUNSTATE);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_APPRUNSTATE);
buf.putShort(ENDPOINT_APPRUNSTATE);
buf.put(APPRUNSTATE_START);
buf.put(start ? APPRUNSTATE_START : APPRUNSTATE_STOP);
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
return buf.array();
} else {
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
pairs.add(new Pair<>(1, (Object) 1)); // launch
int param = start ? 1 : 0;
pairs.add(new Pair<>(1, (Object) param));
return encodeApplicationMessagePush(ENDPOINT_LAUNCHER, uuid, pairs);
}
}
@ -1034,10 +1160,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
while (dictSize-- > 0) {
Integer key = buf.getInt();
byte type = buf.get();
short length = buf.getShort(); // length
short length = buf.getShort();
switch (type) {
case TYPE_INT32:
case TYPE_UINT32:
case TYPE_INT:
case TYPE_UINT:
dict.add(new Pair<Integer, Object>(key, buf.getInt()));
break;
case TYPE_CSTRING:
@ -1056,16 +1182,89 @@ public class PebbleProtocol extends GBDeviceProtocol {
return dict;
}
private GBDeviceEvent[] decodeDictToJSONAppMessage(UUID uuid, ByteBuffer buf) throws JSONException {
buf.order(ByteOrder.LITTLE_ENDIAN);
byte dictSize = buf.get();
if (dictSize == 0) {
LOG.info("dict size is 0, ignoring");
return null;
}
JSONArray jsonArray = new JSONArray();
while (dictSize-- > 0) {
JSONObject jsonObject = new JSONObject();
Integer key = buf.getInt();
byte type = buf.get();
short length = buf.getShort();
jsonObject.put("key", key);
jsonObject.put("length", length);
switch (type) {
case TYPE_UINT:
jsonObject.put("type", "uint");
if (length == 1) {
jsonObject.put("value", buf.get() & 0xff);
} else if (length == 2) {
jsonObject.put("value", buf.getShort() & 0xffff);
} else {
jsonObject.put("value", buf.getInt() & 0xffffffffL);
}
break;
case TYPE_INT:
jsonObject.put("type", "int");
if (length == 1) {
jsonObject.put("value", buf.get());
} else if (length == 2) {
jsonObject.put("value", buf.getShort());
} else {
jsonObject.put("value", buf.getInt());
}
break;
case TYPE_BYTEARRAY:
case TYPE_CSTRING:
byte[] bytes = new byte[length];
buf.get(bytes);
if (type == TYPE_BYTEARRAY) {
jsonObject.put("type", "bytes");
jsonObject.put("value", Base64.encode(bytes, Base64.NO_WRAP));
} else {
jsonObject.put("type", "string");
jsonObject.put("value", Arrays.toString(bytes));
}
break;
default:
LOG.info("unknown type in appmessage, ignoring");
return null;
}
jsonArray.put(jsonObject);
}
// this is a hack we send an ack to the Pebble immediately because we cannot map the transaction_id from the intent back to a uuid yet
GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id);
GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage();
appMessage.appUUID = uuid;
appMessage.id = last_id & 0xff;
appMessage.message = jsonArray.toString();
return new GBDeviceEvent[]{appMessage, sendBytesAck};
}
byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList<Pair<Integer, Object>> pairs) {
int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict)
for (Pair<Integer, Object> pair : pairs) {
length += 7; // key + type + length
if (pair.second instanceof Integer) {
length += 4;
} else if (pair.second instanceof Short) {
length += 2;
} else if (pair.second instanceof Byte) {
length += 1;
} else if (pair.second instanceof String) {
length += ((String) pair.second).length() + 1;
length += ((String) pair.second).getBytes().length + 1;
} else if (pair.second instanceof byte[]) {
length += ((byte[]) pair.second).length;
}
}
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) length);
@ -1076,24 +1275,77 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.putLong(uuid.getLeastSignificantBits());
buf.put((byte) pairs.size());
buf.order(ByteOrder.LITTLE_ENDIAN); // Um, yes, really
buf.order(ByteOrder.LITTLE_ENDIAN);
for (Pair<Integer, Object> pair : pairs) {
buf.putInt(pair.first);
if (pair.second instanceof Integer) {
buf.put(TYPE_INT32);
buf.putShort((short) 4); // length of int
buf.put(TYPE_INT);
buf.putShort((short) 4); // length
buf.putInt((int) pair.second);
} else if (pair.second instanceof Short) {
buf.put(TYPE_INT);
buf.putShort((short) 2); // length
buf.putShort((short) pair.second);
} else if (pair.second instanceof Byte) {
buf.put(TYPE_INT);
buf.putShort((short) 1); // length
buf.put((byte) pair.second);
} else if (pair.second instanceof String) {
String str = (String) pair.second;
buf.put(TYPE_CSTRING);
buf.putShort((short) (((String) pair.second).length() + 1));
buf.put(((String) pair.second).getBytes());
buf.putShort((short) (str.getBytes().length + 1));
buf.put(str.getBytes());
buf.put((byte) 0);
} else if (pair.second instanceof byte[]) {
byte[] bytes = (byte[]) pair.second;
buf.put(TYPE_BYTEARRAY);
buf.putShort((short) bytes.length);
buf.put(bytes);
}
}
return buf.array();
}
public byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) {
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
JSONObject jsonObject = (JSONObject) jsonArray.get(i);
String type = (String) jsonObject.get("type");
int key = (int) jsonObject.get("key");
int length = (int) jsonObject.get("length");
switch (type) {
case "uint":
case "int":
if (length == 1) {
pairs.add(new Pair<>(key, (Object) (byte) jsonObject.getInt("value")));
} else if (length == 2) {
pairs.add(new Pair<>(key, (Object) (short) jsonObject.getInt("value")));
} else {
if (type.equals("uint")) {
pairs.add(new Pair<>(key, (Object) (int) (jsonObject.getInt("value") & 0xffffffffL)));
} else {
pairs.add(new Pair<>(key, (Object) jsonObject.getInt("value")));
}
}
break;
case "string":
pairs.add(new Pair<>(key, (Object) jsonObject.getString("value")));
break;
case "bytes":
byte[] bytes = Base64.decode(jsonObject.getString("value"), Base64.NO_WRAP);
pairs.add(new Pair<>(key, (Object) bytes));
break;
}
} catch (JSONException e) {
return null;
}
}
return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs);
}
private static byte reverseBits(byte in) {
byte out = 0;
for (int i = 0; i < 8; i++) {
@ -1160,7 +1412,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (command == 0x02) {
int id = buf.getInt();
byte action = buf.get();
if (action >= 0x01 && action <= 0x03) {
if (action >= 0x01 && action <= 0x04) {
GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
devEvtNotificationControl.handle = id;
GBDeviceEventSendBytes sendBytesAck = null;
@ -1169,7 +1421,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
case 0x01:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN;
sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeActionResponse2x(id, 6, "Opened");
sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Opened");
break;
case 0x02:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
@ -1177,6 +1429,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
case 0x03:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL;
break;
case 0x04:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE;
sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Muted");
break;
default:
return null;
}
@ -1198,7 +1455,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
long uuid_low = buf.getLong();
int id = (int) (uuid_low & 0xffffffff);
byte action = buf.get();
if (action >= 0x01 && action <= 0x03) {
if (action >= 0x01 && action <= 0x04) {
GBDeviceEventNotificationControl dismissNotification = new GBDeviceEventNotificationControl();
dismissNotification.handle = id;
String caption = "undefined";
@ -1207,17 +1464,22 @@ public class PebbleProtocol extends GBDeviceProtocol {
case 0x01:
dismissNotification.event = GBDeviceEventNotificationControl.Event.OPEN;
caption = "Opened";
icon_id = 49;
icon_id = PebbleIconID.DURING_PHONE_CALL;
break;
case 0x02:
dismissNotification.event = GBDeviceEventNotificationControl.Event.DISMISS;
caption = "Dismissed";
icon_id = 51;
icon_id = PebbleIconID.RESULT_DISMISSED;
break;
case 0x03:
dismissNotification.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL;
caption = "All dismissed";
icon_id = 51;
icon_id = PebbleIconID.RESULT_DISMISSED;
break;
case 0x04:
dismissNotification.event = GBDeviceEventNotificationControl.Event.MUTE;
caption = "Muted";
icon_id = PebbleIconID.RESULT_MUTE;
break;
}
GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
@ -1361,11 +1623,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
buf.get(tmp, 0, 9);
Byte hwRev = buf.get();
if (hwRev > 0 && hwRev < hwRevisions.length) {
int hwRev = buf.get() + 5;
if (hwRev >= 0 && hwRev < hwRevisions.length) {
versionCmd.hwVersion = hwRevisions[hwRev];
} else if (hwRev == -3) { // basalt emulator
versionCmd.hwVersion = "dvt";
}
devEvts = new GBDeviceEvent[]{versionCmd};
break;
@ -1478,6 +1738,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
} else if (GadgetbridgePblSupport.uuid.equals(uuid)) {
ArrayList<Pair<Integer, Object>> dict = decodeDict(buf);
devEvts = mGadgetbridgePblSupport.handleMessage(dict);
} else {
try {
devEvts = decodeDictToJSONAppMessage(uuid, buf);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
break;
case APPLICATIONMESSAGE_ACK:

View File

@ -51,6 +51,6 @@ public class WeatherNeatSupport {
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
sendBytes.encodedBytes = encodeWeatherNeatMessage("Berlin", "22 C", "cloudy", 0);
return new GBDeviceEvent[] {sendBytes};
return new GBDeviceEvent[]{sendBytes};
}
}

View File

@ -3,8 +3,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.view.KeyEvent;
import org.slf4j.Logger;
@ -51,16 +53,25 @@ public class GBMusicControlReceiver extends BroadcastReceiver {
}
if (keyCode != -1) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
String audioPlayer = sharedPrefs.getString("audio_player", "default");
long eventtime = SystemClock.uptimeMillis();
Intent downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
KeyEvent downEvent = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_DOWN, keyCode, 0);
downIntent.putExtra(Intent.EXTRA_KEY_EVENT, downEvent);
if (!"default".equals(audioPlayer)) {
downIntent.setPackage(audioPlayer);
}
context.sendOrderedBroadcast(downIntent, null);
Intent upIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
KeyEvent upEvent = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_UP, keyCode, 0);
upIntent.putExtra(Intent.EXTRA_KEY_EVENT, upEvent);
if (audioPlayer != null) {
upIntent.setPackage(audioPlayer);
}
context.sendOrderedBroadcast(upIntent, null);
}
}

View File

@ -5,22 +5,23 @@ import org.slf4j.LoggerFactory;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
/**
* An abstract base class for devices speaking a serial protocol, like via
* an rfcomm bluetooth socket or a TCP socket.
*
* <p/>
* This class uses two helper classes to deal with that:
* - GBDeviceIoThread, which creates and maintains the actual socket connection and implements the transport layer
* - GBDeviceProtocol, which implements the encoding and decoding of messages, i.e. the actual device specific protocol
*
* <p/>
* Note that these two classes need to be implemented in a device specific way.
*
* <p/>
* This implementation implements all methods of {@link EventHandler}, calls the {@link GBDeviceProtocol device protocol}
* to create the device specific message for the respective events and sends them to the device via {@link #sendToDevice(byte[])}.
*/
@ -84,6 +85,7 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
/**
* Sends the given message to the device. This implementation delegates the
* writing to the {@link #getDeviceIOThread device io thread}
*
* @param bytes the message to send to the device
*/
protected void sendToDevice(byte[] bytes) {
@ -106,20 +108,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
}
@Override
public void onSMS(String from, String body) {
byte[] bytes = gbDeviceProtocol.encodeSMS(from, body);
sendToDevice(bytes);
}
@Override
public void onEmail(String from, String subject, String body) {
byte[] bytes = gbDeviceProtocol.encodeEmail(from, subject, body);
sendToDevice(bytes);
}
@Override
public void onGenericNotification(String title, String details, int handle) {
byte[] bytes = gbDeviceProtocol.encodeGenericNotification(title, details, handle);
public void onNotification(NotificationSpec notificationSpec) {
byte[] bytes = gbDeviceProtocol.encodeNotification(notificationSpec);
sendToDevice(bytes);
}
@ -148,8 +138,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
}
@Override
public void onAppStart(UUID uuid) {
byte[] bytes = gbDeviceProtocol.encodeAppStart(uuid);
public void onAppStart(UUID uuid, boolean start) {
byte[] bytes = gbDeviceProtocol.encodeAppStart(uuid, start);
sendToDevice(bytes);
}

View File

@ -2,20 +2,13 @@ package nodomain.freeyourgadget.gadgetbridge.service.serial;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
public abstract class GBDeviceProtocol {
public byte[] encodeSMS(String from, String body) {
return null;
}
public byte[] encodeEmail(String from, String subject, String body) {
return null;
}
public byte[] encodeGenericNotification(String title, String details, int handle) {
public byte[] encodeNotification(NotificationSpec notificationSpec) {
return null;
}
@ -47,7 +40,7 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodeAppStart(UUID uuid) {
public byte[] encodeAppStart(UUID uuid, boolean start) {
return null;
}
@ -70,5 +63,4 @@ public abstract class GBDeviceProtocol {
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
return null;
}
}
}

View File

@ -5,10 +5,10 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
public class DeviceHelper {
private static DeviceHelper instance = new DeviceHelper();

View File

@ -51,7 +51,8 @@ public class FileUtils {
* Reads the contents of the given InputStream into a byte array, but does not
* read more than maxLen bytes. If the stream provides more than maxLen bytes,
* an IOException is thrown.
* @param in the stream to read from
*
* @param in the stream to read from
* @param maxLen the maximum number of bytes to read/return
* @return the bytes read from the InputStream
* @throws IOException when reading failed or when maxLen was exceeded

View File

@ -40,6 +40,7 @@ public class GB {
public static final int NOTIFICATION_ID = 1;
public static final int NOTIFICATION_ID_INSTALL = 2;
public static final int NOTIFICATION_ID_LOW_BATTERY = 3;
public static final int NOTIFICATION_ID_TRANSFER = 4;
private static final Logger LOG = LoggerFactory.getLogger(GB.class);
public static final int INFO = 1;
@ -80,6 +81,11 @@ public class GB {
nm.notify(id, notification);
}
private static void removeNotification(int id, Context context) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(id);
}
public static void setReceiversEnableState(boolean enable, Context context) {
LOG.info("Setting broadcast receivers to: " + enable);
final Class<?>[] receiverClasses = {
@ -275,6 +281,41 @@ public class GB {
}
}
private static Notification createTransferNotification(String text, boolean ongoing,
int percentage, Context context) {
Intent notificationIntent = new Intent(context, ControlCenter.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
notificationIntent, 0);
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentTitle(context.getString(R.string.app_name))
.setContentText(text)
.setContentIntent(pendingIntent)
.setOngoing(ongoing);
if (ongoing) {
nb.setProgress(100, percentage, percentage == 0);
nb.setSmallIcon(android.R.drawable.stat_sys_download);
} else {
nb.setProgress(0, 0, false);
nb.setSmallIcon(android.R.drawable.stat_sys_download_done);
}
return nb.build();
}
public static void updateTransferNotification(String text, boolean ongoing, int percentage, Context context) {
if (percentage == 100) {
removeNotification(NOTIFICATION_ID_TRANSFER, context);
} else {
Notification notification = createTransferNotification(text, ongoing, percentage, context);
updateNotification(notification, NOTIFICATION_ID_TRANSFER, context);
}
}
private static Notification createInstallNotification(String text, boolean ongoing,
int percentage, Context context) {
Intent notificationIntent = new Intent(context, ControlCenter.class);
@ -314,7 +355,7 @@ public class GB {
notificationIntent, 0);
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
.setContentTitle( context.getString(R.string.notif_battery_low_title))
.setContentTitle(context.getString(R.string.notif_battery_low_title))
.setContentText(text)
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.ic_notification_low_battery)
@ -339,4 +380,10 @@ public class GB {
public static GBEnvironment env() {
return environment;
}
public static void assertThat(boolean condition, String errorMessage) {
if (!condition) {
throw new AssertionError(errorMessage);
}
}
}

View File

@ -11,10 +11,10 @@
android:layout_weight="40">
</com.github.mikephil.charting.charts.PieChart>
<nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/sleepchart"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="20" />

View File

@ -0,0 +1,16 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AppBlacklistActivity">
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/appListView"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

View File

@ -3,7 +3,7 @@
android:layout_height="match_parent"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
<nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/activitysleepchart"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -11,10 +11,10 @@
android:layout_weight="20">
</com.github.mikephil.charting.charts.PieChart>
<nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/sleepchart"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="20" />

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:padding="8dp">
<CheckBox
android:id="@+id/item_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:clickable="false"
android:focusable="false" />
<ImageView
android:id="@+id/item_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_toRightOf="@+id/item_checkbox" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/item_image"
android:orientation="vertical"
android:paddingEnd="8dp"
android:paddingStart="8dp">
<TextView
android:id="@+id/item_name"
style="@style/Base.TextAppearance.AppCompat.SearchResult.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollHorizontally="false"
android:singleLine="true"
android:text="Item Name" />
<TextView
android:id="@+id/item_details"
style="@style/Base.TextAppearance.AppCompat.SearchResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Item Description" />
</LinearLayout>
</RelativeLayout>

View File

@ -14,6 +14,7 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">App Manager</string>
<string name="appmananger_app_delete">Löschen</string>
<!--Strings related to AppBlacklist-->
<!--Strings related to FwAppInstaller-->
<string name="title_activity_fw_app_insaller">FW/App Installer</string>
<string name="fw_upgrade_notice">Es soll die Firmware %s anstelle der aktuell installierten Version aufs Mi Band gespielt werden.</string>
@ -24,13 +25,15 @@
<string name="title_activity_settings">Einstellungen</string>
<string name="pref_header_general">Allgemeine Einstellungen</string>
<string name="pref_title_general_autoconnectonbluetooth">Verbinde, wenn Bluetooth eingeschaltet wird</string>
<string name="pref_title_audo_player">Bevorzugter Audioplayer</string>
<string name="pref_default">Standard</string>
<string name="pref_header_datetime">Datum und Zeit</string>
<string name="pref_title_datetime_syctimeonconnect">Uhrzeit synchronisieren</string>
<string name="pref_summary_datetime_syctimeonconnect">Synchronisiere die Urzeit mit dem Gerät (bei Verbindingsaufbau und wenn die Zeit oder Zeitzone auf dem Android Gerät eingestellt wird)</string>
<string name="pref_header_notifications">Benachrichtigungen</string>
<string name="pref_title_notifications_repetitions">Wiederholungen</string>
<string name="pref_title_notifications_call">Anrufe</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_incoming_call">Eingehende Anrufe</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Pebble Nachrichten</string>
<string name="pref_summary_notifications_pebblemsg">Unterstützung für Anwendungen, die Benachrichtigungen per Intent an die Pebble schicken. Kann für Conversations verwendet werden.</string>
@ -42,10 +45,13 @@
<string name="pref_header_development">Entwickleroptionen</string>
<string name="pref_title_development_miaddr">Mi Band MAC Adresse</string>
<string name="pref_title_pebble_settings">Pebble Einstellungen</string>
<string name="pref_title_enable_pebblekit">Erlaube Zugriff von anderen Android Apps</string>
<string name="pref_summary_enable_pebblekit">Experimentelle Unterstützung für Android Apps, die PebbleKit benutzen</string>
<string name="pref_title_pebble_forceprotocol">Benachrichtigungsprotokoll erzwingen</string>
<string name="pref_summary_pebble_forceprotocol">Diese Option erzwingt das neuste Benachrichtigungsprotokoll abhängig von der Firmwareversion. NUR EINSCHALTEN, WENN DU WEISST WAS DU TUST!</string>
<string name="pref_title_pebble_forceuntested">Ungetestete Features freischalten</string>
<string name="pref_summary_pebble_forceuntested">Schaltet ungetetestete Features frei. TU DIES NUR, WENN DU WEIßT, WAS DU TUST!</string>
<string name="pref_title_pebble_reconnect_attempts">Neuverbindungsversuche</string>
<string name="not_connected">nicht verbunden</string>
<string name="connecting">verbinde</string>
<string name="connected">verbunden</string>
@ -144,7 +150,7 @@
<string name="controlcenter_start_activitymonitor">Deine Aktivität (ALPHA)</string>
<string name="cannot_connect">Kann keine Verbindung herstellen: %1$s</string>
<string name="installer_activity_unable_to_find_handler">Kann keinen Handler für die Installation dieser Datei finden.</string>
<string name="pbw_install_handler_unable_to_install">Kann die gegebene Datei %1$s nicht installieren</string>
<string name="pbw_install_handler_unable_to_install">Konnte folgende Datei nicht installieren: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">Kann die gegebene Firmware nicht installieren. Sie passt nicht zur Hardware Revision der Pebble.</string>
<string name="installer_activity_wait_while_determining_status">Bitte warten während der Installationsoption festgestellt wird...</string>
<string name="notif_battery_low_title">Gadget Akkustand niedrig!</string>
@ -172,4 +178,6 @@
<string name="chart_steps">Schritte</string>
<string name="liveactivity_live_activity">Live Aktivität</string>
<string name="weeksteps_today_steps_description">Schritte heute, Ziel: %1$s</string>
<string name="pref_title_dont_ack_transfer">Transfer von Aktivitätsdaten nicht bestätigen</string>
<string name="pref_summary_dont_ack_transfers">Wenn der Transfer der Aktivitätsdaten nicht bestätigt wird, werden die Daten nicht auf dem Mi Band gelöscht. Das ist Sinnvoll, wenn neben Gadgetbridge noch andere Apps auf das Mi Band zugreifen.</string>
</resources>

View File

@ -14,6 +14,8 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestor de App</string>
<string name="appmananger_app_delete">Borrar</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">Lista negra de notificaciones</string>
<!--Strings related to FwAppInstaller-->
<string name="title_activity_fw_app_insaller">Instalador de FW/App</string>
<string name="fw_upgrade_notice">Estás a punto de instalar el firmware %s en lugar del que está en tu MiBand.</string>
@ -24,13 +26,14 @@
<string name="title_activity_settings">Ajustes</string>
<string name="pref_header_general">Ajustes generales</string>
<string name="pref_title_general_autoconnectonbluetooth">Conectar al encender el bluetooth</string>
<string name="pref_title_audo_player">Reproductor de audio preferido</string>
<string name="pref_default">Predeterminado</string>
<string name="pref_header_datetime">Fecha y hora</string>
<string name="pref_title_datetime_syctimeonconnect">Sincronizar hora</string>
<string name="pref_summary_datetime_syctimeonconnect">Sincroniza la hora en el dispositivo cuando se conecte o se cambie la hora o zona horaria en Android</string>
<string name="pref_header_notifications">Notificaciones</string>
<string name="pref_title_notifications_repetitions">Repeticiones</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_incoming_call">Llamadas entrantes</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Mensajes de Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Soporte para aplicaciones que envían notificaciones a Pebble a través de Intent. Puede ser usado por Conversations.</string>
@ -39,6 +42,7 @@
<string name="always">siempre</string>
<string name="when_screen_off">cuando la pantalla está apagada</string>
<string name="never">nunca</string>
<string name="pref_blacklist">Añadir app a la lista negra</string>
<string name="pref_header_development">Opciones de desarrollador</string>
<string name="pref_title_development_miaddr">Dirección de MiBand</string>
<string name="pref_title_pebble_settings">Ajustes de Pebble</string>
@ -46,6 +50,7 @@
<string name="pref_summary_pebble_forceprotocol">Esta opción fuerza el uso del último protocolo de notificación dependiendo de la versión de firmware. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO!</string>
<string name="pref_title_pebble_forceuntested">Habilitar características no probadas</string>
<string name="pref_summary_pebble_forceuntested">Habilita características que no han sido comprobadas. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO!</string>
<string name="pref_title_pebble_reconnect_attempts">Intentos de reconectar</string>
<string name="not_connected">no conectado</string>
<string name="connecting">conectando</string>
<string name="connected">conectado</string>
@ -144,7 +149,7 @@
<string name="controlcenter_start_activitymonitor">Tu actividad (ALPHA)</string>
<string name="cannot_connect">No se puede conectar: %1$s</string>
<string name="installer_activity_unable_to_find_handler">No se ha podido encontrar un controlador para instalar este archivo.</string>
<string name="pbw_install_handler_unable_to_install">No se puede instalar el archivo: %1$s</string>
<string name="pbw_install_handler_unable_to_install">No se ha podido instalar el fichero: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">No se puede instalar este firmware: no coincide con la revision hardware de tu Pebble.</string>
<string name="installer_activity_wait_while_determining_status">Por favor, espera mientras se determina el estado de la instalación...</string>
<string name="notif_battery_low_title">Batería baja del Gadget!</string>
@ -172,4 +177,6 @@
<string name="chart_steps">Pasos</string>
<string name="liveactivity_live_activity">Actividad</string>
<string name="weeksteps_today_steps_description">Pasos hoy, objetivo: %1$s</string>
<string name="pref_title_dont_ack_transfer">No \"ackear\" transferencia de datos de actividad</string>
<string name="pref_summary_dont_ack_transfers">Si los datos de actividad no son \"ackeados\" a la banda, no serán despejados. Útil si GB se usa conjuntamente con otras apps.</string>
</resources>

View File

@ -13,10 +13,11 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestionnaire d\'application</string>
<string name="appmananger_app_delete">Supprimer</string>
<!--Strings related to AppBlacklist-->
<!--Strings related to FwAppInstaller-->
<string name="fw_upgrade_notice">Vous êtes sur le point d\'installer le firmware %s à la place de celui qui est actuellement sur votre Mi Band.</string>
<string name="miband_firmware_known">Ce firmware a été testé et est connu pour être compatible avec Gadgetbridge.</string>
<string name="miband_firmware_unknown_warning">Ce firmware n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge. \ N \ nIl n\'est pas conseillé de flasher votre Mi Band.</string>
<string name="miband_firmware_unknown_warning">Ce firmware n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge. \n \n Il n\'est pas conseillé de flasher votre Mi Band.</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">Paramètre</string>
<string name="pref_header_general">Paramètres généraux</string>
@ -27,26 +28,25 @@
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_notifications_repetitions">Répétitions</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_incoming_call">Appel entrant</string>
<string name="pref_title_notifications_k9mail">K9-Email</string>
<string name="pref_title_notifications_pebblemsg">Message Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Support pour les applications qui envoient des notifications au Pebble via Intent. Peut être utilisé pour les conversations.</string>
<string name="pref_title_notifications_generic">Support des notififactions génériques</string>
<string name="pref_title_whenscreenon">même lorsque l\'écran est allumé</string>
<string name="always">toujours</string>
<string name="when_screen_off">quand l\'écran est éteint</string>
<string name="never">jamais</string>
<string name="pref_title_whenscreenon">Même lorsque l\'écran est allumé</string>
<string name="always">Toujours</string>
<string name="when_screen_off">Quand l\'écran est éteint</string>
<string name="never">Jamais</string>
<string name="pref_header_development">Options développeur</string>
<string name="pref_title_development_miaddr">Adresse Mi Band</string>
<string name="pref_title_pebble_settings">Paramètres Pebble</string>
<string name="pref_title_pebble_forceprotocol">Protocole des notifications en vigueur</string>
<string name="pref_summary_pebble_forceprotocol">Cette option force l\'utilisant du dernier protocole de notification qui dépend de la verrsion du firmware. ACTIVER LA UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES!</string>
<string name="pref_title_pebble_forceuntested">Activités les fonctionnalités non testé</string>
<string name="pref_summary_pebble_forceuntested">Activer les fonctionnalités non testés</string>
<string name="not_connected">non connecté</string>
<string name="connecting">en train de se connecter</string>
<string name="connected">connecté</string>
<string name="unknown_state">état inconnu</string>
<string name="pref_summary_pebble_forceprotocol">Cette option force l\'utilisation du dernier protocole de notification qui dépend de la verrsion du firmware. ACTIVER LA UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES!</string>
<string name="pref_title_pebble_forceuntested">Activer les fonctionnalités non testé</string>
<string name="pref_summary_pebble_forceuntested">Activer les fonctionnalités non testés. ACTIVER UNIQUEMENT SI VOUS SAVEZ CE QE VOUS FAITES!</string>
<string name="not_connected">Non connecté</string>
<string name="connecting">En train de se connecter</string>
<string name="connected">Connecté</string>
<string name="unknown_state">État inconnu</string>
<string name="connectionstate_hw_fw">HW: %1$s FW: %2$s</string>
<string name="connectionstate_fw">FW: %1$s</string>
<string name="_unknown_">(inconnu)</string>
@ -55,21 +55,22 @@
<string name="this_is_a_test_notification_from_gadgetbridge">Ceci est un test de notification venant de Gadgetbridge</string>
<string name="bluetooth_is_not_supported_">Le bluetooth n\'est pas supporté</string>
<string name="bluetooth_is_disabled_">Le bluetooth est désactivé</string>
<string name="tap_connected_device_for_app_mananger">Taper sur lpour le connecter au gestionnaire d\'application</string>
<string name="tap_connected_device_for_app_mananger">Taper sur l\'appareil pour le connecter au gestionnaire d\'application</string>
<string name="tap_a_device_to_connect">Tapper sur l\'appareil pour le connecter</string>
<string name="cannot_connect_bt_address_invalid_">Ne peut être connecter. Ladresse bluetooth est invalide? </string>
<string name="gadgetbridge_running">Gadgetbridge est en fonctionnement</string>
<string name="installing_binary_d_d">Installation du binaire %1$d/%2$d</string>
<string name="installation_failed_">installation en échec</string>
<string name="installation_successful">installation réalisé</string>
<string name="installation_failed_">Echec d\'installation</string>
<string name="installation_successful">Installation réalisé</string>
<string name="firmware_install_warning">VOUS ÊTES EN TRAIN D\'INSTALLER UN FIRMWARE, PROCEDEZ À VOS PROPRES RISQUES. CE FIRMWARE EST POUR LA VERSION HW: %s</string>
<string name="app_install_info">Vous êtes sur le point d\'installer l\'application suivante:\n\n\n%1$s Version %2$s par %3$s\n</string>
<string name="n_a">N/A</string>
<string name="initialized">initialisé</string>
<string name="initialized">Initialisé</string>
<string name="appversion_by_creator">%1$s par %2$s</string>
<string name="title_activity_discovery">Décourir les appareils</string>
<string name="discovery_stop_scanning">Arreter de scanner</string>
<string name="discovery_start_scanning">Démarrer le scan</string>
<string name="action_discover">Connecter un nouvelle appareil</string>
<string name="device_with_rssi">%1$s (%2$s)</string>
<string name="title_activity_android_pairing">Coupler l\'appareil</string>
<string name="android_pairing_hint">Utiliser l\'appareillage bluetouth d\'android pour coupler l\'appareil</string>
@ -78,11 +79,11 @@
<string name="message_cannot_pair_no_mac">Aucune adresse mac fournis, ne peut être couplé</string>
<string name="preferences_category_device_specific_settings">Paramètres spécifique à l\'appareil </string>
<string name="preferences_miband_settings">Paramètres Mi Band</string>
<string name="male">homme</string>
<string name="female">femme</string>
<string name="other">autre</string>
<string name="left">gauche</string>
<string name="right">droite</string>
<string name="male">Homme</string>
<string name="female">Femme</string>
<string name="other">Autre</string>
<string name="left">Gauche</string>
<string name="right">Droite</string>
<string name="miband_pairing_using_dummy_userdata">Aucune donnée utilisateur valide fournis, utilisation des données fictives pour le moment</string>
<string name="miband_pairing_tap_hint">Quand votre Mi Band vibre et clignote, appuyez dessus plusieurs fois d\'affilée.</string>
<string name="appinstaller_install">Installer</string>
@ -98,7 +99,7 @@
<string name="pref_header_vibration_count">Nombre de vibration</string>
<string name="title_activity_sleepmonitor">Moniteur de sommeil</string>
<string name="pref_write_logfiles">Ecrire le fichier de logs (besoin de redemarrer)</string>
<string name="initializing">iniitialisation</string>
<string name="initializing">Initialisation</string>
<string name="busy_task_fetch_activity_data">Récupération des données d\'activité</string>
<string name="sleep_activity_date_range">De %1$s à %2$s</string>
<string name="miband_prefs_wearside">Port main gauche ou droite?</string>
@ -130,17 +131,16 @@
<string name="alarm_thu_short">Jeu</string>
<string name="alarm_fri_short">Ven</string>
<string name="alarm_sat_short">Sam</string>
<string name="alarm_smart_wakeup">réveil intelligent</string>
<string name="alarm_smart_wakeup">Réveil intelligent</string>
<string name="user_feedback_miband_set_alarms_failed">Il y avais une erreur lors du paramétrage des alarmes, s\'il vous plaît essayer à nouveau!</string>
<string name="user_feedback_miband_set_alarms_ok">Alarmes envoyer à l\'appareil</string>
<string name="chart_no_data_synchronize">Aucune donnée. Synchroniser l\'appareil?</string>
<string name="user_feedback_miband_activity_data_transfer">A propos du transférer %1$s de données à partir de %2$s</string>
<string name="user_feedback_miband_activity_data_transfer">À propos du transférer %1$s de données à partir de %2$s</string>
<string name="miband_prefs_fitness_goal">Objectif de pas par jour</string>
<string name="dbaccess_error_executing">Erreur dexécution %1$s\'</string>
<string name="controlcenter_start_activitymonitor">Votre activité (ALPHA)</string>
<string name="cannot_connect">Impossible de se connecter: %1$s</string>
<string name="installer_activity_unable_to_find_handler">Impossible de trouver un gestionnaire pour installer ce fichier.</string>
<string name="pbw_install_handler_unable_to_install">Impossible d\'installer le fichier donné: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">Impossible d\'installer le firmware donnée: il ne correspond pas à la version du matériel de votre Pebble.</string>
<string name="installer_activity_wait_while_determining_status">S\'il vous plait patientez pendant la détermination du status de l\'installation...</string>
<string name="notif_battery_low_title">Gadget batterie Vide!</string>
@ -160,9 +160,10 @@
<string name="pbwinstallhandler_correct_hw_revision">Révision correcte du matériel</string>
<string name="pbwinstallhandler_incorrect_hw_revision">Version Hardware incorrecte!</string>
<string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
<string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problème avec le transfert du firmware. Ne redémarrez pas votre bande Mi!</string>
<string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problème avec le transfert du firmware. Ne redémarrez pas votre Mi band!</string>
<string name="updatefirmwareoperation_metadata_updateproblem">Problème avec le transfert de métadonnées du firmware</string>
<string name="updatefirmwareoperation_update_complete">Installation complète du firmware</string>
<string name="updatefirmwareoperation_update_complete_rebooting">Installation complète du firmware, redémarrage de l\'appareil</string>
<string name="updatefirmwareoperation_write_failed">Échec lors de l\'écriture du firmware</string>
<string name="chart_steps">Pas</string>
</resources>

View File

@ -7,13 +7,21 @@
<string name="action_quit">Esci</string>
<string name="controlcenter_fetch_activity_data">Sincronizza</string>
<string name="controlcenter_start_sleepmonitor">Monitoraggio del sonno (ALPHA)</string>
<string name="controlcenter_find_device">Trova dispositivo smarrito...</string>
<string name="controlcenter_take_screenshot">Salva uno screenshot</string>
<string name="controlcenter_disconnect">Disconetti</string>
<string name="title_activity_debug">Debug</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestione app</string>
<string name="appmananger_app_delete">Cancella</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">Blocco notifiche</string>
<!--Strings related to FwAppInstaller-->
<string name="title_activity_fw_app_insaller">Installazione FW/App</string>
<string name="fw_upgrade_notice">Il firmware %s verrà installato al posto di quello attualmente sulla Mi Band.</string>
<string name="miband_firmware_known">Questo firmware è stato testato ed è compatibile con Gadgetbridge.</string>
<string name="miband_firmware_unknown_warning">Questo firmware NON è stato testato e potrebbe non essere compatibile con Gadgetbridge.\n\nInstallarlo a proprio rischio sulla Mi Band.</string>
<string name="miband_firmware_suggest_whitelist">Se hai intenzione di procedere e tutto continua a funzionare ti invitiamo a contattare gli sviluppatori e dire loro di aggiungere il firmware: %s a quelli testati</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">Impostazioni</string>
<string name="pref_header_general">Impostazioni globali</string>
@ -24,7 +32,6 @@
<string name="pref_header_notifications">Notifiche</string>
<string name="pref_title_notifications_repetitions">Ripetizioni</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_incoming_call">Chiamate in arrivo</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Pebble Messages</string>
<string name="pref_summary_notifications_pebblemsg">Supporto per applicazioni che inviano le notifiche a Pebble usando Intents. Può essere usato per Conversations.</string>
@ -33,11 +40,14 @@
<string name="always">sempre</string>
<string name="when_screen_off">se lo schermo è spento</string>
<string name="never">mai</string>
<string name="pref_blacklist">Blocca applicazioni</string>
<string name="pref_header_development">Opzioni di sviluppo</string>
<string name="pref_title_development_miaddr">Indirizzo Miband</string>
<string name="pref_title_pebble_settings">Impostazioni Pebble</string>
<string name="pref_title_pebble_forceprotocol">Forza protocollo delle notifiche</string>
<string name="pref_summary_pebble_forceprotocol">Questa opzione forza l\'utilizzo della versione più recente delle notifiche in dipendenza del firmware del tuo dispositivo. ABILITALO SOLO SE SAI COSA STAI FACENDO!</string>
<string name="pref_title_pebble_forceuntested">Abilita funzionalità non testate</string>
<string name="pref_summary_pebble_forceuntested">Abilita funzionalità non testate. ABILITARE SOLO SE SI SA QUELLO CHE SI STA FACENDO!</string>
<string name="not_connected">non connesso</string>
<string name="connecting">in collegamento</string>
<string name="connected">connesso</string>
@ -57,6 +67,7 @@
<string name="installing_binary_d_d">installazione del binario %1$d/%2$d</string>
<string name="installation_failed_">Installazione fallita!</string>
<string name="installation_successful">installazione conclusa con successo</string>
<string name="firmware_install_warning">STAI INSTALLANDO UN FIRMWARE, PROCEDI A TUO RISCHIO.\n\n Questo firmware è per la versione HW: %s</string>
<string name="app_install_info">Si sta per installare la app:\n\n\n%1$s Versione %2$s di %3$s\n</string>
<string name="n_a">N/A</string>
<string name="initialized">inizializzato</string>
@ -64,6 +75,7 @@
<string name="title_activity_discovery">Ricerca dispositivi</string>
<string name="discovery_stop_scanning">Interrompi scansione</string>
<string name="discovery_start_scanning">Inizia scansione</string>
<string name="action_discover">Connetti un nuovo dispositivo</string>
<string name="device_with_rssi">%1$s (%2$s)</string>
<string name="title_activity_android_pairing">Accoppia dispositivo</string>
<string name="android_pairing_hint">Utilizza la funzione del sistema operativo per accoppiare il dispositivo.</string>
@ -124,9 +136,44 @@
<string name="alarm_thu_short">Gio</string>
<string name="alarm_fri_short">Ven</string>
<string name="alarm_sat_short">Sab</string>
<string name="alarm_smart_wakeup">Risveglio intelligente</string>
<string name="alarm_smart_wakeup">Sveglia intelligente</string>
<string name="user_feedback_miband_set_alarms_failed">C\'è stato un errore impostando le sveglie, prova di nuovo.</string>
<string name="user_feedback_miband_set_alarms_ok">Sveglie sincronizzate con il dispositivo!</string>
<string name="chart_no_data_synchronize">Non ci sono dati. Effettua una sincronizzazione con il tuo device.</string>
<string name="user_feedback_miband_activity_data_transfer">Vengono trasferiti %1$s a partire dal %2$s</string>
<string name="miband_prefs_fitness_goal">Traguardo giornaliero di passi</string>
<string name="dbaccess_error_executing">Errore eseguendo \'%1$s\'</string>
<string name="controlcenter_start_activitymonitor">Le tue attività (ALPHA)</string>
<string name="cannot_connect">Impossibile connettere: %1$s</string>
<string name="installer_activity_unable_to_find_handler">Impossibile aprire questo file.</string>
<string name="pbw_install_handler_unable_to_install">Impossibile installare il file: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">Impossibile installare il firmware: non corrisponde alla versione hardware del tuo Pebble.</string>
<string name="installer_activity_wait_while_determining_status">Lo stato dell\'installazione viene rilevato...</string>
<string name="notif_battery_low_title">Batteria del dispositivo bassa!</string>
<string name="notif_battery_low_percent">%1$s batteria rimanente: %2$s%%</string>
<string name="notif_battery_low_bigtext_last_charge_time">Ultima ricarica: %s \n</string>
<string name="notif_battery_low_bigtext_number_of_charges">Numero di ricariche: %s</string>
<string name="sleepchart_your_sleep">Il tuo sonno</string>
<string name="weekstepschart_steps_a_week">Passi della settimana</string>
<string name="activity_sleepchart_activity_and_sleep">Attività e sonno</string>
<string name="updating_firmware">Aggiornamento del Firmware...</string>
<string name="fwapp_install_device_not_ready">Il file non può essere installato, il dispositivo non è pronto.</string>
<string name="miband_installhandler_miband_firmware">Firmware Mi Band: %1$s</string>
<string name="miband_fwinstaller_compatible_version">Versione compatibile</string>
<string name="miband_fwinstaller_untested_version">Versione non testata!</string>
<string name="fwappinstaller_connection_state">Connessione al device: %1$s</string>
<string name="pbw_installhandler_pebble_firmware">Firmware Pebble: %1$s</string>
<string name="pbwinstallhandler_correct_hw_revision">Versione hardware corretta</string>
<string name="pbwinstallhandler_incorrect_hw_revision">Conflitto nella versione hardware!</string>
<string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
<string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problema durante il caricamento del firmware. NON RIAVVIARE la Mi Band!</string>
<string name="updatefirmwareoperation_metadata_updateproblem">Problema durante il caricamento dei metadati del firmware</string>
<string name="updatefirmwareoperation_update_complete">Installazione del firmware completata</string>
<string name="updatefirmwareoperation_update_complete_rebooting">Installazione del firmware completata, riavvio del dispositivo...</string>
<string name="updatefirmwareoperation_write_failed">Scrittura del firmware non riuscita</string>
<string name="chart_steps">Passi</string>
<string name="liveactivity_live_activity">Attività in tempo reale</string>
<string name="weeksteps_today_steps_description">Passi di oggi, traguardo: %1$s</string>
<string name="pref_title_dont_ack_transfer">Non confermare il trasferimento dati</string>
<string name="pref_summary_dont_ack_transfers">Se il trasferimento non viene confermato, i dati rimangono memorizzati sulla Mi Band. Utile se GB è usato insieme ad altre app.</string>
</resources>

View File

@ -12,6 +12,7 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">앱 관리자</string>
<string name="appmananger_app_delete">삭제</string>
<!--Strings related to AppBlacklist-->
<!--Strings related to FwAppInstaller-->
<!--Strings related to Settings-->
<string name="title_activity_settings">설정</string>
@ -23,7 +24,6 @@
<string name="pref_header_notifications">알림</string>
<string name="pref_title_notifications_repetitions">반복</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_incoming_call">걸려오는 전화</string>
<string name="pref_title_notifications_k9mail">K-9 메일</string>
<string name="pref_title_notifications_pebblemsg">Pebble 메세지</string>
<string name="pref_summary_notifications_pebblemsg">인텐트를 통해 Pebble에 알림을 전송할 수 있는 앱 지원. Conversations 앱 등에 사용할 수 있습니다.</string>

View File

@ -5,25 +5,36 @@
<string name="action_settings">Настройки</string>
<string name="action_debug">Отладка</string>
<string name="action_quit">Выход</string>
<string name="controlcenter_fetch_activity_data">Синхронизировать</string>
<string name="controlcenter_start_sleepmonitor">Анализ сна (АЛЬФА)</string>
<string name="controlcenter_find_device">Найти устройство...</string>
<string name="controlcenter_take_screenshot">Сделать снимок экрана</string>
<string name="controlcenter_disconnect">Отключиться</string>
<string name="title_activity_debug">Отладка</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">App Mananger</string>
<string name="appmananger_app_delete">Удалить</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">Заблокированные уведомления</string>
<!--Strings related to FwAppInstaller-->
<string name="title_activity_fw_app_insaller">Установщик прошивки/приложений</string>
<string name="fw_upgrade_notice">Вы собираетесь установить прошивку %s заместо текущей на вашем Mi Band.</string>
<string name="miband_firmware_known">Эта прошивка была проверена и совместима с Gadgetbridge.</string>
<string name="miband_firmware_unknown_warning">Эта прошивка не протестирована и может быть несовместима с Gadgetbridge.\n\nНе рекомендуется устанавливать её на ваш Mi Band!</string>
<string name="miband_firmware_suggest_whitelist">Если вы желаете продолжить и впоследствии всё функционирует нормально, пожалуйста, сообщите разработчикам Gadgetbridge, чтобы они пометили как совместимую версию прошивки: %s</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">Настройки</string>
<string name="pref_header_general">Общие настройки</string>
<string name="pref_title_general_autoconnectonbluetooth">Подключение к устройству при активации Bluetooth</string>
<string name="pref_title_audo_player">Предпочтительный музыкальный плеер</string>
<string name="pref_default">По-умолчанию</string>
<string name="pref_header_datetime">Дата и время</string>
<string name="pref_title_datetime_syctimeonconnect">Синхронизировать время при подключении</string>
<string name="pref_summary_datetime_syctimeonconnect">Синхронизировать время при подключении к устройству, а также при изменении времени или временной зоны в системе</string>
<string name="pref_header_notifications">Уведомления</string>
<string name="pref_title_notifications_repetitions">Повторы</string>
<string name="pref_title_notifications_call">Вызовы</string>
<string name="pref_title_notifications_sms">СМС-сообщения</string>
<string name="pref_title_notifications_incoming_call">Входящие звонки</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Сообщения Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Поддержка приложений, посылающих уведомления на Pebble с помощью Intent. Может использоваться для Conversations.</string>
@ -32,11 +43,17 @@
<string name="always">всегда</string>
<string name="when_screen_off">когда экран выключен</string>
<string name="never">никогда</string>
<string name="pref_blacklist">Нежелательные приложения</string>
<string name="pref_header_development">Настройки для разработчиков</string>
<string name="pref_title_development_miaddr">Адрес Mi Band</string>
<string name="pref_title_pebble_settings">Настройки Pebble</string>
<string name="pref_title_enable_pebblekit">Разрешить доступ приложениям третьих лиц</string>
<string name="pref_summary_enable_pebblekit">Добавить экспериментальную поддержку приложений Android, использующих PebbleKit</string>
<string name="pref_title_pebble_forceprotocol">Принудительный протокол уведомлений</string>
<string name="pref_summary_pebble_forceprotocol">Эта настройка заставляет принудительно использовать самый новый протокол уведомлений, зависящий от версии прошивки. ВКЛЮЧАЙТЕ, ТОЛЬКО ЕСЛИ ЗНАЕТЕ НА ЧТО ВЫ ИДЁТЕ.</string>
<string name="pref_title_pebble_forceuntested">Включить непроверенные функции</string>
<string name="pref_summary_pebble_forceuntested">Включить функции, которые не протестированы. ВКЛЮЧАЙТЕ НА СВОЙ СТРАХ И РИСК!</string>
<string name="pref_title_pebble_reconnect_attempts">Попытки переподключения</string>
<string name="not_connected">нет соединения</string>
<string name="connecting">соединение</string>
<string name="connected">соединено</string>
@ -56,6 +73,7 @@
<string name="installing_binary_d_d">установки бинарного файла %1$d/%2$d</string>
<string name="installation_failed_">установка не удалась!</string>
<string name="installation_successful">установка завершена</string>
<string name="firmware_install_warning">ВЫ ПЫТАЕТЕСЬ УСТАНОВИТЬ ПРОШИВКУ, ПРОДОЛЖАЙТЕ НА СВОЙ СТРАХ И РИСК.\n\n\n Эта прошивка для ревизии устройства: %s</string>
<string name="app_install_info">Вы собираетесь установить приложение:\n\n\n%1$s версия %2$s от %3$s\n</string>
<string name="n_a">Недоступно</string>
<string name="initialized">Инициализировано</string>
@ -63,6 +81,7 @@
<string name="title_activity_discovery">Поиск устройства</string>
<string name="discovery_stop_scanning">Остановить поиск</string>
<string name="discovery_start_scanning">Умный поиск</string>
<string name="action_discover">Подключиться к новому устройству</string>
<string name="device_with_rssi">%1$s (%2$s)</string>
<string name="title_activity_android_pairing">Сопряжение устройств</string>
<string name="android_pairing_hint">Для сопряжения устройств используйте диалог Android.</string>
@ -112,6 +131,7 @@
<string name="pref_screen_notification_profile_incoming_call">Уведомления о входящем звонке</string>
<string name="control_center_find_lost_device">Найти потерянное устройство</string>
<string name="control_center_cancel_to_stop_vibration">Отмените, чтобы остановить вибро</string>
<string name="title_activity_charts">Ваша активность</string>
<string name="title_activity_set_alarm">Завести Будильник</string>
<string name="controlcenter_start_configure_alarms">Завести будильник</string>
<string name="title_activity_alarm_details">Свойства будильника</string>
@ -126,4 +146,40 @@
<string name="user_feedback_miband_set_alarms_failed">Произошла ошибка при настройке будильника, попробуйте ещё!</string>
<string name="user_feedback_miband_set_alarms_ok">Будильник был послан на устройство!</string>
<string name="chart_no_data_synchronize">Нет данных. Синхронизировать устройство?</string>
<string name="user_feedback_miband_activity_data_transfer">Будет передано %1$s данных, начиная с %2$s</string>
<string name="miband_prefs_fitness_goal">Цель шагов на каждый день</string>
<string name="dbaccess_error_executing">Произошла ошибка при выполнении \'%1$s\'</string>
<string name="controlcenter_start_activitymonitor">Ваша активность (АЛЬФА)</string>
<string name="cannot_connect">Подключиться не удалось: %1$s</string>
<string name="installer_activity_unable_to_find_handler">Не удалось найти обработчик для установки этого файла.</string>
<string name="pbw_install_handler_unable_to_install">Не удалось установить данный файл: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">Не удалось установить данную прошивку: она не совпадает с версией устройства вашего Pebble</string>
<string name="installer_activity_wait_while_determining_status">Пожалуйста подождите, идёт определение статуса установки...</string>
<string name="notif_battery_low_title">Низкий заряд устройства!</string>
<string name="notif_battery_low_percent">%1$s осталось заряда: %2$s%%</string>
<string name="notif_battery_low_bigtext_last_charge_time">Последний раз устройство заряжалось: %s \n</string>
<string name="notif_battery_low_bigtext_number_of_charges">Количество циклов перезарядки: %s</string>
<string name="sleepchart_your_sleep">Ваш сон</string>
<string name="weekstepschart_steps_a_week">Шагов в неделю</string>
<string name="activity_sleepchart_activity_and_sleep">Ваша активность и сон</string>
<string name="updating_firmware">Обновление прошивки...</string>
<string name="fwapp_install_device_not_ready">Файл не может быть установлен, устройство не готово.</string>
<string name="miband_installhandler_miband_firmware">Версия прошивки Mi Band: %1$s</string>
<string name="miband_fwinstaller_compatible_version">Совместимая версия</string>
<string name="miband_fwinstaller_untested_version">Не протестированная версия!</string>
<string name="fwappinstaller_connection_state">Подключение к устройству: %1$s</string>
<string name="pbw_installhandler_pebble_firmware">Версия прошивки Pebble: %1$s</string>
<string name="pbwinstallhandler_correct_hw_revision">Корректная ревизия устройства</string>
<string name="pbwinstallhandler_incorrect_hw_revision">Ревизия устройства не совпадает!</string>
<string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
<string name="updatefirmwareoperation_updateproblem_do_not_reboot">Проблема с передачей прошивки. НЕ ПЕРЕЗАГРУЖАЙТЕ ваш Mi Band!</string>
<string name="updatefirmwareoperation_metadata_updateproblem">Проблема с передачей метаданных прошивки</string>
<string name="updatefirmwareoperation_update_complete">Установка прошивки завершена</string>
<string name="updatefirmwareoperation_update_complete_rebooting">Установка прошивки завершена, устройство перезагружается...</string>
<string name="updatefirmwareoperation_write_failed">Запись прошивки завершилась неудачей</string>
<string name="chart_steps">Шаги</string>
<string name="liveactivity_live_activity">Жизненная активность</string>
<string name="weeksteps_today_steps_description">Шагов сегодня, цель: %1$s</string>
<string name="pref_title_dont_ack_transfer">Не передавать данные об активности</string>
<string name="pref_summary_dont_ack_transfers">Если данные об активности не будут переданы на устройство, оно не будет очищено. Полезно, если GB используется с другими приложениями.</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More