1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-26 03:46:49 +01:00

Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge into feature-weather

This commit is contained in:
danielegobbetti 2016-01-09 18:48:39 +01:00
commit 4bb78722b5
19 changed files with 487 additions and 87 deletions

View File

@ -29,7 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.IDSenderLookup; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
//import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver; //import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
@ -45,7 +45,7 @@ public class GBApplication extends Application {
private static final Lock dbLock = new ReentrantLock(); private static final Lock dbLock = new ReentrantLock();
private static DeviceService deviceService; private static DeviceService deviceService;
private static SharedPreferences sharedPrefs; private static SharedPreferences sharedPrefs;
private static IDSenderLookup mIDSenderLookup = new IDSenderLookup(); private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
public static final String ACTION_QUIT public static final String ACTION_QUIT
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit"; = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
@ -61,10 +61,7 @@ public class GBApplication extends Application {
} }
}; };
//private BluetoothConnectReceiver systemBTReceiver = new BluetoothConnectReceiver();
private void quit() { private void quit() {
//unregisterSystemBTReceiver();
GB.removeAllNotifications(this); GB.removeAllNotifications(this);
} }
@ -103,25 +100,11 @@ public class GBApplication extends Application {
filterLocal.addAction(ACTION_QUIT); filterLocal.addAction(ACTION_QUIT);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
//registerSystemBTReceiver();
// for testing DB stuff // for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase(); // SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();
// db.close(); // db.close();
} }
/*
private void registerSystemBTReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
filter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
registerReceiver(systemBTReceiver, filter);
}
private void unregisterSystemBTReceiver() {
unregisterReceiver(systemBTReceiver);
}
*/
private void setupExceptionHandler() { private void setupExceptionHandler() {
LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler()); LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(handler); Thread.setDefaultUncaughtExceptionHandler(handler);
@ -259,7 +242,7 @@ public class GBApplication extends Application {
return result; return result;
} }
public static IDSenderLookup getIDSenderLookup() { public static LimitedQueue getIDSenderLookup() {
return mIDSenderLookup; return mIDSenderLookup;
} }
} }

View File

