Browse Source

fixed #5 - added an android wear module

main
James Fenn 3 years ago
parent
commit
b76cf679c8
24 changed files with 559 additions and 11 deletions
  1. +10
    -10
      app/src/main/java/james/metronome/activities/MainActivity.java
  2. +1
    -1
      settings.gradle
  3. +1
    -0
      wear/.gitignore
  4. +28
    -0
      wear/build.gradle
  5. +25
    -0
      wear/proguard-rules.pro
  6. +33
    -0
      wear/src/main/AndroidManifest.xml
  7. +173
    -0
      wear/src/main/java/james/metronome/wear/MainActivity.java
  8. +195
    -0
      wear/src/main/java/james/metronome/wear/MetronomeService.java
  9. BIN
      wear/src/main/res/drawable-hdpi/ic_notification.png
  10. BIN
      wear/src/main/res/drawable-mdpi/ic_notification.png
  11. BIN
      wear/src/main/res/drawable-xhdpi/ic_notification.png
  12. BIN
      wear/src/main/res/drawable-xxhdpi/ic_notification.png
  13. +9
    -0
      wear/src/main/res/drawable/ic_pause.xml
  14. +9
    -0
      wear/src/main/res/drawable/ic_play.xml
  15. +9
    -0
      wear/src/main/res/drawable/ic_sound.xml
  16. +9
    -0
      wear/src/main/res/drawable/ic_vibration.xml
  17. +49
    -0
      wear/src/main/res/layout/activity_main.xml
  18. BIN
      wear/src/main/res/mipmap-hdpi/ic_launcher.png
  19. BIN
      wear/src/main/res/mipmap-mdpi/ic_launcher.png
  20. BIN
      wear/src/main/res/mipmap-xhdpi/ic_launcher.png
  21. BIN
      wear/src/main/res/mipmap-xxhdpi/ic_launcher.png
  22. BIN
      wear/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  23. BIN
      wear/src/main/res/raw/click.mp3
  24. +8
    -0
      wear/src/main/res/values/strings.xml

+ 10
- 10
app/src/main/java/james/metronome/activities/MainActivity.java View File

@@ -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) {


+ 1
- 1
settings.gradle View File

@@ -1 +1 @@
include ':app'
include ':app', ':wear'

+ 1
- 0
wear/.gitignore View File

@@ -0,0 +1 @@
/build

+ 28
- 0
wear/build.gradle View File

@@ -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'
}

+ 25
- 0
wear/proguard-rules.pro View File

@@ -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

+ 33
- 0
wear/src/main/AndroidManifest.xml View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="james.metronome.wear">

<uses-feature android:name="android.hardware.type.watch" />

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault">
<uses-library
android:name="com.google.android.wearable"
android:required="false" />

<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service android:name=".MetronomeService" />
</application>

</manifest>

+ 173
- 0
wear/src/main/java/james/metronome/wear/MainActivity.java View File

@@ -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);
}
}

+ 195
- 0
wear/src/main/java/james/metronome/wear/MetronomeService.java View File

@@ -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();
}
}

BIN
wear/src/main/res/drawable-hdpi/ic_notification.png View File

Before After
Width: 36  |  Height: 36  |  Size: 568 B

BIN
wear/src/main/res/drawable-mdpi/ic_notification.png View File

Before After
Width: 24  |  Height: 24  |  Size: 364 B

BIN
wear/src/main/res/drawable-xhdpi/ic_notification.png View File

Before After
Width: 48  |  Height: 48  |  Size: 806 B

BIN
wear/src/main/res/drawable-xxhdpi/ic_notification.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.2 KiB

+ 9
- 0
wear/src/main/res/drawable/ic_pause.xml View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
</vector>

+ 9
- 0
wear/src/main/res/drawable/ic_play.xml View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M8,5v14l11,-7z" />
</vector>

+ 9
- 0
wear/src/main/res/drawable/ic_sound.xml View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
</vector>

+ 9
- 0
wear/src/main/res/drawable/ic_vibration.xml View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M0,15h2L2,9L0,9v6zM3,17h2L5,7L3,7v10zM22,9v6h2L24,9h-2zM19,17h2L21,7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19L8,19L8,5h8v14z" />
</vector>

+ 49
- 0
wear/src/main/res/layout/activity_main.xml View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_box="all">

<ImageView
android:id="@+id/vibration"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitCenter"
android:tint="@android:color/white"
android:src="@drawable/ic_vibration" />

<ImageView
android:id="@+id/play"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerInside"
android:tint="@android:color/white"
android:src="@drawable/ic_play" />

<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="300" />

<TextView
android:id="@+id/bpm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="14sp" />

</LinearLayout>

</android.support.wearable.view.BoxInsetLayout>

BIN
wear/src/main/res/mipmap-hdpi/ic_launcher.png View File

Before After
Width: 72  |  Height: 72  |  Size: 3.0 KiB

BIN
wear/src/main/res/mipmap-mdpi/ic_launcher.png View File

Before After
Width: 48  |  Height: 48  |  Size: 1.8 KiB

BIN
wear/src/main/res/mipmap-xhdpi/ic_launcher.png View File

Before After
Width: 96  |  Height: 96  |  Size: 3.8 KiB

BIN
wear/src/main/res/mipmap-xxhdpi/ic_launcher.png View File

Before After
Width: 144  |  Height: 144  |  Size: 7.0 KiB

BIN
wear/src/main/res/mipmap-xxxhdpi/ic_launcher.png View File

Before After
Width: 192  |  Height: 192  |  Size: 8.3 KiB

BIN
wear/src/main/res/raw/click.mp3 View File


+ 8
- 0
wear/src/main/res/values/strings.xml View File

@@ -0,0 +1,8 @@
<resources>
<string name="app_name">Metronome</string>

<string name="bpm">%1$s BPM</string>

<string name="notification_title">Metronome is running…</string>
<string name="notification_desc">Touch to stop.</string>
</resources>

Loading…
Cancel
Save