diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 3c854781a..05c1ac7a9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -29,7 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.util.IDSenderLookup; +import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; //import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver; @@ -45,7 +45,7 @@ public class GBApplication extends Application { private static final Lock dbLock = new ReentrantLock(); private static DeviceService deviceService; private static SharedPreferences sharedPrefs; - private static IDSenderLookup mIDSenderLookup = new IDSenderLookup(); + private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); public static final String 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() { - //unregisterSystemBTReceiver(); GB.removeAllNotifications(this); } @@ -103,25 +100,11 @@ public class GBApplication extends Application { filterLocal.addAction(ACTION_QUIT); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); - //registerSystemBTReceiver(); // for testing DB stuff // SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase(); // 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() { LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler()); Thread.setDefaultUncaughtExceptionHandler(handler); @@ -259,7 +242,7 @@ public class GBApplication extends Application { return result; } - public static IDSenderLookup getIDSenderLookup() { + public static LimitedQueue getIDSenderLookup() { return mIDSenderLookup; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index 1e7cdc00d..a4fbe772c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -13,6 +13,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.os.Bundle; import android.support.v4.app.NavUtils; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.RemoteInput; import android.view.MenuItem; import android.view.View; import android.widget.Button; @@ -38,6 +39,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class DebugActivity extends Activity { 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 sendEmailButton; private Button incomingCallButton; @@ -56,8 +61,16 @@ public class DebugActivity extends Activity { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(GBApplication.ACTION_QUIT)) { - finish(); + switch (intent.getAction()) { + 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); 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); sendSMSButton = (Button) findViewById(R.id.sendSMSButton); @@ -216,27 +232,42 @@ public class DebugActivity extends Activity { } private void importDB() { - DBHandler dbHandler = null; - try { - dbHandler = GBApplication.acquireDB(); - DBHelper helper = new DBHelper(this); - File dir = FileUtils.getExternalFilesDir(); - SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper(); - File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName()); - helper.importDB(sqLiteOpenHelper, sourceFile); - helper.validateDB(sqLiteOpenHelper); - GB.toast(this, "Import successful.", Toast.LENGTH_LONG, GB.INFO); - } catch (Exception ex) { - GB.toast(this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); - } finally { - if (dbHandler != null) { - dbHandler.release(); - } - } + new AlertDialog.Builder(this) + .setCancelable(true) + .setTitle("Import Activity Data?") + .setMessage("Really overwrite the current activity database? All your activity data (if any) will be lost.") + .setPositiveButton("Overwrite", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + DBHandler dbHandler = null; + try { + dbHandler = GBApplication.acquireDB(); + DBHelper helper = new DBHelper(DebugActivity.this); + File dir = FileUtils.getExternalFilesDir(); + SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper(); + File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName()); + 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() { - AlertDialog dialog = new AlertDialog.Builder(this) + new AlertDialog.Builder(this) .setCancelable(true) .setTitle("Delete Activity Data?") .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); NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this); - ncomp.setContentTitle(getString(R.string.test_notification)); - ncomp.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge)); - ncomp.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge)); - ncomp.setSmallIcon(R.drawable.ic_notification); - ncomp.setAutoCancel(true); - ncomp.setContentIntent(pendingIntent); + + RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REPLY) + .build(); + + Intent replyIntent = new Intent(ACTION_REPLY); + + 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()); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java index 2a13e60ff..7b3af1b8f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java @@ -30,7 +30,9 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator { @Override 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 @@ -74,7 +76,7 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator { } 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 { UserInfo userInfo = getConfiguredUserInfo(dummyMacAddress); return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java index e5109e51b..c33a1909e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java @@ -1,15 +1,18 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband; import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; 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.os.Handler; +import android.os.Looper; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; -import android.util.Log; import android.widget.TextView; 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 String STATE_MIBAND_ADDRESS = "mibandMacAddress"; + private static final long DELAY_AFTER_BONDING = 1000; // 1s private TextView message; private boolean isPairing; private String macAddress; + private String bondingMacAddress; + private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() { @Override 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 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -99,7 +128,13 @@ public class MiBandPairingActivity extends Activity { @Override 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) { stopPairing(); } @@ -109,15 +144,24 @@ public class MiBandPairingActivity extends Activity { private void startPairing() { isPairing = true; message.setText(getString(R.string.miband_pairing, macAddress)); + IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED); 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) { isPairing = false; LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver); + unregisterReceiver(mBondingReceiver); if (pairedSuccessfully) { SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -133,4 +177,27 @@ public class MiBandPairingActivity extends Activity { // TODO 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); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java index d3e33cc4b..349c0f481 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java @@ -9,7 +9,8 @@ import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDevi 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")); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 5fc8c9629..81eaaf807 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -16,15 +16,20 @@ import android.os.PowerManager; import android.preference.PreferenceManager; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.RemoteInput; import android.support.v4.content.LocalBroadcastManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; public class NotificationListener extends NotificationListenerService { @@ -38,6 +43,10 @@ public class NotificationListener extends NotificationListenerService { = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open"; public static final String 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() { @SuppressLint("NewApi") @@ -90,6 +99,28 @@ public class NotificationListener extends NotificationListenerService { case ACTION_DISMISS_ALL: NotificationListener.this.cancelAllNotifications(); 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_ALL); filterLocal.addAction(ACTION_MUTE); + filterLocal.addAction(ACTION_REPLY); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); } @@ -222,6 +254,18 @@ public class NotificationListener extends NotificationListenerService { dissectNotificationTo(notification, notificationSpec); notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better + + NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification); + List 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); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index b1f745675..cee7a5e2e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -89,6 +89,7 @@ public class GBDeviceService implements DeviceService { @Override public void onNotification(NotificationSpec notificationSpec) { Intent intent = createIntent().setAction(ACTION_NOTIFICATION) + .putExtra(EXTRA_NOTIFICATION_FLAGS, notificationSpec.flags) .putExtra(EXTRA_NOTIFICATION_PHONENUMBER, notificationSpec.phoneNumber) .putExtra(EXTRA_NOTIFICATION_SENDER, notificationSpec.sender) .putExtra(EXTRA_NOTIFICATION_SUBJECT, notificationSpec.subject) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 342c66d22..af4a6d1b8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -35,6 +35,7 @@ public interface DeviceService extends EventHandler { String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_NOTIFICATION_BODY = "notification_body"; + String EXTRA_NOTIFICATION_FLAGS = "notification_flags"; String EXTRA_NOTIFICATION_ID = "notification_id"; String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber"; String EXTRA_NOTIFICATION_SENDER = "notification_sender"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java index fde544d50..e140ba47c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java @@ -1,6 +1,9 @@ package nodomain.freeyourgadget.gadgetbridge.model; public class NotificationSpec { + public static final int FLAG_WEARABLE_REPLY = 0x00000001; + + public int flags; public int id; public String sender; public String phoneNumber; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index f9d3b8144..56c9cd811 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -229,16 +229,22 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { action = NotificationListener.ACTION_MUTE; break; case REPLY: - String phoneNumber = GBApplication.getIDSenderLookup().lookup(deviceEvent.handle); + String phoneNumber = (String) GBApplication.getIDSenderLookup().lookup(deviceEvent.handle); 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); + } else { + LOG.info("got notfication reply for notification id " + deviceEvent.handle + " : " + deviceEvent.reply); + action = NotificationListener.ACTION_REPLY; } break; } if (action != null) { Intent notificationListenerIntent = new Intent(action); notificationListenerIntent.putExtra("handle", deviceEvent.handle); + if (deviceEvent.reply != null) { + notificationListenerIntent.putExtra("reply", deviceEvent.reply); + } LocalBroadcastManager.getInstance(context).sendBroadcast(notificationListenerIntent); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index f84014e9b..d85c12909 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -39,7 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; 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_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_TRACK; 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_PHONENUMBER; 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.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE); notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + notificationSpec.flags = intent.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0); notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); if (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null) { notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber); notificationSpec.id = mRandom.nextInt(); // FIXME: add this in external SMS Receiver? 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 SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (sharedPrefs.getBoolean("pebble_force_untested", false)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java index 746342bf9..3c2387c01 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.UUID; 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 @@ -25,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; */ public abstract class AbstractBTLEOperation implements GattCallback, BTLEOperation { private final T mSupport; + protected OperationStatus operationStatus = OperationStatus.INITIAL; protected AbstractBTLEOperation(T support) { mSupport = support; @@ -38,7 +40,9 @@ public abstract class AbstractBTLEOperation */ @Override public final void perform() throws IOException { + operationStatus = OperationStatus.STARTED; prePerform(); + operationStatus = OperationStatus.RUNNING; doPerform(); } @@ -101,6 +105,10 @@ public abstract class AbstractBTLEOperation getDevice().sendDeviceUpdateIntent(getContext()); } + public boolean isOperationRunning() { + return operationStatus == OperationStatus.RUNNING; + } + public T getSupport() { return mSupport; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java index f346852fb..ffabce535 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java @@ -25,6 +25,7 @@ public abstract class AbstractMiBandOperation extends AbstractBTLEOperation 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; - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java new file mode 100644 index 000000000..42bc1566a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java @@ -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 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 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; + } +} diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 000000000..f652abc18 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,204 @@ + + + ガジェットブリッジ + ガジェットブリッジ + 設定 + デバッグ + 終了 + 同期 + 睡眠観測(アルファ) + デバイスをバイブさせる + スクリーンショットを撮る + 切断 + デバッグ + + アプリ管理画面 + 削除 + + ステータス通知ブラックリスト + + ファームウェア・アプリのインストール + お使いのMi Bandに、現在のファームウェアの代わりに%sをインストールしようとしています。 + このファームウェアはテスト済で、ガジェットブリッジと互換性があることがわかっています。 + このファームウェアは未テストで、ガジェットブリッジと互換性がない可能性があります。\n\nお使いのMi Bandにフラッシュすることは奨励されていません! + それでも続行して、その後正しく作業を続ける場合は、ガジェットブリッジ開発者にホワイトリスト ファームウェア バージョンを教えてください: %s + + 設定 + 一般設定 + Bluetoothがオンになったときにデバイスに接続 + お好みのオーディオプレイヤー + デフォルト + 日付と時刻 + 時間を合わせる + 接続したとき、Androidで時間またはタイムゾーンを変更したときに、デバイスに時間を同期 + 通知 + 繰り返し + 電話通知 + SMS + K9メール + Pebbleメッセージ + インテント経由でPebbleに通知を送信するアプリケーションをサポートします。Conversationsに使用することができます。 + 一般ステータス通知対応 + … スクリーンがオンのときにも + いつも + スクリーンがオフのとき + なし + アップのブラックリスト + 定型の返信 + 開発者用設定 + Mi Bandのアドレス + Pebbleの設定 + 第三者のアンドロイドアップにアクセス権利を与える + PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします + 通知プロトコルを強制する + このオプションを指定すると、ファームウェアのバージョンに応じて強制的に最新の通知プロトコルを使用します。何をしているかわかっている場合のみ有効にしてください! + 未テストの機能を有効にする + テストされていない機能を有効にします。何をしているかわかっている場合のみ有効にしてください! + 再接続の試行 + 非接続 + 接続中 + 接続 + 不明な状態 + HW: %1$s FW: %2$s + FW: %1$s + (不明) + テスト + 通知のテスト + これはガジェットブリッジからのテスト通知です + Bluetoothはサポートされていません。 + Bluetoothは無効です。 + アプリマネージャーの接続されたデバイスをタップ + 接続するデバイスをタップ + 接続できません。 BTアドレスが無効? + ガジェットブリッジは実行中 + バイナリーのインストール中 %1$d/%2$d + インストールに失敗しました! + インストールに成功しました + ファームウェアをインストールしようとしています。ご自身の責任で行って下さい。\n\n\n このファームウェアは次のHWリビジョン用: %s + 次のアプリをインストールしようとしています:\n\n\n%1$s バージョン %2$s 作成 %3$s\n + N/A + 初期化しました + %1$s 作成 %2$s + デバイス探索 + スキャンを停止 + 探索を開始 + 新しいデバイスに接続 + %1$s (%2$s) + デバイスのペアリング + デバイスをペアリングするには、AndroidのBluetoothペアリングのダイアログを使用します。 + お使いのMi Bandをペアリング + %sとペアリング… + MACアドレスが渡されませんでした。ペアリングできません。 + デバイス固有の設定 + Mi Bandの設定 + 男性 + 女性 + その他 + + + 有効なユーザーデータはありません。今のところ、ダミーのユーザーデータを使用します。 + お使いのMi Bandが振動と点滅したとき、それを連続して数回タップしてください。 + インストール + お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。 + 注: + デバイスイメージ + あなたについて + 名前/別名 + 誕生年 + 性別 + 身長(cm) + 体重(kg) + バイブレーション回数 + 睡眠観測 + ログファイルを出力 (再起動が必要) + 初期化中 + 活動データを取得中 + %1$sから%2$sまで + 左または右に身に着けますか? + バイブレーション プロファイル + スタッカート + 短い + 標準 + 長い + 水滴 + 鳴り響く + 目覚まし時計 + バイブレーション + SMS通知 + バイブレーションの設定 + 全般通知 + Pebble通知 + K9 Mail 通知 + 着信通知 + なくしたデバイスを探す + キャンセルで振動を停止します。 + あなたの活動 + アラームを設定 + アラームを設定 + アラームの詳細 + + + + + + + + スマート ウェイクアップ + アラームの設定時にエラーが発生しました。もう一度試してください! + アラームをデバイスに送信しました! + データはありません。デバイスを同期しますか? + %1$s のデータを %2$s から転送することについて + 毎日の目標ステップ + \'%1$s\' の実行エラー + あなたの活動 (アルファ版) + 接続できません: %1$s + このファイルをインストールするハンドラーを見つけることができません。 + 指定されたファイルをインストールできません: %1$s + 指定されたファームウェアをインストールできません: Pebbleのハードウェアリビジョンと一致しません。 + インストールのステータスが決定するまでお待ちください… + ガジェット バッテリー残量低! + %1$s バッテリー残: %2$s%% + 最後の充電: %s \n + 充電回数: %s + あなたの睡眠 + 週あたり歩数 + あなたの活動と睡眠 + ファームウェアの更新中… + ファイルをインストールできません。デバイスの準備ができていません。 + Mi Band ファームウェア %1$s + 互換性のバージョン + テストされていないバージョン! + デバイスへの接続: %1$s + Pebble ファームウェア %1$s + ハードウェアリビジョンを修正 + ハードウェアリビジョンが一致しません! + %1$s (%2$s) + ファームウェアの転送で問題があります。お使いのMi Bandを再起動しないでください! + ファームウェアのメタデータ転送で問題があります + ファームウェアのインストールが完了しました + ファームウェアのインストールが完了しました。デバイスを再起動します… + ファームウェアの書き込みに失敗しました + 歩数 + 生活活動 + 今日の歩数、目標: %1$s + 活動データは、バンドが応答しない場合は、クリアされません。 GBを他のアプリと一緒に使用する場合に便利です。 + 同期後もMi Bandに活動データを保持します。 GBを他のアプリと一緒に使用する場合に便利です。 + 活動データ転送に応答しない + 歩数履歴 + 現在の歩数/分 + 合計歩数 + 毎分の歩数履歴 + あなたの活動を開始 + 活動 + 浅い睡眠 + 深い睡眠 + 非装着 + 接続されていません。 + すべてのアラームを無効にしました + 活動データをデバイスに保持 + 非互換性のファームウェア + このファームウェアは、デバイスと互換性がありません + 今後のイベントのために予約するアラーム + 再接続の待機中 + 再インストール +