@ -13,6 +13,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -38,6 +39,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DebugActivity extends Activity { public class DebugActivity extends Activity {
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class); private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
private static final String EXTRA_REPLY = "reply";
private static final String ACTION_REPLY
= "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply";
private Button sendSMSButton; private Button sendSMSButton;
private Button sendEmailButton; private Button sendEmailButton;
private Button incomingCallButton; private Button incomingCallButton;
@ -56,8 +61,16 @@ public class DebugActivity extends Activity {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(GBApplication.ACTION_QUIT)) { switch (intent.getAction()) {
finish(); case GBApplication.ACTION_QUIT:
finish();
break;
case ACTION_REPLY:
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
LOG.info("got wearable reply: " + reply);
GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO);
break;
} }
} }
}; };
@ -68,7 +81,10 @@ public class DebugActivity extends Activity {
setContentView(R.layout.activity_debug); setContentView(R.layout.activity_debug);
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
registerReceiver(mReceiver, new IntentFilter(GBApplication.ACTION_QUIT)); IntentFilter filter = new IntentFilter();
filter.addAction(GBApplication.ACTION_QUIT);
filter.addAction(ACTION_REPLY);
registerReceiver(mReceiver, filter);
editContent = (EditText) findViewById(R.id.editContent); editContent = (EditText) findViewById(R.id.editContent);
sendSMSButton = (Button) findViewById(R.id.sendSMSButton); sendSMSButton = (Button) findViewById(R.id.sendSMSButton);
@ -216,27 +232,42 @@ public class DebugActivity extends Activity {
} }
private void importDB() { private void importDB() {
DBHandler dbHandler = null; new AlertDialog.Builder(this)
try { .setCancelable(true)
dbHandler = GBApplication.acquireDB(); .setTitle("Import Activity Data?")
DBHelper helper = new DBHelper(this); .setMessage("Really overwrite the current activity database? All your activity data (if any) will be lost.")
File dir = FileUtils.getExternalFilesDir(); .setPositiveButton("Overwrite", new DialogInterface.OnClickListener() {
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper(); @Override
File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName()); public void onClick(DialogInterface dialog, int which) {
helper.importDB(sqLiteOpenHelper, sourceFile); DBHandler dbHandler = null;
helper.validateDB(sqLiteOpenHelper); try {
GB.toast(this, "Import successful.", Toast.LENGTH_LONG, GB.INFO); dbHandler = GBApplication.acquireDB();
} catch (Exception ex) { DBHelper helper = new DBHelper(DebugActivity.this);
GB.toast(this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); File dir = FileUtils.getExternalFilesDir();
} finally { SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
if (dbHandler != null) { File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName());
dbHandler.release(); helper.importDB(sqLiteOpenHelper, sourceFile);
} helper.validateDB(sqLiteOpenHelper);
} GB.toast(DebugActivity.this, "Import successful.", Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
GB.toast(DebugActivity.this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
if (dbHandler != null) {
dbHandler.release();
}
}
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
} }
private void deleteActivityDatabase() { private void deleteActivityDatabase() {
AlertDialog dialog = new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setCancelable(true) .setCancelable(true)
.setTitle("Delete Activity Data?") .setTitle("Delete Activity Data?")
.setMessage("Really delete the entire activity database? All your activity data will be lost.") .setMessage("Really delete the entire activity database? All your activity data will be lost.")
@ -266,13 +297,30 @@ public class DebugActivity extends Activity {
notificationIntent, 0); notificationIntent, 0);
NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this);
ncomp.setContentTitle(getString(R.string.test_notification)); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REPLY)
ncomp.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge)); .build();
ncomp.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge));
ncomp.setSmallIcon(R.drawable.ic_notification); Intent replyIntent = new Intent(ACTION_REPLY);
ncomp.setAutoCancel(true);
ncomp.setContentIntent(pendingIntent); PendingIntent replyPendingIntent = PendingIntent.getBroadcast(this, 0, replyIntent, 0);
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(android.R.drawable.ic_input_add, "Reply", replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action);
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.test_notification))
.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.extend(wearableExtender);
nManager.notify((int) System.currentTimeMillis(), ncomp.build()); nManager.notify((int) System.currentTimeMillis(), ncomp.build());
} }

View File

