Created foreground service

This commit is contained in:
Ruben van de Ven 2018-10-30 15:39:54 +01:00
parent 43c11574a8
commit c9e31bf9f0
20 changed files with 445 additions and 58 deletions

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="imageWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="imageAssetPanel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="launcher">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="scalingPercent" value="146" />
<entry key="trimmed" value="true" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="foregroundImage" value="$PROJECT_DIR$/../Heart_Rate_Monitor_Flat_Icon_Vector.svg" />
<entry key="showSafeZone" value="false" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="notification">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="trimmed" value="true" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="image">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="paddingPercent" value="5" />
<entry key="trimmed" value="true" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="assetType" value="IMAGE" />
<entry key="imageAsset" value="$PROJECT_DIR$/../icon_notif.png" />
<entry key="outputName" value="ic_notification" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="outputIconType" value="NOTIFICATION" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

1
README.md Normal file
View File

@ -0,0 +1 @@
Icon by [VideoPlasty](https://videoplasty.com/) Creative Commons BY-SA, trough [WikiMedia](https://commons.wikimedia.org/wiki/File:Heart_Rate_Monitor_Flat_Icon_Vector.svg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -8,6 +8,7 @@ import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
@ -28,6 +29,8 @@ public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.getSimpleName();
private TextView statusLabel;
private TextView serviceButton;
private TextView beatStatus;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -35,6 +38,21 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
statusLabel = findViewById(R.id.status_msg);
serviceButton = findViewById(R.id.service_button);
beatStatus = findViewById(R.id.beat_status);
// View.OnClickListener listener = new On;
serviceButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
serviceButton.setEnabled(false);
if(HeartRateService.isRunning()) {
startHeartRateMonitor();
} else {
stopHeartRateMonitor();
}
}
});
setup();
@ -53,8 +71,10 @@ public class MainActivity extends AppCompatActivity {
public void setup() {
if (HeartRateService.isRunning()) {
statusLabel.setText("Running");
updateServiceButton(true);
} else {
statusLabel.setText("Stopped");
updateServiceButton(false);
}
registerBroadcastReceiver();
}
@ -64,14 +84,25 @@ public class MainActivity extends AppCompatActivity {
Toast.makeText(this, "Already running", Toast.LENGTH_LONG);
} else {
Intent intent = new Intent(MainActivity.this, HeartRateService.class);
intent.setAction(HeartRateService.ACTION_START);
startService(intent);
}
}
public void updateServiceButton(boolean running) {
if(running) {
serviceButton.setText(R.string.service_stop);
} else {
serviceButton.setText(R.string.service_start);
}
serviceButton.setEnabled(true);
}
public void stopHeartRateMonitor() {
// stop tracking
Intent intent = new Intent(MainActivity.this, HeartRateService.class);
stopService(intent);
intent.setAction(HeartRateService.ACTION_STOP);
startService(intent);
}
/**
@ -86,6 +117,11 @@ public class MainActivity extends AppCompatActivity {
filter.addAction(HeartRateService.BROADCAST_MONITOR_UPDATE);
filter.addAction(HeartRateService.BROADCAST_MONITOR_UNKNOWN);
filter.addAction(HeartRateService.BROADCAST_SOCKET_SENT);
filter.addAction(HeartRateService.BROADCAST_SERVICE_START);
filter.addAction(HeartRateService.BROADCAST_SERVICE_STOPPED);
filter.addAction(HeartRateService.BROADCAST_BEAT);
filter.addAction(HeartRateService.BROADCAST_MONITOR_CONNECT_ATTEMPT);
filter.addAction(HeartRateService.BROADCAST_MONITOR_CONNECT_RESULT);
registerReceiver(mBroadcastReceiver, filter);
}
@ -112,53 +148,50 @@ public class MainActivity extends AppCompatActivity {
return;
}
switch (intent.getAction()) {
case HeartRateService.BROADCAST_SERVICE_START:
statusLabel.setText("Started");
updateServiceButton(true);
break;
case HeartRateService.BROADCAST_SERVICE_STOPPED:
statusLabel.setText("Service Stopped");
updateServiceButton(false);
break;
case HeartRateService.BROADCAST_MONITOR_CONNECTED:
statusLabel.setText("Running");
statusLabel.setText("Connected");
break;
case HeartRateService.BROADCAST_MONITOR_DISCONNECTED:
statusLabel.setText("Stopped");
statusLabel.setText("Disconnected");
break;
case HeartRateService.BROADCAST_MONITOR_UPDATE:
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
String currentDateandTime = sdf.format(new Date());
statusLabel.setText("Last update: %s".format(currentDateandTime));
break;
case HeartRateService.BROADCAST_SOCKET_SENT:
String msg = intent.getStringExtra("msg");
statusLabel.setText("Last msg: %s".format(msg));
break;
case HeartRateService.BROADCAST_MONITOR_UNKNOWN:
int status = intent.getIntExtra("status", -1);
statusLabel.setText("Unknown state: %s".format(String.valueOf(status)));
break;
case HeartRateService.BROADCAST_BEAT:
String rate = intent.getStringExtra("rate");
String count = intent.getStringExtra("count");
String time = intent.getStringExtra("time");
Log.d(TAG, "Beat: " + rate);
beatStatus.setText(rate + " bpm / " + count + " beats / "+time + " s");
break;
case HeartRateService.BROADCAST_MONITOR_CONNECT_ATTEMPT:
beatStatus.setText("Attempting connection to monitor");
break;
case HeartRateService.BROADCAST_MONITOR_CONNECT_RESULT:
String connectMsg = intent.getStringExtra("msg");
beatStatus.setText("Connection: %s".format(connectMsg ));
break;
}
}
};
/**
* Get the token for use with the syncing.
* @return
*/
public String getToken() {
String json = null;
String token = null;
try {
InputStream is = getAssets().open("token.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
try {
JSONObject jsonObject = new JSONObject(json);
token = jsonObject.getString("token'");
} catch (JSONException e) {
e.printStackTrace();
return null;
}
return token;
}
}

View File

@ -1,6 +1,8 @@
package com.rubenvandeven.heartbeatstreamer.heartrate;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.DialogInterface;
import android.content.Intent;
@ -9,6 +11,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@ -23,11 +26,19 @@ import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.WebSocket;
import com.rubenvandeven.heartbeatstreamer.MainActivity;
import com.rubenvandeven.heartbeatstreamer.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Date;
import java.util.EnumSet;
public class HeartRateService extends Service {
@ -37,22 +48,51 @@ public class HeartRateService extends Service {
public static final String BROADCAST_MONITOR_DISCONNECTED = "com.rubenvandeven.heartbeat.broadcast.monitor_stopped";
public static final String BROADCAST_MONITOR_UNKNOWN = "com.rubenvandeven.heartbeat.broadcast.monitor_unknown";
public static final String BROADCAST_MONITOR_UPDATE = "com.rubenvandeven.heartbeat.broadcast.monitor_update";
public static final String BROADCAST_MONITOR_CONNECT_ATTEMPT = "com.rubenvandeven.heartbeat.broadcast.connect_attempt";
public static final String BROADCAST_MONITOR_CONNECT_RESULT = "com.rubenvandeven.heartbeat.broadcast.connect_reslt";
public static final String BROADCAST_SOCKET_SENT = "com.rubenvandeven.heartbeat.broadcast.socket_sent";
public static final String BROADCAST_SERVICE_START = "com.rubenvandeven.heartbeat.broadcast.service_start";
public static final String BROADCAST_SERVICE_STOPPED = "com.rubenvandeven.heartbeat.broadcast.service_stopped";
public static final String BROADCAST_BEAT = "com.rubenvandeven.heartbeat.broadcast.beat";
public static final String ACTION_START = "com.rubenvandeven.heartbeat.service_start";
public static final String ACTION_STOP = "com.rubenvandeven.heartbeat.service_stop";
public static final int NOTIFICATION_ID = 101;
AsyncScanController<AntPlusHeartRatePcc> hrScanCtrl;
AntPlusHeartRatePcc hrPcc;
PccReleaseHandle<AntPlusHeartRatePcc> releaseHandle;
String lastMsg = "";
JSONObject lastMsg = null;
public static boolean isRunning = false;
NotificationCompat.Builder builder;
NotificationManager notificationManager;
@Override
public void onCreate() {
isRunning = true;
notificationManager =
(NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
setRunning(true);
requestAccessToPcc();
}
public void setRunning(boolean running) {
if(running == isRunning) {
return;
}
isRunning = running;
Intent i;
if(running) {
i = new Intent(BROADCAST_SERVICE_START);
} else {
i = new Intent(BROADCAST_SERVICE_STOPPED);
}
sendBroadcast(i);
}
/**
* Start main thread, request location updates, start synchronization.
*
@ -63,7 +103,32 @@ public class HeartRateService extends Service {
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Received start id " + startId + ": " + intent);
if (intent.getAction().equals(ACTION_START)) {
Log.i(TAG, "Received start id " + startId + ": " + intent);
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
builder = new NotificationCompat.Builder(this)
.setContentText("Starting")
.setContentTitle("Heartbeat Streamer")
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(false)
.setOngoing(true)
.setOnlyAlertOnce(false)
.setContentIntent(pendingIntent)
;
startForeground(NOTIFICATION_ID,
builder.build());
} else if (intent.getAction().equals(ACTION_STOP)) {
Log.i(TAG, "Received stop id " + startId + ": " + intent);
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
@ -93,24 +158,31 @@ public class HeartRateService extends Service {
public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode,
DeviceState initialDeviceState) {
// showDataDisplay("Connecting...");
Intent i = new Intent(BROADCAST_MONITOR_CONNECT_RESULT);
switch (resultCode) {
case SUCCESS:
i.putExtra("msg", "Success");
connectHr(result);
break;
case CHANNEL_NOT_AVAILABLE:
showNotification("Channel Not Available");
i.putExtra("msg", "Channel Not Available");
break;
case OTHER_FAILURE:
showNotification("RequestAccess failed. See logcat for details.");
i.putExtra("msg", "RequestAccess failed. See logcat for details.");
break;
case USER_CANCELLED:
showNotification("Cancelled. Do reset.");
i.putExtra("msg", "Cancelled. Do reset.");
break;
case UNRECOGNIZED:
default:
showNotification("Unknown error. Do reset.");
i.putExtra("msg", "unknown error. Do reset.");
break;
}
sendBroadcast(i);
}
};
@ -138,30 +210,86 @@ public class HeartRateService extends Service {
final String textHeartBeatEventTime = df.format(heartBeatEventTime)
+ ((AntPlusHeartRatePcc.DataState.INITIAL_VALUE.equals(dataState)) ? "*" : "");
final String msg = String.format("{\"rate\":\"%s\", \"count\":\"%s\", \"time\":\"%s\"}", textHeartRate, textHeartBeatCount, textHeartBeatEventTime);
Intent intent = new Intent(BROADCAST_BEAT);
intent.putExtra("rate", textHeartRate);
intent.putExtra("count", textHeartBeatCount);
intent.putExtra("time", textHeartBeatEventTime);
sendBroadcast(intent);
if(msg.contentEquals(lastMsg)) {
Log.i(TAG, "Skip duplicate");
return;
builder.setContentText(textHeartRate + " bpm");
notificationManager.notify(NOTIFICATION_ID, builder.build());
JSONObject jObjectData = new JSONObject();
// final String msg = String.format("{\"rate\":\"%s\", \"count\":\"%s\", \"time\":\"%s\"}", textHeartRate, textHeartBeatCount, textHeartBeatEventTime);
final String msg;
try {
jObjectData.put("rate", textHeartRate);
jObjectData.put("count", textHeartBeatCount);
jObjectData.put("time", textHeartBeatEventTime);
jObjectData.put("timestamp", DateFormat.getDateTimeInstance().format(new Date()));
jObjectData.put("token", getToken());
msg = jObjectData.toString();
if(lastMsg != null && lastMsg.getString("time").equals(jObjectData.getString("time"))) {
Log.i(TAG, "Skip duplicate");
return;
}
lastMsg = jObjectData;
sendResult(jObjectData.toString());
} catch (JSONException e) {
e.printStackTrace();
}
lastMsg = msg;
sendResult(msg);
}
});
}
private void sendResult(String msg) {
Log.d(TAG, "Send: " + msg);
final String m = msg;
String token = null;
/**
* Get the token for use with the syncing.
* @return
*/
public String getToken() {
if(token != null) {
return token;
}
String json = null;
try {
InputStream is = getAssets().open("token.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
try {
JSONObject jsonObject = new JSONObject(json);
token = jsonObject.getString("token'");
} catch (JSONException e) {
e.printStackTrace();
return null;
}
return token;
}
private void sendResult(final String m) {
Log.d(TAG, "Send: " + m);
Future<WebSocket> fws = AsyncHttpClient.getDefaultInstance().websocket("ws://heartbeat.rubenvandeven.com:8888/ws", "my-protocol", new AsyncHttpClient.WebSocketConnectCallback() {
@Override
public void onCompleted(Exception ex, final WebSocket webSocket) {
if (ex != null) {
ex.printStackTrace();
// TODO: retry in 30s - ie. after internet loss
// TODO: store for later sync
return;
}
webSocket.send(m);
@ -188,9 +316,20 @@ public class HeartRateService extends Service {
* wait to reconnect to dead HR-monitor
*/
protected void waitAndReconnect() {
//TODO:
Log.d(TAG, "Wait & reconnect");
requestAccessToPcc();
if(hrPcc != null) {
hrPcc.subscribeHeartRateDataEvent(null);
}
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
Log.i(TAG, "Attempt reconnect");
requestAccessToPcc();
}
},
20000); // 20 sec delay for connection
}
/**
@ -201,6 +340,7 @@ public class HeartRateService extends Service {
if(hrPcc != null) {
hrPcc.subscribeHeartRateDataEvent(null);
}
sendBroadcast( new Intent(BROADCAST_MONITOR_CONNECT_ATTEMPT) );
releaseHandle = AntPlusHeartRatePcc.requestAccess(this, 4818, 0, resultReceiver, stateChangeReceiver);
}
@ -216,7 +356,7 @@ public class HeartRateService extends Service {
{
releaseHandle.close();
}
isRunning = false;
setRunning(false);
super.onDestroy();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,83 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="757.16064"
android:viewportHeight="757.16064">
<group android:translateX="-101.41968"
android:translateY="-101.41968">
<path
android:pathData="M733.8,717.5h-507c-5.7,0 -10.4,-4.7 -10.4,-10.4v-31.3c0,-5.7 4.7,-10.4 10.4,-10.4h507c5.7,0 10.4,4.7 10.4,10.4v31.3C744.2,712.9 739.6,717.5 733.8,717.5z"
android:fillColor="#E6E6E6"/>
<path
android:pathData="M786.3,681H173.7c-13.5,0 -24.5,-10.9 -24.5,-24.5V266.9c0,-13.5 10.9,-24.5 24.5,-24.5h612.6c13.5,0 24.5,10.9 24.5,24.5v389.6C810.7,670 799.8,681 786.3,681z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M203,289.3h554v304h-554z"
android:fillColor="#4B535C"/>
<path
android:pathData="M223.4,309.7l533.6,0l0,-20.4l-554,0l0,304l20.4,0z"
android:fillColor="#333333"/>
<path
android:pathData="M206.4,620.7h83.9v23.3h-83.9z"
android:fillColor="#333333"/>
<path
android:pathData="M313.3,620.7h83.9v23.3h-83.9z"
android:fillColor="#333333"/>
<path
android:pathData="M305,309.7L305,593.3"
android:strokeAlpha="0.12"
android:strokeWidth="4.8683"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.12"/>
<path
android:pathData="M417.7,309.7L417.7,593.3"
android:strokeAlpha="0.12"
android:strokeWidth="4.8683"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.12"/>
<path
android:pathData="M530.5,309.7L530.5,593.3"
android:strokeAlpha="0.12"
android:strokeWidth="4.8683"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.12"/>
<path
android:pathData="M643.2,309.7L643.2,593.3"
android:strokeAlpha="0.12"
android:strokeWidth="4.8683"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.12"/>
<path
android:pathData="M223.4,392.4L757,392.4"
android:strokeAlpha="0.12"
android:strokeWidth="4.8683"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.12"/>
<path
android:pathData="M223.4,485.1L757,485.1"
android:strokeAlpha="0.12"
android:strokeWidth="4.8683"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.12"/>
<path
android:pathData="M744.2,632.3m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0"
android:fillColor="#C1272D"/>
<path
android:pathData="M704.7,632.3m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0"
android:fillColor="#8CC63F"/>
<path
android:pathData="M665.1,632.3m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0"
android:fillColor="#C1272D"/>
<path
android:pathData="M201.8,465.9l106.5,0l51.6,-85.3l37.8,139.6l52.2,-110.6l33.8,68.5l46.8,-126.5l46.9,142.5l45.4,-88.7l28.2,62.2l104.9,0"
android:strokeWidth="9.7366"
android:fillColor="#00000000"
android:strokeColor="#12A88B"/>
</group>
</vector>

View File

@ -1,19 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/status_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="..."
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
<TextView
android:id="@+id/beat_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="..."
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/service_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/service_start" />
</LinearLayout>
</LinearLayout>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">`
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -22,4 +22,7 @@
<item>Bike Speed</item>
<item>Stride-based Speed and Distance</item>
</string-array>
<string name="service_start">Start service</string>
<string name="service_stop">Stop service</string>
</resources>