/*
* Copyright (C) 2014 weides@gmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.runnerup.service;
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataItemBuffer;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import org.runnerup.common.tracker.TrackerState;
import org.runnerup.common.util.Constants;
import org.runnerup.common.util.ValueModel;
import org.runnerup.view.MainActivity;
import java.util.HashSet;
import static com.google.android.gms.wearable.PutDataRequest.WEAR_URI_SCHEME;
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
public class StateService extends Service implements NodeApi.NodeListener, MessageApi.MessageListener, DataApi.DataListener, ValueModel.ChangeListener<Bundle> {
public static final String UPDATE_TIME = "UPDATE_TIME";
private final IBinder mBinder = new LocalBinder();
private GoogleApiClient mGoogleApiClient;
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final HashSet<Node> connectedNodes = new HashSet<Node>();
private String phoneNode;
private Bundle data;
private final ValueModel<TrackerState> trackerState = new ValueModel<TrackerState>();
private final ValueModel<Bundle> headers = new ValueModel<>();
private MainActivity headersListener;
@Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(getApplicationContext())
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
/* set "our" data */
setData();
Wearable.NodeApi.addListener(mGoogleApiClient, StateService.this);
Wearable.MessageApi.addListener(mGoogleApiClient, StateService.this);
Wearable.DataApi.addListener(mGoogleApiClient, StateService.this);
/* read already existing data */
readData();
/** get info about connected nodes in background */
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(
new ResultCallback<NodeApi.GetConnectedNodesResult>() {
@Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
System.err.println("onGetConnectedNodes");
for (Node n : nodes.getNodes()) {
onPeerConnected(n);
}
}
});
}
@Override
public void onConnectionSuspended(int cause) {
}
})
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult result) {
}
})
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
this.headers.registerChangeListener(this);
System.err.println("StateService.onCreate()");
}
private boolean checkConnection() {
return mGoogleApiClient != null && mGoogleApiClient.isConnected();
}
private void readData() {
Wearable.DataApi.getDataItems(mGoogleApiClient, new Uri.Builder()
.scheme(WEAR_URI_SCHEME).path(Constants.Wear.Path.PHONE_NODE_ID).build())
.setResultCallback(
new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
for (DataItem dataItem : dataItems) {
phoneNode = dataItem.getUri().getHost();
System.err.println("getDataItem => phoneNode:" + phoneNode);
}
dataItems.release();
}
});
Wearable.DataApi.getDataItems(mGoogleApiClient, new Uri.Builder()
.scheme(WEAR_URI_SCHEME).path(Constants.Wear.Path.TRACKER_STATE).build())
.setResultCallback(
new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
for (DataItem dataItem : dataItems) {
TrackerState newState = getTrackerStateFromDataItem(dataItem);
if (newState != null)
setTrackerState(newState);
}
dataItems.release();
}
});
Wearable.DataApi.getDataItems(mGoogleApiClient, new Uri.Builder()
.scheme(WEAR_URI_SCHEME).path(Constants.Wear.Path.HEADERS).build())
.setResultCallback(
new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
for (DataItem dataItem : dataItems) {
Bundle b = DataMapItem.fromDataItem(dataItem).getDataMap().toBundle();
b.putLong(UPDATE_TIME, System.currentTimeMillis());
headers.set(b);
}
dataItems.release();
}
});
}
private void setData() {
Wearable.DataApi.putDataItem(mGoogleApiClient,
PutDataRequest.create(Constants.Wear.Path.WEAR_NODE_ID));
}
private void clearData() {
/* delete our node id */
Wearable.DataApi.deleteDataItems(mGoogleApiClient,
new Uri.Builder().scheme(WEAR_URI_SCHEME).path(
Constants.Wear.Path.WEAR_NODE_ID).build());
}
@Override
public void onDestroy() {
System.err.println("StateService.onDestroy()");
trackerState.clearListeners();
if (mGoogleApiClient != null) {
if (mGoogleApiClient.isConnected()) {
phoneNode = null;
connectedNodes.clear();
clearData();
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
Wearable.DataApi.removeListener(mGoogleApiClient, this);
}
mGoogleApiClient.disconnect();
mGoogleApiClient = null;
}
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onValueChanged(ValueModel<Bundle> instance, Bundle oldValue, Bundle newValue) {
if (headersListener != null)
headersListener.onValueChanged(oldValue, newValue);
}
public class LocalBinder extends android.os.Binder {
public StateService getService() {
return StateService.this;
}
}
Bundle getBundle(Bundle src, long lastUpdateTime) {
if (src == null)
return null;
long updateTime = src.getLong(UPDATE_TIME, 0);
if (lastUpdateTime >= updateTime)
return null;
Bundle b = new Bundle();
b.putAll(src);
return b;
}
public Bundle getHeaders(long lastUpdateTime) {
return getBundle(headers.get(), lastUpdateTime);
}
public Bundle getData(long lastUpdateTime) {
return getBundle(data, lastUpdateTime);
}
@Override
public void onPeerConnected(Node node) {
System.err.println("onPeerConnected: " + node.getDisplayName() + ", " + node.getId());
connectedNodes.add(node);
}
@Override
public void onPeerDisconnected(Node node) {
System.err.println("onPeerDisconnected: " + node.getDisplayName() + ", " + node.getId());
connectedNodes.remove(node);
if (node.getId().contentEquals(phoneNode))
phoneNode = null;
}
@Override
public void onMessageReceived(MessageEvent messageEvent) {
if (Constants.Wear.Path.MSG_WORKOUT_EVENT.contentEquals(messageEvent.getPath())) {
data = DataMap.fromByteArray(messageEvent.getData()).toBundle();
data.putLong(UPDATE_TIME, System.currentTimeMillis());
} else {
System.err.println("onMessageReceived: " + messageEvent);
}
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent ev : dataEvents) {
System.err.println("onDataChanged: " + ev.getDataItem().getUri());
String path = ev.getDataItem().getUri().getPath();
if (Constants.Wear.Path.PHONE_NODE_ID.contentEquals(path)) {
setPhoneNode(ev);
} else if (Constants.Wear.Path.HEADERS.contentEquals(path)) {
setHeaders(ev);
} else if (Constants.Wear.Path.TRACKER_STATE.contentEquals(path)) {
setTrackerState(ev);
}
}
}
public void setPhoneNode(DataEvent ev) {
if (ev.getType() == DataEvent.TYPE_CHANGED) {
phoneNode = new String(ev.getDataItem().getData());
} else if (ev.getType() == DataEvent.TYPE_DELETED) {
phoneNode = null;
resetState();
}
}
private void setHeaders(DataEvent ev) {
if (ev.getType() == DataEvent.TYPE_CHANGED) {
Bundle b = DataMapItem.fromDataItem(ev.getDataItem()).getDataMap().toBundle();
b.putLong(UPDATE_TIME, System.currentTimeMillis());
System.err.println("setHeaders(): b=" + b);
headers.set(b);
} else {
headers.set(null);
resetState();
}
}
static TrackerState getTrackerStateFromDataItem(DataItem dataItem) {
if (!dataItem.isDataValid())
return null;
Bundle b = DataMap.fromByteArray(dataItem.getData()).toBundle();
if (b.containsKey(Constants.Wear.TrackerState.STATE)) {
return TrackerState.valueOf(b.getInt(Constants.Wear.TrackerState.STATE));
}
return null;
}
private void setTrackerState(DataEvent ev) {
TrackerState newVal = null;
if (ev.getType() == DataEvent.TYPE_CHANGED) {
newVal = getTrackerStateFromDataItem(ev.getDataItem());
if (newVal == null) {
// This is weird. TrackerState is set to a invalid value...skip out
return;
}
} else if (ev.getType() == DataEvent.TYPE_DELETED) {
// trackerState being deleted
newVal = null;
resetState();
}
setTrackerState(newVal);
}
private void resetState() {
data = null;
headers.set(null);
}
private void setTrackerState(TrackerState newVal) {
trackerState.set(newVal);
}
public TrackerState getTrackerState() {
return trackerState.get();
}
public void registerTrackerStateListener(ValueModel.ChangeListener<TrackerState> listener) {
trackerState.registerChangeListener(listener);
}
public void unregisterTrackerStateListener(ValueModel.ChangeListener<TrackerState> listener) {
trackerState.unregisterChangeListener(listener);
}
public void registerHeadersListener(MainActivity listener) {
this.headersListener = listener;
}
public void unregisterHeadersListener(MainActivity listener) {
this.headersListener = null;
}
public void sendStart() {
if (!checkConnection())
return;
Wearable.MessageApi.sendMessage(mGoogleApiClient, phoneNode,
Constants.Wear.Path.MSG_CMD_WORKOUT_START, null);
}
public void sendPauseResume() {
if (!checkConnection())
return;
if (getTrackerState() == TrackerState.STARTED) {
Wearable.MessageApi.sendMessage(mGoogleApiClient, phoneNode,
Constants.Wear.Path.MSG_CMD_WORKOUT_PAUSE, null);
} else if (getTrackerState() == TrackerState.PAUSED) {
Wearable.MessageApi.sendMessage(mGoogleApiClient, phoneNode,
Constants.Wear.Path.MSG_CMD_WORKOUT_RESUME, null);
}
}
public void sendNewLap() {
if (!checkConnection())
return;
Wearable.MessageApi.sendMessage(mGoogleApiClient, phoneNode,
Constants.Wear.Path.MSG_CMD_WORKOUT_NEW_LAP, null);
}
}