@ -30,7 +30,9 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supports(GBDeviceCandidate candidate) { public boolean supports(GBDeviceCandidate candidate) {
return candidate.getMacAddress().toUpperCase().startsWith(MiBandService.MAC_ADDRESS_FILTER); String macAddress = candidate.getMacAddress().toUpperCase();
return macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1_1A)
|| macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1S);
} }
@Override @Override
@ -74,7 +76,7 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
} }
public static boolean hasValidUserInfo() { public static boolean hasValidUserInfo() {
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER + ":00:00:00"; String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
try { try {
UserInfo userInfo = getConfiguredUserInfo(dummyMacAddress); UserInfo userInfo = getConfiguredUserInfo(dummyMacAddress);
return true; return true;

View File

@ -1,15 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband; package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.app.Activity; import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -29,9 +32,12 @@ public class MiBandPairingActivity extends Activity {
private static final int REQ_CODE_USER_SETTINGS = 52; private static final int REQ_CODE_USER_SETTINGS = 52;
private static final String STATE_MIBAND_ADDRESS = "mibandMacAddress"; private static final String STATE_MIBAND_ADDRESS = "mibandMacAddress";
private static final long DELAY_AFTER_BONDING = 1000; // 1s
private TextView message; private TextView message;
private boolean isPairing; private boolean isPairing;
private String macAddress; private String macAddress;
private String bondingMacAddress;
private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() { private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -45,6 +51,29 @@ public class MiBandPairingActivity extends Activity {
} }
}; };
private final BroadcastReceiver mBondingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (bondingMacAddress != null && bondingMacAddress.equals(device.getAddress())) {
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
if (bondState == BluetoothDevice.BOND_BONDED) {
LOG.info("Bonded with " + device.getAddress());
bondingMacAddress = null;
Looper mainLooper = Looper.getMainLooper();
new Handler(mainLooper).postDelayed(new Runnable() {
@Override
public void run() {
performPair();
}
}, DELAY_AFTER_BONDING);
}
}
}
}
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -99,7 +128,13 @@ public class MiBandPairingActivity extends Activity {
@Override @Override
protected void onDestroy() { protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver); try {
// just to be sure, remove the receivers -- might actually be already unregistered
LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver);
unregisterReceiver(mBondingReceiver);
} catch (IllegalArgumentException ex) {
// already unregistered, ignore
}
if (isPairing) { if (isPairing) {
stopPairing(); stopPairing();
} }
@ -109,15 +144,24 @@ public class MiBandPairingActivity extends Activity {
private void startPairing() { private void startPairing() {
isPairing = true; isPairing = true;
message.setText(getString(R.string.miband_pairing, macAddress)); message.setText(getString(R.string.miband_pairing, macAddress));
IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED); IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter); LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter);
filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mBondingReceiver, filter);
GBApplication.deviceService().connect(macAddress, true); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
if (device != null) {
performBluetoothPair(device);
} else {
GB.toast(this, "No such Bluetooth Device: " + macAddress, Toast.LENGTH_LONG, GB.ERROR);
}
} }
private void pairingFinished(boolean pairedSuccessfully) { private void pairingFinished(boolean pairedSuccessfully) {
isPairing = false; isPairing = false;
LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver);
unregisterReceiver(mBondingReceiver);
if (pairedSuccessfully) { if (pairedSuccessfully) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@ -133,4 +177,27 @@ public class MiBandPairingActivity extends Activity {
// TODO // TODO
isPairing = false; isPairing = false;
} }
protected void performBluetoothPair(BluetoothDevice device) {
int bondState = device.getBondState();
if (bondState == BluetoothDevice.BOND_BONDED) {
LOG.info("Already bonded: " + device.getAddress());
performPair();
return;
}
bondingMacAddress = device.getAddress();
if (bondState == BluetoothDevice.BOND_BONDING) {
LOG.info("Bonding in progress: " + device.getAddress());
return;
}
if (!device.createBond()) {
GB.toast(this, "Unable to pair with " + device.getAddress(), Toast.LENGTH_LONG, GB.ERROR);
}
}
private void performPair() {
GBApplication.deviceService().connect(macAddress, true);
}
} }

View File

