mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-09-03 04:56:27 +02:00
Merge branch 'master' into live-activity-data
This commit is contained in:
commit
4250a002b4
51
CHANGELOG.md
51
CHANGELOG.md
@ -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
|
||||
@ -29,7 +48,7 @@
|
||||
* 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
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
|
||||
public interface InstallActivity {
|
||||
void setInfoText(String text);
|
||||
|
||||
void setInstallEnabled(boolean enable);
|
||||
|
||||
void clearInstallItems();
|
||||
|
@ -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",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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> {
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
@ -9,6 +9,7 @@ public class GBDeviceEventNotificationControl extends GBDeviceEvent {
|
||||
UNKNOWN,
|
||||
DISMISS,
|
||||
DISMISS_ALL,
|
||||
OPEN
|
||||
OPEN,
|
||||
MUTE
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 createCalendar();
|
||||
}
|
||||
|
||||
/**
|
||||
* uses the standard algorithm to convert a Calendar object to a byte array to send to MiBand
|
||||
*
|
||||
* @param timestamp
|
||||
* @return
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -179,13 +179,14 @@ public class MiBandService {
|
||||
|
||||
*/
|
||||
|
||||
/* MODES: unknown
|
||||
|
||||
public static final MODE_REGULAR_DATA_LEN_BYTE = 0x0t;
|
||||
|
||||
public static final MODE_REGULAR_DATA_LEN_MINITE = 0x1t
|
||||
/* MODES: probably related to the sample data structure
|
||||
*/
|
||||
|
||||
public static final byte MODE_REGULAR_DATA_LEN_BYTE = 0x0;
|
||||
|
||||
// was MODE_REGULAR_DATA_LEN_MINITE
|
||||
public static final byte MODE_REGULAR_DATA_LEN_MINUTE = 0x1;
|
||||
|
||||
/* PROFILE: unknown
|
||||
|
||||
public static final PROFILE_STATE_UNKNOWN:I = 0x0
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 + "/";
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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,17 +36,22 @@ 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)) {
|
||||
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) {
|
||||
@ -51,9 +60,16 @@ public class NotificationListener extends NotificationListenerService {
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
// ACTION_MUTE
|
||||
LOG.info("going to mute " + sbn.getPackageName());
|
||||
GBApplication.addToBlacklist(sbn.getPackageName());
|
||||
}
|
||||
}
|
||||
} else if (action.equals(ACTION_DISMISS)) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_DISMISS: {
|
||||
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
|
||||
int handle = intent.getIntExtra("handle", -1);
|
||||
for (StatusBarNotification sbn : sbns) {
|
||||
@ -69,8 +85,11 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (action.equals(ACTION_DISMISS_ALL)) {
|
||||
break;
|
||||
}
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") || source.equals("com.android.mms")) {
|
||||
if (!"never".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
String title = extras.getCharSequence(Notification.EXTRA_TITLE).toString();
|
||||
String content = null;
|
||||
notificationSpec.title = extras.getCharSequence(Notification.EXTRA_TITLE).toString();
|
||||
if (extras.containsKey(Notification.EXTRA_TEXT)) {
|
||||
CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||
if (contentCS != null) {
|
||||
content = contentCS.toString();
|
||||
notificationSpec.body = contentCS.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
GBApplication.deviceService().onGenericNotification(title, content, (int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
|
||||
}
|
||||
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
|
||||
GBApplication.deviceService().onNotification(notificationSpec);
|
||||
}
|
||||
|
||||
private boolean isServiceRunning() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -2,6 +2,8 @@ package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
public interface ItemWithDetails {
|
||||
String getName();
|
||||
|
||||
String getDetails();
|
||||
|
||||
int getIcon();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
public enum NotificationType {
|
||||
|
||||
UNDEFINED,
|
||||
|
||||
CHAT,
|
||||
EMAIL,
|
||||
FACEBOOK,
|
||||
SMS,
|
||||
TWITTER,
|
||||
}
|
@ -2,7 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
public enum ServiceCommand {
|
||||
|
||||
UNDEFINEND,
|
||||
UNDEFINED,
|
||||
|
||||
CALL_ACCEPT,
|
||||
CALL_END,
|
||||
|
@ -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);
|
||||
|
@ -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: {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
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) {
|
||||
|
@ -22,6 +22,7 @@ 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 btAdapter the bluetooth adapter to use
|
||||
* @param context the android context, e.g. to look up resources
|
||||
@ -30,6 +31,7 @@ public interface DeviceSupport extends EventHandler {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ public class SetProgressAction extends PlainAction {
|
||||
|
||||
/**
|
||||
* When run, will update the progress notification.
|
||||
*
|
||||
* @param text
|
||||
* @param ongoing
|
||||
* @param percentage
|
||||
|
@ -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[]{
|
||||
|
@ -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,19 +409,22 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSMS(String from, String body) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmail(String from, String subject, String body) {
|
||||
break;
|
||||
case EMAIL:
|
||||
performPreferredNotification("email received", ORIGIN_K9MAIL, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenericNotification(String title, String details, int handle) {
|
||||
break;
|
||||
case CHAT:
|
||||
performPreferredNotification("chat message received", ORIGIN_PEBBLEMSG, null);
|
||||
break;
|
||||
default:
|
||||
performPreferredNotification("generic notification received", ORIGIN_GENERIC, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
private ActivityStruct activityStruct;
|
||||
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 = 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;
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ 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
|
||||
@ -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)) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -51,6 +51,7 @@ 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 maxLen the maximum number of bytes to read/return
|
||||
* @return the bytes read from the InputStream
|
||||
|
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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" />
|
||||
|
||||
|
16
app/src/main/res/layout/activity_appblacklist.xml
Normal file
16
app/src/main/res/layout/activity_appblacklist.xml
Normal 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>
|
@ -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"
|
||||
|
@ -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" />
|
||||
|
||||
|
50
app/src/main/res/layout/item_with_checkbox.xml
Normal file
50
app/src/main/res/layout/item_with_checkbox.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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. L’adresse 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 d’exé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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -11,6 +11,15 @@
|
||||
<item>never</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="notification_mode_toggle">
|
||||
<item>@string/always</item>
|
||||
<item>@string/never</item>
|
||||
</string-array>
|
||||
<string-array name="notification_mode_values_toggle">
|
||||
<item>always</item>
|
||||
<item>never</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="gender">
|
||||
<item>@string/male</item>
|
||||
<item>@string/female</item>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user