diff --git a/app/src/main/java/james/metronome/activities/MainActivity.java b/app/src/main/java/james/metronome/activities/MainActivity.java
index 855d72c1d9098f5d8afe159842ece6aaf30fed79..069be4b91f6cfb5da206d9803609bce2b8c9f18a 100644
--- a/app/src/main/java/james/metronome/activities/MainActivity.java
+++ b/app/src/main/java/james/metronome/activities/MainActivity.java
@@ -77,7 +77,7 @@ public class MainActivity extends AestheticActivity implements TicksView.OnTickC
playView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (isBound && service != null) {
+ if (isBound()) {
if (service.isPlaying())
service.pause();
else service.play();
@@ -207,15 +207,6 @@ public class MainActivity extends AestheticActivity implements TicksView.OnTickC
textColorPrimarySubscription.dispose();
}
- @Override
- protected void onStart() {
- Intent intent = new Intent(this, MetronomeService.class);
- startService(intent);
- bindService(intent, this, Context.BIND_AUTO_CREATE);
-
- super.onStart();
- }
-
@Override
protected void onResume() {
super.onResume();
@@ -228,6 +219,15 @@ public class MainActivity extends AestheticActivity implements TicksView.OnTickC
unsubscribe();
}
+ @Override
+ protected void onStart() {
+ Intent intent = new Intent(this, MetronomeService.class);
+ startService(intent);
+ bindService(intent, this, Context.BIND_AUTO_CREATE);
+
+ super.onStart();
+ }
+
@Override
protected void onStop() {
if (isBound) {
diff --git a/settings.gradle b/settings.gradle
index e7b4def49cb53d9aa04228dd3edb14c9e635e003..9ccfb619151662df9eff70d200387b93ad9a6a41 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app'
+include ':app', ':wear'
diff --git a/wear/.gitignore b/wear/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9
--- /dev/null
+++ b/wear/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/wear/build.gradle b/wear/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..02a6b13d3498eca1b105c0b6168561c143695191
--- /dev/null
+++ b/wear/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+
+
+android {
+ compileSdkVersion 26
+ buildToolsVersion "26.0.0"
+
+ defaultConfig {
+ applicationId "james.metronome.wear"
+ minSdkVersion 21
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.google.android.support:wearable:2.0.1'
+ compile 'com.google.android.gms:play-services-wearable:10.2.1'
+ provided 'com.google.android.wearable:wearable:1.0.0'
+}
diff --git a/wear/proguard-rules.pro b/wear/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..be289b01a1c04e124fcdd736e0996f3e767ca074
--- /dev/null
+++ b/wear/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/james/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..326e59ab66fc02ec4230c1d49b4d0f90ea0aef22
--- /dev/null
+++ b/wear/src/main/AndroidManifest.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/java/james/metronome/wear/MainActivity.java b/wear/src/main/java/james/metronome/wear/MainActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..c41ee72a7e244816bb2eb4cfef433bea2ccf5b26
--- /dev/null
+++ b/wear/src/main/java/james/metronome/wear/MainActivity.java
@@ -0,0 +1,173 @@
+package james.metronome.wear;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.wearable.activity.WearableActivity;
+import android.support.wearable.view.BoxInsetLayout;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+public class MainActivity extends WearableActivity implements ServiceConnection, MetronomeService.TickListener {
+
+ private BoxInsetLayout container;
+ private ImageView vibrationView;
+ private ImageView playView;
+ private TextView bpmView;
+ private SeekBar seekBar;
+
+ private MetronomeService service;
+ private boolean isBound;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ setAmbientEnabled();
+
+ container = findViewById(R.id.container);
+ vibrationView = findViewById(R.id.vibration);
+ playView = findViewById(R.id.play);
+ bpmView = findViewById(R.id.bpm);
+ seekBar = findViewById(R.id.seekBar);
+
+ if (isBound()) {
+ vibrationView.setImageResource(service.isVibration() ? R.drawable.ic_vibration : R.drawable.ic_sound);
+ playView.setImageResource(service.isPlaying() ? R.drawable.ic_pause : R.drawable.ic_play);
+ bpmView.setText(String.format(Locale.getDefault(), getString(R.string.bpm), String.valueOf(service.getBpm())));
+ seekBar.setProgress(service.getBpm());
+ }
+
+ vibrationView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ service.setVibration(!service.isVibration());
+ vibrationView.setImageResource(service.isVibration() ? R.drawable.ic_vibration : R.drawable.ic_sound);
+ }
+ });
+
+ playView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (isBound()) {
+ if (service.isPlaying())
+ service.pause();
+ else service.play();
+ }
+ }
+ });
+
+ seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+ setBpm(i);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+ }
+
+ private void setBpm(int bpm) {
+ if (isBound()) {
+ service.setBpm(bpm);
+ bpmView.setText(String.format(Locale.getDefault(), getString(R.string.bpm), String.valueOf(bpm)));
+ }
+ }
+
+ private boolean isBound() {
+ return isBound && service != null;
+ }
+
+ @Override
+ public void onEnterAmbient(Bundle ambientDetails) {
+ super.onEnterAmbient(ambientDetails);
+ updateDisplay();
+ }
+
+ @Override
+ public void onUpdateAmbient() {
+ super.onUpdateAmbient();
+ updateDisplay();
+ }
+
+ @Override
+ public void onExitAmbient() {
+ updateDisplay();
+ super.onExitAmbient();
+ }
+
+ private void updateDisplay() {
+ if (isAmbient())
+ container.setBackgroundColor(Color.BLACK);
+ else container.setBackground(null);
+ }
+
+ @Override
+ protected void onStart() {
+ Intent intent = new Intent(this, MetronomeService.class);
+ startService(intent);
+ bindService(intent, this, Context.BIND_AUTO_CREATE);
+
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ if (isBound) {
+ unbindService(this);
+ isBound = false;
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ MetronomeService.LocalBinder binder = (MetronomeService.LocalBinder) iBinder;
+ service = binder.getService();
+ service.setTickListener(this);
+ isBound = true;
+
+ if (vibrationView != null)
+ vibrationView.setImageResource(service.isVibration() ? R.drawable.ic_vibration : R.drawable.ic_sound);
+
+ if (playView != null)
+ playView.setImageResource(service.isPlaying() ? R.drawable.ic_pause : R.drawable.ic_play);
+
+ if (bpmView != null)
+ bpmView.setText(String.format(Locale.getDefault(), getString(R.string.bpm), String.valueOf(service.getBpm())));
+
+ if (seekBar != null)
+ seekBar.setProgress(service.getBpm());
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ isBound = false;
+ }
+
+ @Override
+ public void onStartTicks() {
+ playView.setImageResource(R.drawable.ic_pause);
+ }
+
+ @Override
+ public void onStopTicks() {
+ playView.setImageResource(R.drawable.ic_play);
+ }
+}
diff --git a/wear/src/main/java/james/metronome/wear/MetronomeService.java b/wear/src/main/java/james/metronome/wear/MetronomeService.java
new file mode 100644
index 0000000000000000000000000000000000000000..2015ddbfe4b01b5c889193cbad336233cdaac588
--- /dev/null
+++ b/wear/src/main/java/james/metronome/wear/MetronomeService.java
@@ -0,0 +1,195 @@
+package james.metronome.wear;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationCompat;
+
+public class MetronomeService extends Service implements Runnable {
+
+ public static final String ACTION_PAUSE = "james.metronome.ACTION_PAUSE";
+
+ public static final String PREF_VIBRATION = "vibration";
+ public static final String PREF_INTERVAL = "interval";
+
+ private final IBinder binder = new LocalBinder();
+
+ private SharedPreferences prefs;
+ private int bpm;
+ private long interval;
+
+ private SoundPool soundPool;
+ private Handler handler;
+ private int soundId = -1;
+ private boolean isPlaying;
+ private boolean isVibration;
+
+ private Vibrator vibrator;
+
+ private TickListener listener;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ soundPool = new SoundPool.Builder()
+ .setMaxStreams(1)
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .build())
+ .build();
+ } else soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
+
+ isVibration = prefs.getBoolean(PREF_VIBRATION, true);
+ if (!isVibration)
+ soundId = soundPool.load(this, R.raw.click, 1);
+
+ interval = prefs.getLong(PREF_INTERVAL, 500);
+ bpm = toBpm(interval);
+
+ handler = new Handler();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null && intent.getAction() != null) {
+ switch (intent.getAction()) {
+ case ACTION_PAUSE:
+ pause();
+ }
+ }
+ return START_STICKY;
+ }
+
+ private static int toBpm(long interval) {
+ return (int) (60000 / interval);
+ }
+
+ private static long toInterval(int bpm) {
+ return (long) 60000 / bpm;
+ }
+
+ public void play() {
+ handler.post(this);
+ isPlaying = true;
+
+ Intent intent = new Intent(this, MetronomeService.class);
+ intent.setAction(ACTION_PAUSE);
+
+ startForeground(530,
+ new NotificationCompat.Builder(this)
+ .setContentTitle(getString(R.string.notification_title))
+ .setContentText(getString(R.string.notification_desc))
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentIntent(PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_ONE_SHOT))
+ .build()
+ );
+
+ if (listener != null)
+ listener.onStartTicks();
+ }
+
+ public void pause() {
+ handler.removeCallbacks(this);
+ stopForeground(true);
+ isPlaying = false;
+
+ if (listener != null)
+ listener.onStopTicks();
+ }
+
+ public void setBpm(int bpm) {
+ this.bpm = bpm;
+ interval = toInterval(bpm);
+ prefs.edit().putLong(PREF_INTERVAL, interval).apply();
+ }
+
+ public void setVibration(boolean vibration) {
+ isVibration = vibration;
+ if (!isVibration)
+ soundId = soundPool.load(this, R.raw.click, 1);
+ else soundId = -1;
+
+ prefs.edit().putBoolean(PREF_VIBRATION, isVibration).apply();
+ }
+
+ public boolean isPlaying() {
+ return isPlaying;
+ }
+
+ public long getInterval() {
+ return interval;
+ }
+
+ public int getBpm() {
+ return bpm;
+ }
+
+ public boolean isVibration() {
+ return isVibration;
+ }
+
+ public void setTickListener(TickListener listener) {
+ this.listener = listener;
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ listener = null;
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ handler.removeCallbacks(this);
+ super.onDestroy();
+ }
+
+ @Override
+ public void run() {
+ if (isPlaying) {
+ if (soundId != -1)
+ soundPool.play(soundId, 1.0f, 1.0f, 0, 0, 1.0f);
+ else if (Build.VERSION.SDK_INT >= 26)
+ vibrator.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE));
+ else vibrator.vibrate(50);
+
+ handler.postDelayed(this, interval);
+ }
+ }
+
+ public class LocalBinder extends Binder {
+ public MetronomeService getService() {
+ return MetronomeService.this;
+ }
+ }
+
+ public interface TickListener {
+ void onStartTicks();
+
+ void onStopTicks();
+ }
+}
diff --git a/wear/src/main/res/drawable-hdpi/ic_notification.png b/wear/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a77d4c02144444e068075b823409fc127dec16a
Binary files /dev/null and b/wear/src/main/res/drawable-hdpi/ic_notification.png differ
diff --git a/wear/src/main/res/drawable-mdpi/ic_notification.png b/wear/src/main/res/drawable-mdpi/ic_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..c51e577b62675d9e0fc723e26d1b7be4c3d44e9a
Binary files /dev/null and b/wear/src/main/res/drawable-mdpi/ic_notification.png differ
diff --git a/wear/src/main/res/drawable-xhdpi/ic_notification.png b/wear/src/main/res/drawable-xhdpi/ic_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..4aa838340e3c968258a954ee4b9f010dee54bc69
Binary files /dev/null and b/wear/src/main/res/drawable-xhdpi/ic_notification.png differ
diff --git a/wear/src/main/res/drawable-xxhdpi/ic_notification.png b/wear/src/main/res/drawable-xxhdpi/ic_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e46e5bdc1fe6f988b406caebf92836f39d266af
Binary files /dev/null and b/wear/src/main/res/drawable-xxhdpi/ic_notification.png differ
diff --git a/wear/src/main/res/drawable/ic_pause.xml b/wear/src/main/res/drawable/ic_pause.xml
new file mode 100644
index 0000000000000000000000000000000000000000..53b5e6a9aacaf7ed8f5e471ef3047b6c21174436
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_pause.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_play.xml b/wear/src/main/res/drawable/ic_play.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dbe3ec664e84e6a96f68925b6e42313f7414ace6
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_play.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_sound.xml b/wear/src/main/res/drawable/ic_sound.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2ee5bce43339893f157b976c88fbe906a353d4c4
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_sound.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_vibration.xml b/wear/src/main/res/drawable/ic_vibration.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5d1a010e663b85d2716885d780d566ccaa98e1eb
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_vibration.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/wear/src/main/res/layout/activity_main.xml b/wear/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3afc8e3524e2bc16589f9f0d120ddf4efdb05cd4
--- /dev/null
+++ b/wear/src/main/res/layout/activity_main.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wear/src/main/res/mipmap-hdpi/ic_launcher.png b/wear/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..e715f8b7afacd4b834b6f921083d2e66253ecc30
Binary files /dev/null and b/wear/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/mipmap-mdpi/ic_launcher.png b/wear/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c1ab32e6fef98b538ed43382ae1ea9993be917a
Binary files /dev/null and b/wear/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/mipmap-xhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ca302927091522a4e0bb859d2d2fb2403152485
Binary files /dev/null and b/wear/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..7033eec5d6d132e6a2959f1b79ac7e7c12ff9a01
Binary files /dev/null and b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..546f413998e490314978fca48fe2578803c33d9c
Binary files /dev/null and b/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/raw/click.mp3 b/wear/src/main/res/raw/click.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..48480af0e90beaa1f5ad7e8ca3105b09dd834e7d
Binary files /dev/null and b/wear/src/main/res/raw/click.mp3 differ
diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..878da7001778074d88d3f09cb4b7320c89db8985
--- /dev/null
+++ b/wear/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+ Metronome
+
+ %1$s BPM
+
+ Metronome is running…
+ Touch to stop.
+