@ -9,7 +9,8 @@ import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDevi
public class MiBandService { public class MiBandService {
public static final String MAC_ADDRESS_FILTER = "88:0F:10"; public static final String MAC_ADDRESS_FILTER_1_1A = "88:0F:10";
public static final String MAC_ADDRESS_FILTER_1S = "C8:0F:10";
public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0")); public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));

View File

@ -16,15 +16,20 @@ import android.os.PowerManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
public class NotificationListener extends NotificationListenerService { public class NotificationListener extends NotificationListenerService {
@ -38,6 +43,10 @@ public class NotificationListener extends NotificationListenerService {
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open"; = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open";
public static final String ACTION_MUTE public static final String ACTION_MUTE
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute"; = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute";
public static final String ACTION_REPLY
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.reply";
private LimitedQueue mActionLookup = new LimitedQueue(16);
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@SuppressLint("NewApi") @SuppressLint("NewApi")
@ -90,6 +99,28 @@ public class NotificationListener extends NotificationListenerService {
case ACTION_DISMISS_ALL: case ACTION_DISMISS_ALL:
NotificationListener.this.cancelAllNotifications(); NotificationListener.this.cancelAllNotifications();
break; break;
case ACTION_REPLY:
int id = intent.getIntExtra("handle", -1);
String reply = intent.getStringExtra("reply");
NotificationCompat.Action replyAction = (NotificationCompat.Action) mActionLookup.lookup(id);
if (replyAction != null && replyAction.getRemoteInputs() != null) {
RemoteInput[] remoteInputs = replyAction.getRemoteInputs();
PendingIntent actionIntent = replyAction.getActionIntent();
Intent localIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle extras = new Bundle();
extras.putCharSequence(remoteInputs[0].getResultKey(), reply);
RemoteInput.addResultsToIntent(remoteInputs, localIntent, extras);
try {
LOG.info("will send reply intent to remote application");
actionIntent.send(context, 0, localIntent);
mActionLookup.remove(id);
} catch (PendingIntent.CanceledException e) {
LOG.warn("replyToLastNotification error: " + e.getLocalizedMessage());
}
}
break;
} }
} }
@ -103,6 +134,7 @@ public class NotificationListener extends NotificationListenerService {
filterLocal.addAction(ACTION_DISMISS); filterLocal.addAction(ACTION_DISMISS);
filterLocal.addAction(ACTION_DISMISS_ALL); filterLocal.addAction(ACTION_DISMISS_ALL);
filterLocal.addAction(ACTION_MUTE); filterLocal.addAction(ACTION_MUTE);
filterLocal.addAction(ACTION_REPLY);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
} }
@ -222,6 +254,18 @@ public class NotificationListener extends NotificationListenerService {
dissectNotificationTo(notification, notificationSpec); dissectNotificationTo(notification, notificationSpec);
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification);
List<NotificationCompat.Action> actions = wearableExtender.getActions();
for (NotificationCompat.Action act : actions) {
if (act != null && act.getRemoteInputs() != null) {
LOG.info("found wearable action: " + act.getTitle() + " " + sbn.getTag());
mActionLookup.add(notificationSpec.id, act);
notificationSpec.flags |= NotificationSpec.FLAG_WEARABLE_REPLY;
break;
}
}
GBApplication.deviceService().onNotification(notificationSpec); GBApplication.deviceService().onNotification(notificationSpec);
} }

View File

@ -89,6 +89,7 @@ public class GBDeviceService implements DeviceService {
@Override @Override
public void onNotification(NotificationSpec notificationSpec) { public void onNotification(NotificationSpec notificationSpec) {
Intent intent = createIntent().setAction(ACTION_NOTIFICATION) Intent intent = createIntent().setAction(ACTION_NOTIFICATION)
.putExtra(EXTRA_NOTIFICATION_FLAGS, notificationSpec.flags)
.putExtra(EXTRA_NOTIFICATION_PHONENUMBER, notificationSpec.phoneNumber) .putExtra(EXTRA_NOTIFICATION_PHONENUMBER, notificationSpec.phoneNumber)
.putExtra(EXTRA_NOTIFICATION_SENDER, notificationSpec.sender) .putExtra(EXTRA_NOTIFICATION_SENDER, notificationSpec.sender)
.putExtra(EXTRA_NOTIFICATION_SUBJECT, notificationSpec.subject) .putExtra(EXTRA_NOTIFICATION_SUBJECT, notificationSpec.subject)

View File

@ -35,6 +35,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body"; String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
String EXTRA_NOTIFICATION_ID = "notification_id"; String EXTRA_NOTIFICATION_ID = "notification_id";
String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber"; String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber";
String EXTRA_NOTIFICATION_SENDER = "notification_sender"; String EXTRA_NOTIFICATION_SENDER = "notification_sender";

View File

@ -1,6 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
public class NotificationSpec { public class NotificationSpec {
public static final int FLAG_WEARABLE_REPLY = 0x00000001;
public int flags;
public int id; public int id;
public String sender; public String sender;
public String phoneNumber; public String phoneNumber;

View File

@ -229,16 +229,22 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
action = NotificationListener.ACTION_MUTE; action = NotificationListener.ACTION_MUTE;
break; break;
case REPLY: case REPLY:
String phoneNumber = GBApplication.getIDSenderLookup().lookup(deviceEvent.handle); String phoneNumber = (String) GBApplication.getIDSenderLookup().lookup(deviceEvent.handle);
if (phoneNumber != null) { if (phoneNumber != null) {
LOG.info("got notfication reply for " + phoneNumber + " : " + deviceEvent.reply); LOG.info("got notfication reply for SMS from " + phoneNumber + " : " + deviceEvent.reply);
SmsManager.getDefault().sendTextMessage(phoneNumber, null, deviceEvent.reply, null, null); SmsManager.getDefault().sendTextMessage(phoneNumber, null, deviceEvent.reply, null, null);
} else {
LOG.info("got notfication reply for notification id " + deviceEvent.handle + " : " + deviceEvent.reply);
action = NotificationListener.ACTION_REPLY;
} }
break; break;
} }
if (action != null) { if (action != null) {
Intent notificationListenerIntent = new Intent(action); Intent notificationListenerIntent = new Intent(action);
notificationListenerIntent.putExtra("handle", deviceEvent.handle); notificationListenerIntent.putExtra("handle", deviceEvent.handle);
if (deviceEvent.reply != null) {
notificationListenerIntent.putExtra("reply", deviceEvent.reply);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(notificationListenerIntent); LocalBroadcastManager.getInstance(context).sendBroadcast(notificationListenerIntent);
} }
} }

View File

@ -39,7 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.IDSenderLookup;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
@ -71,6 +70,7 @@ 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_ARTIST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK; 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_BODY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_FLAGS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID; 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_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER;
@ -219,13 +219,16 @@ public class DeviceCommunicationService extends Service {
notificationSpec.body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY); notificationSpec.body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE); notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE);
notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
notificationSpec.flags = intent.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0);
notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME);
if (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null) { if (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null) {
notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber); notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber);
notificationSpec.id = mRandom.nextInt(); // FIXME: add this in external SMS Receiver? notificationSpec.id = mRandom.nextInt(); // FIXME: add this in external SMS Receiver?
GBApplication.getIDSenderLookup().add(notificationSpec.id, notificationSpec.phoneNumber); GBApplication.getIDSenderLookup().add(notificationSpec.id, notificationSpec.phoneNumber);
}
if (((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) > 0)
|| (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null)) {
// NOTE: maybe not where it belongs // NOTE: maybe not where it belongs
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPrefs.getBoolean("pebble_force_untested", false)) { if (sharedPrefs.getBoolean("pebble_force_untested", false)) {

View File

@ -9,6 +9,7 @@ import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
/** /**
* Abstract base class for a BTLEOperation, i.e. an operation that does more than * Abstract base class for a BTLEOperation, i.e. an operation that does more than
@ -25,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
*/ */
public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport> implements GattCallback, BTLEOperation { public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport> implements GattCallback, BTLEOperation {
private final T mSupport; private final T mSupport;
protected OperationStatus operationStatus = OperationStatus.INITIAL;
protected AbstractBTLEOperation(T support) { protected AbstractBTLEOperation(T support) {
mSupport = support; mSupport = support;
@ -38,7 +40,9 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
*/ */
@Override @Override
public final void perform() throws IOException { public final void perform() throws IOException {
operationStatus = OperationStatus.STARTED;
prePerform(); prePerform();
operationStatus = OperationStatus.RUNNING;
doPerform(); doPerform();
} }
@ -101,6 +105,10 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
getDevice().sendDeviceUpdateIntent(getContext()); getDevice().sendDeviceUpdateIntent(getContext());
} }
public boolean isOperationRunning() {
return operationStatus == OperationStatus.RUNNING;
}
public T getSupport() { public T getSupport() {
return mSupport; return mSupport;
} }

View File

@ -25,6 +25,7 @@ public abstract class AbstractMiBandOperation extends AbstractBTLEOperation<MiBa
@Override @Override
protected void operationFinished() { protected void operationFinished() {
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null && getDevice().isConnected()) { if (getDevice() != null && getDevice().isConnected()) {
try { try {
TransactionBuilder builder = performInitialized("reenabling disabled notifications"); TransactionBuilder builder = performInitialized("reenabling disabled notifications");

View File

@ -174,6 +174,12 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
* @see #bufferActivityData(byte[]) * @see #bufferActivityData(byte[])
*/ */
private void handleActivityNotif(byte[] value) { private void handleActivityNotif(byte[] value) {
if (!isOperationRunning()) {
LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
getSupport().logMessageContent(value);
return;
}
if (value.length == activityMetadataLength) { if (value.length == activityMetadataLength) {
handleActivityMetadata(value); handleActivityMetadata(value);
} else { } else {

View File

@ -0,0 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
public enum OperationStatus {
INITIAL,
STARTED,
RUNNING,
FINISHED
}

View File

@ -1590,14 +1590,14 @@ public class PebbleProtocol extends GBDeviceProtocol {
byte[] reply = new byte[length]; byte[] reply = new byte[length];
buf.get(reply); buf.get(reply);
// FIXME: this does not belong here, but we want at least check if there is no chance at all to send out the SMS later before we report success // FIXME: this does not belong here, but we want at least check if there is no chance at all to send out the SMS later before we report success
String phoneNumber = GBApplication.getIDSenderLookup().lookup(id); String phoneNumber = (String) GBApplication.getIDSenderLookup().lookup(id);
if (phoneNumber != null) { //if (phoneNumber != null) {
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY; devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
devEvtNotificationControl.reply = new String(reply); devEvtNotificationControl.reply = new String(reply);
caption = "SENT"; caption = "SENT";
icon_id = PebbleIconID.RESULT_SENT; icon_id = PebbleIconID.RESULT_SENT;
failed = false; failed = false;
} //}
} }
} }
if (failed) { if (failed) {

View File

@ -1,26 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.util;
import android.util.Pair;
import java.util.LinkedList;
public class IDSenderLookup {
private static final int LIMIT = 16;
private LinkedList<Pair> list = new LinkedList<>();
public void add(int id, String sender) {
if (list.size() > LIMIT - 1) {
list.removeFirst();
}
list.add(new Pair<>(id, sender));
}
public String lookup(int id) {
for (Pair entry : list) {
if (id == (Integer) entry.first) {
return (String) entry.second;
}
}
return null;
}
}

View File

@ -0,0 +1,40 @@
package nodomain.freeyourgadget.gadgetbridge.util;
import android.util.Pair;
import java.util.Iterator;
import java.util.LinkedList;
public class LimitedQueue {
private final int limit;
private LinkedList<Pair> list = new LinkedList<>();
public LimitedQueue(int limit) {
this.limit = limit;
}
public void add(int id, Object obj) {
if (list.size() > limit - 1) {
list.removeFirst();
}
list.add(new Pair<>(id, obj));
}
public void remove(int id) {
for (Iterator<Pair> iter = list.iterator(); iter.hasNext(); ) {
Pair pair = iter.next();
if (pair.first == id) {
iter.remove();
}
}
}
public Object lookup(int id) {
for (Pair entry : list) {
if (id == (Integer) entry.first) {
return entry.second;
}
}
return null;
}
}

View File

@ -0,0 +1,204 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">ガジェットブリッジ</string>
<string name="title_activity_controlcenter">ガジェットブリッジ</string>
<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">アプリ管理画面</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">お使いのMi Bandに、現在のファームウェアの代わりに%sをインストールしようとしています。</string>
<string name="miband_firmware_known">このファームウェアはテスト済で、ガジェットブリッジと互換性があることがわかっています。</string>
<string name="miband_firmware_unknown_warning">このファームウェアは未テストで、ガジェットブリッジと互換性がない可能性があります。\n\nお使いのMi Bandにフラッシュすることは奨励されていません!</string>
<string name="miband_firmware_suggest_whitelist">それでも続行して、その後正しく作業を続ける場合は、ガジェットブリッジ開発者にホワイトリスト ファームウェア バージョンを教えてください: %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">接続したとき、Androidで時間またはタイムゾーンを変更したときに、デバイスに時間を同期</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">SMS</string>
<string name="pref_title_notifications_k9mail">K9メール</string>
<string name="pref_title_notifications_pebblemsg">Pebbleメッセージ</string>
<string name="pref_summary_notifications_pebblemsg">インテント経由でPebbleに通知を送信するアプリケーションをサポートします。Conversationsに使用することができます。</string>
<string name="pref_title_notifications_generic">一般ステータス通知対応</string>
<string name="pref_title_whenscreenon">… スクリーンがオンのときにも</string>
<string name="always">いつも</string>
<string name="when_screen_off">スクリーンがオフのとき</string>
<string name="never">なし</string>
<string name="pref_blacklist">アップのブラックリスト</string>
<string name="pref_title_canned_replies">定型の返信</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">PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします</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>
<string name="unknown_state">不明な状態</string>
<string name="connectionstate_hw_fw">HW: %1$s FW: %2$s</string>
<string name="connectionstate_fw">FW: %1$s</string>
<string name="_unknown_">(不明)</string>
<string name="test">テスト</string>
<string name="test_notification">通知のテスト</string>
<string name="this_is_a_test_notification_from_gadgetbridge">これはガジェットブリッジからのテスト通知です</string>
<string name="bluetooth_is_not_supported_">Bluetoothはサポートされていません。</string>
<string name="bluetooth_is_disabled_">Bluetoothは無効です。</string>
<string name="tap_connected_device_for_app_mananger">アプリマネージャーの接続されたデバイスをタップ</string>
<string name="tap_a_device_to_connect">接続するデバイスをタップ</string>
<string name="cannot_connect_bt_address_invalid_">接続できません。 BTアドレスが無効?</string>
<string name="gadgetbridge_running">ガジェットブリッジは実行中</string>
<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 このファームウェアは次のHWリビジョン用: %s</string>
<string name="app_install_info">次のアプリをインストールしようとしています:\n\n\n%1$s バージョン %2$s 作成 %3$s\n</string>
<string name="n_a">N/A</string>
<string name="initialized">初期化しました</string>
<string name="appversion_by_creator">%1$s 作成 %2$s</string>
<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のBluetoothペアリングのダイアログを使用します。</string>
<string name="title_activity_mi_band_pairing">お使いのMi Bandをペアリング</string>
<string name="miband_pairing">%sとペアリング…</string>
<string name="message_cannot_pair_no_mac">MACアドレスが渡されませんでした。ペアリングできません。</string>
<string name="preferences_category_device_specific_settings">デバイス固有の設定</string>
<string name="preferences_miband_settings">Mi Bandの設定</string>
<string name="male">男性</string>
<string name="female">女性</string>
<string name="other">その他</string>
<string name="left"></string>
<string name="right"></string>
<string name="miband_pairing_using_dummy_userdata">有効なユーザーデータはありません。今のところ、ダミーのユーザーデータを使用します。</string>
<string name="miband_pairing_tap_hint">お使いのMi Bandが振動と点滅したとき、それを連続して数回タップしてください。</string>
<string name="appinstaller_install">インストール</string>
<string name="discovery_connected_devices_hint">お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。</string>
<string name="discovery_note">注:</string>
<string name="candidate_item_device_image">デバイスイメージ</string>
<string name="miband_prefs_about_you">あなたについて</string>
<string name="miband_prefs_alias">名前/別名</string>
<string name="miband_prefs_year_birth">誕生年</string>
<string name="miband_prefs_gender">性別</string>
<string name="miband_prefs_height_cm">身長cm</string>
<string name="miband_prefs_weight_kg">体重kg</string>
<string name="pref_header_vibration_count">バイブレーション回数</string>
<string name="title_activity_sleepmonitor">睡眠観測</string>
<string name="pref_write_logfiles">ログファイルを出力 (再起動が必要)</string>
<string name="initializing">初期化中</string>
<string name="busy_task_fetch_activity_data">活動データを取得中</string>
<string name="sleep_activity_date_range">%1$sから%2$sまで</string>
<string name="miband_prefs_wearside">左または右に身に着けますか?</string>
<string name="pref_screen_vibration_profile">バイブレーション プロファイル</string>
<string name="vibration_profile_staccato">スタッカート</string>
<string name="vibration_profile_short">短い</string>
<string name="vibration_profile_medium">標準</string>
<string name="vibration_profile_long">長い</string>
<string name="vibration_profile_waterdrop">水滴</string>
<string name="vibration_profile_ring">鳴り響く</string>
<string name="vibration_profile_alarm_clock">目覚まし時計</string>
<string name="miband_prefs_vibration">バイブレーション</string>
<string name="pref_screen_notification_profile_sms">SMS通知</string>
<string name="pref_header_vibration_settings">バイブレーションの設定</string>
<string name="pref_screen_notification_profile_generic">全般通知</string>
<string name="pref_screen_notification_profile_pebblemsg">Pebble通知</string>
<string name="pref_screen_notification_profile_k9mail">K9 Mail 通知</string>
<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>
<string name="alarm_sun_short"></string>
<string name="alarm_mon_short"></string>
<string name="alarm_tue_short"></string>
<string name="alarm_wed_short"></string>
<string name="alarm_thu_short"></string>
<string name="alarm_fri_short"></string>
<string name="alarm_sat_short"></string>
<string name="alarm_smart_wakeup">スマート ウェイクアップ</string>
<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_summary_dont_ack_transfers">活動データは、バンドが応答しない場合は、クリアされません。 GBを他のアプリと一緒に使用する場合に便利です。</string>
<string name="pref_summary_keep_data_on_device">同期後もMi Bandに活動データを保持します。 GBを他のアプリと一緒に使用する場合に便利です。</string>
<string name="pref_title_dont_ack_transfer">活動データ転送に応答しない</string>
<string name="live_activity_steps_history">歩数履歴</string>
<string name="live_activity_current_steps_per_minute">現在の歩数/分</string>
<string name="live_activity_total_steps">合計歩数</string>
<string name="live_activity_steps_per_minute_history">毎分の歩数履歴</string>
<string name="live_activity_start_your_activity">あなたの活動を開始</string>
<string name="abstract_chart_fragment_kind_activity">活動</string>
<string name="abstract_chart_fragment_kind_light_sleep">浅い睡眠</string>
<string name="abstract_chart_fragment_kind_deep_sleep">深い睡眠</string>
<string name="abstract_chart_fragment_kind_not_worn">非装着</string>
<string name="device_not_connected">接続されていません。</string>
<string name="user_feedback_all_alarms_disabled">すべてのアラームを無効にしました</string>
<string name="pref_title_keep_data_on_device">活動データをデバイスに保持</string>
<string name="miband_fwinstaller_incompatible_version">非互換性のファームウェア</string>
<string name="fwinstaller_firmware_not_compatible_to_device">このファームウェアは、デバイスと互換性がありません</string>
<string name="miband_prefs_reserve_alarm_calendar">今後のイベントのために予約するアラーム</string>
<string name="waiting_for_reconnect">再接続の待機中</string>
<string name="appmananger_app_reinstall">再インストール</string>
</resources>