package com.eveningoutpost.dexdrip.wearintegration;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;

import com.eveningoutpost.dexdrip.BestGlucose;
import com.eveningoutpost.dexdrip.BuildConfig;
import com.eveningoutpost.dexdrip.G5Model.CalibrationState;
import com.eveningoutpost.dexdrip.G5Model.Ob1G5StateMachine;
import com.eveningoutpost.dexdrip.Home;
import com.eveningoutpost.dexdrip.Models.ActiveBluetoothDevice;
import com.eveningoutpost.dexdrip.Models.AlertType;
import com.eveningoutpost.dexdrip.Models.BgReading;
import com.eveningoutpost.dexdrip.Models.BloodTest;
import com.eveningoutpost.dexdrip.Models.Calibration;
import com.eveningoutpost.dexdrip.Models.HeartRate;
import com.eveningoutpost.dexdrip.Models.JoH;
import com.eveningoutpost.dexdrip.Models.Sensor;
import com.eveningoutpost.dexdrip.Models.StepCounter;
import com.eveningoutpost.dexdrip.Models.TransmitterData;
import com.eveningoutpost.dexdrip.Models.Treatments;
import com.eveningoutpost.dexdrip.Models.UserError;
import com.eveningoutpost.dexdrip.R;
import com.eveningoutpost.dexdrip.Services.G5CollectionService;
import com.eveningoutpost.dexdrip.Services.Ob1G5CollectionService;
import com.eveningoutpost.dexdrip.UtilityModels.AlertPlayer;
import com.eveningoutpost.dexdrip.UtilityModels.BgGraphBuilder;
import com.eveningoutpost.dexdrip.UtilityModels.BgSendQueue;
import com.eveningoutpost.dexdrip.UtilityModels.Blukon;
import com.eveningoutpost.dexdrip.UtilityModels.CollectionServiceStarter;
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
import com.eveningoutpost.dexdrip.UtilityModels.Inevitable;
import com.eveningoutpost.dexdrip.UtilityModels.LowPriorityThread;
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
import com.eveningoutpost.dexdrip.UtilityModels.StatusLine;
import com.eveningoutpost.dexdrip.UtilityModels.WearSyncBooleans;
import com.eveningoutpost.dexdrip.UtilityModels.WearSyncPersistentStrings;
import com.eveningoutpost.dexdrip.utils.CheckBridgeBattery;
import com.eveningoutpost.dexdrip.utils.DexCollectionType;
import com.eveningoutpost.dexdrip.utils.GetWearApk;
import com.eveningoutpost.dexdrip.utils.PowerStateReceiver;
import com.eveningoutpost.dexdrip.xdrip;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.Channel;
import com.google.android.gms.wearable.ChannelApi;
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.DataMap;
import com.google.android.gms.wearable.DataMapItem;
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.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.internal.bind.DateTypeAdapter;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.eveningoutpost.dexdrip.G5Model.Ob1G5StateMachine.PREF_QUEUE_DRAINED;
import static com.eveningoutpost.dexdrip.Models.JoH.showNotification;
import static com.eveningoutpost.dexdrip.Models.JoH.ts;

@SuppressLint("LogNotTimber")
public class WatchUpdaterService extends WearableListenerService implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
    public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend");
    public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
    public static final String ACTION_SYNC_DB = WatchUpdaterService.class.getName().concat(".SyncDB");//KS
    public static final String ACTION_RESET_DB = WatchUpdaterService.class.getName().concat(".ResetDB");//KS
    public static final String ACTION_SYNC_LOGS = WatchUpdaterService.class.getName().concat(".SyncLogs");//KS
    public static final String ACTION_CLEAR_LOGS = WatchUpdaterService.class.getName().concat(".ClearLogs");//KS
    public static final String ACTION_STATUS_COLLECTOR = WatchUpdaterService.class.getName().concat(".StatusCollector");//KS
    public static final String ACTION_START_COLLECTOR = WatchUpdaterService.class.getName().concat(".StartCollector");//KS
    public static final String ACTION_SYNC_SENSOR = WatchUpdaterService.class.getName().concat(".SyncSensor");//KS
    public static final String ACTION_SYNC_CALIBRATION = WatchUpdaterService.class.getName().concat(".SyncCalibration");//KS
    public static final String ACTION_SEND_TOAST = WatchUpdaterService.class.getName().concat(".SendWearLocalToast");//KS
    public static final String ACTION_SEND_STATUS = WatchUpdaterService.class.getName().concat(".SendStatus");//KS
    public static final String ACTION_SYNC_ACTIVEBTDEVICE = WatchUpdaterService.class.getName().concat(".SyncActiveBtDevice");//KS
    public static final String ACTION_SYNC_ALERTTYPE = WatchUpdaterService.class.getName().concat(".SyncAlertType");
    public final static String ACTION_BLUETOOTH_COLLECTION_SERVICE_UPDATE
            = "com.eveningoutpost.dexdrip.BLUETOOTH_COLLECTION_SERVICE_UPDATE";
    private static final String ACTION_SEND_G5_QUEUE = WatchUpdaterService.class.getName().concat(".SendG5Queue");
    public static final String ACTION_DISABLE_FORCE_WEAR = WatchUpdaterService.class.getName().concat(".DisableForceWear");//KS
    public static final String ACTION_SNOOZE_ALERT = WatchUpdaterService.class.getName().concat(".SnoozeAlert");//KS
    private static final String WEARABLE_DATA_PATH = "/nightscout_watch_data";
    private static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend";
    private static final String OPEN_SETTINGS = "/openwearsettings";
    private static final String NEW_STATUS_PATH = "/sendstatustowear";
    private static final String SYNC_DB_PATH = "/xdrip_plus_syncweardb";//KS
    private static final String RESET_DB_PATH = "/xdrip_plus_resetweardb";//KS
    private static final String SYNC_BGS_PATH = "/xdrip_plus_syncwearbgs";//KS
    private static final String SYNC_BGS_PRECALCULATED_PATH = "/xdrip_plus_syncwearbgs2";
    private static final String SYNC_LOGS_PATH = "/xdrip_plus_syncwearlogs";
    private static final String SYNC_TREATMENTS_PATH = "/xdrip_plus_syncweartreatments";
    private static final String SYNC_LOGS_REQUESTED_PATH = "/xdrip_plus_syncwearlogsrequested";
    private static final String SYNC_STEP_SENSOR_PATH = "/xdrip_plus_syncwearstepsensor";
    private static final String SYNC_HEART_SENSOR_PATH = "/xdrip_plus_syncwearheartsensor";
    private static final String CLEAR_LOGS_PATH = "/xdrip_plus_clearwearlogs";
    private static final String CLEAR_TREATMENTS_PATH = "/xdrip_plus_clearweartreatments";
    private static final String STATUS_COLLECTOR_PATH = "/xdrip_plus_statuscollector";
    private static final String START_COLLECTOR_PATH = "/xdrip_plus_startcollector";
    private static final String WEARABLE_REPLYMSG_PATH = "/xdrip_plus_watch_data_replymsg";
    private static final String WEARABLE_INITDB_PATH = "/xdrip_plus_watch_data_initdb";
    public static final String WEARABLE_INITTREATMENTS_PATH = "/xdrip_plus_watch_data_inittreatments";
    private static final String WEARABLE_TREATMENTS_DATA_PATH = "/xdrip_plus_watch_treatments_data";//KS
    private static final String WEARABLE_BLOODTEST_DATA_PATH = "/xdrip_plus_watch_bloodtest_data";//KS
    private static final String WEARABLE_INITPREFS_PATH = "/xdrip_plus_watch_data_initprefs";
    private static final String WEARABLE_LOCALE_CHANGED_PATH = "/xdrip_plus_locale_changed_data";//KS
    private static final String WEARABLE_CALIBRATION_DATA_PATH = "/xdrip_plus_watch_cal_data";//KS
    private static final String WEARABLE_BG_DATA_PATH = "/xdrip_plus_watch_bg_data";//KS
    private static final String WEARABLE_SENSOR_DATA_PATH = "/xdrip_plus_watch_sensor_data";//KS
    private static final String WEARABLE_PREF_DATA_PATH = "/xdrip_plus_watch_pref_data";//KS
    private static final String WEARABLE_ACTIVEBTDEVICE_DATA_PATH = "/xdrip_plus_watch_activebtdevice_data";//KS
    private static final String WEARABLE_ALERTTYPE_DATA_PATH = "/xdrip_plus_watch_alerttype_data";//KS
    private static final String DATA_ITEM_RECEIVED_PATH = "/xdrip_plus_data-item-received";//KS
    private static final String WEARABLE_SNOOZE_ALERT = "/xdrip_plus_snooze_payload";
    public static final String WEARABLE_VOICE_PAYLOAD = "/xdrip_plus_voice_payload";
    public static final String WEARABLE_APPROVE_TREATMENT = "/xdrip_plus_approve_treatment";
    public static final String WEARABLE_CANCEL_TREATMENT = "/xdrip_plus_cancel_treatment";
    private static final String WEARABLE_TREATMENT_PAYLOAD = "/xdrip_plus_treatment_payload";
    private static final String WEARABLE_TOAST_NOTIFICATON = "/xdrip_plus_toast";
    private static final String WEARABLE_TOAST_LOCAL_NOTIFICATON = "/xdrip_plus_local_toast";
    private static final String WEARABLE_REQUEST_APK = "/xdrip_plus_can_i_has_apk";
    private static final String WEARABLE_APK_DELIVERY = "/xdrip_plus_here_is_apk";
    private static final String WEARABLE_G5_QUEUE_PATH = "/xdrip_plus_watch_g5_queue";
    public static final String WEARABLE_G5BATTERY_PAYLOAD = "/xdrip_plus_battery_payload";
    private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
    private static final String LAST_RECORD_TIMESTAMP = "wear-sync-last-treatment-record-ts";
    private static String localnode = "";
    private volatile String mWearNodeId = null;
    static final int GET_CAPABILITIES_TIMEOUT_MS = 5000;

    private static final String TAG = "jamorham watchupdater";
    private static final String LAST_WATCH_RECEIVED_TEXT = "watch-last-received-text";
    private static GoogleApiClient googleApiClient;
    private static long lastRequest = 0;//KS
    private static final Integer sendTreatmentsCount = 60;//KS
    private static final Integer sendCalibrationCount = 3;//KS
    private final static Integer sendBgCount = 4;//KS
    private boolean wear_integration = false;
    private boolean pebble_integration = false;
    private boolean is_using_bt = false;
    private static long syncLogsRequested = 0;//KS
    private static byte[] apkBytes;
    private SharedPreferences mPrefs;
    private SharedPreferences.OnSharedPreferenceChangeListener mPreferencesListener;

    public synchronized static void receivedText(Context context, String text) {
        if (text.equals(PersistentStore.getString(LAST_WATCH_RECEIVED_TEXT))) {
            Log.e(TAG, "Received text is same as previous, ignoring: " + text);
            return;
        }
        PersistentStore.setString(LAST_WATCH_RECEIVED_TEXT, text);
        startHomeWithExtra(context, WEARABLE_VOICE_PAYLOAD, text);
    }

    private static void approveTreatment(Context context, String text) {
        startHomeWithExtra(context, WEARABLE_APPROVE_TREATMENT, text);
    }

    private static void cancelTreatment(Context context, String text) {
        startHomeWithExtra(context, WEARABLE_CANCEL_TREATMENT, text);
    }

    private static void startHomeWithExtra(Context context, String extra, String text) {
        Intent intent = new Intent(context, Home.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(extra, text);
        context.startActivity(intent);
    }

    private void sendDataReceived(String path, String notification, long timeOfLastEntry, String type, long watch_syncLogsRequested) {//KS
        Log.d(TAG, "sendDataReceived timeOfLastEntry=" + JoH.dateTimeText(timeOfLastEntry) + " Path=" + path);
        forceGoogleApiConnect();
        if (googleApiClient.isConnected()) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);
            dataMapRequest.setUrgent();
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putLong("timeOfLastEntry", timeOfLastEntry);
            dataMapRequest.getDataMap().putLong("syncLogsRequested", watch_syncLogsRequested);
            dataMapRequest.getDataMap().putString("type", type);
            dataMapRequest.getDataMap().putString("msg", notification);
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e(TAG, "sendDataReceived No connection to wearable available!");
        }
    }

    private void syncFieldData(DataMap dataMap) {
        String dex_txid = dataMap.getString("dex_txid", "");
        byte[] G5_BATTERY_MARKER = dataMap.getByteArray(G5CollectionService.G5_BATTERY_MARKER);
        byte[] G5_FIRMWARE_MARKER = dataMap.getByteArray(G5CollectionService.G5_FIRMWARE_MARKER);
        if (dex_txid != null && dex_txid.equals(mPrefs.getString("dex_txid", "default"))) {
            if (G5_BATTERY_MARKER != null) {
                long watch_last_battery_query = dataMap.getLong(G5CollectionService.G5_BATTERY_FROM_MARKER);
                long phone_last_battery_query = PersistentStore.getLong(G5CollectionService.G5_BATTERY_FROM_MARKER + dex_txid);
                if (watch_last_battery_query > phone_last_battery_query) {
                    G5CollectionService.setStoredBatteryBytes(dex_txid, G5_BATTERY_MARKER);
                    PersistentStore.setLong(G5CollectionService.G5_BATTERY_FROM_MARKER + dex_txid, watch_last_battery_query);
                    G5CollectionService.getBatteryStatusNow = false;
                    Ob1G5CollectionService.getBatteryStatusNow = false;
                }
            }
            if (G5_FIRMWARE_MARKER != null) {
                G5CollectionService.setStoredFirmwareBytes(dex_txid, G5_FIRMWARE_MARKER);
            }
        }
    }

    public static void checkOb1Queue() {
        boolean enable_wearG5 = Pref.getBoolean("enable_wearG5", false);
        if (enable_wearG5) {
            if (Ob1G5CollectionService.usingNativeMode()) {
                final String ob1QueueJson = Ob1G5StateMachine.extractQueueJson();
                if (ob1QueueJson != null) {
                    xdrip.getAppContext().startService(new Intent(xdrip.getAppContext(), WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_SEND_G5_QUEUE).putExtra("queueData", ob1QueueJson));
                }
            }
        }
    }

    private void syncPrefData(DataMap dataMap) {
        boolean enable_wearG5 = dataMap.getBoolean("enable_wearG5", false);
        boolean force_wearG5 = dataMap.getBoolean("force_wearG5", false);
        String node_wearG5 = dataMap.getString("node_wearG5", "");
        String dex_txid = dataMap.getString("dex_txid", "");
        int bridge_battery = dataMap.getInt("bridge_battery", -1);//Used in DexCollectionService
        int nfc_sensor_age = dataMap.getInt("nfc_sensor_age", -1);//Used in DexCollectionService LimiTTer
        boolean bg_notifications_watch = dataMap.getBoolean("bg_notifications_watch", false);
        boolean persistent_high_alert_enabled_watch = dataMap.getBoolean("persistent_high_alert_enabled_watch", false);
        boolean show_wear_treatments = dataMap.getBoolean("show_wear_treatments", false);

        boolean change = false;

        SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit();
        Log.d(TAG, "syncPrefData enable_wearG5: " + enable_wearG5 + " force_wearG5: " + force_wearG5 + " node_wearG5:" + node_wearG5 + " dex_txid: " + dex_txid);

        if (bridge_battery != mPrefs.getInt("bridge_battery", -1)) {//Used by DexCollectionService
            prefs.putInt("bridge_battery", bridge_battery);
            prefs.apply();
            Log.d(TAG, "syncPrefData commit bridge_battery: " + bridge_battery);
            CheckBridgeBattery.checkBridgeBattery();
            if (force_wearG5 && CheckBridgeBattery.checkForceWearBridgeBattery()) {
                force_wearG5 = false;
                change = true;
                Log.d(TAG, "syncPrefData disable_wearG5_on_lowbattery=true; switch force_wearG5:" + force_wearG5);
                String msg = getResources().getString(R.string.notify_when_wear_low_battery);
                JoH.static_toast_long(msg);
                sendWearLocalToast(msg, Toast.LENGTH_LONG);
            }
        }

        if (!node_wearG5.equals(mPrefs.getString("node_wearG5", ""))) {
            change = true;
            prefs.putString("node_wearG5", node_wearG5);
            Log.d(TAG, "syncPrefData node_wearG5:" + node_wearG5);
        }

        if (/*force_wearG5 &&*/ force_wearG5 != mPrefs.getBoolean("force_wearG5", false)) {
            change = true;
            prefs.putBoolean("force_wearG5", force_wearG5);
            Log.d(TAG, "syncPrefData commit force_wearG5:" + force_wearG5);
            if (force_wearG5) {
                final PendingIntent pendingIntent = android.app.PendingIntent.getActivity(xdrip.getAppContext(), 0, new Intent(xdrip.getAppContext(), Home.class), android.app.PendingIntent.FLAG_UPDATE_CURRENT);
                showNotification("Force Wear Enabled", node_wearG5 + " Watch has enabled Force Wear Collection Service", pendingIntent, 771, true, true, true);
            }
        }

        if (nfc_sensor_age != mPrefs.getInt("nfc_sensor_age", 0)) {//Used by DexCollectionService
            change = true;
            prefs.putInt("nfc_sensor_age", nfc_sensor_age);
            Log.d(TAG, "syncPrefData commit nfc_sensor_age: " + nfc_sensor_age);
        }

        if (bg_notifications_watch != mPrefs.getBoolean("bg_notifications_watch", false)) {
            change = true;
            prefs.putBoolean("bg_notifications_watch", bg_notifications_watch);
            Log.d(TAG, "syncPrefData commit bg_notifications_watch: " + bg_notifications_watch);
        }
        PersistentStore.setBoolean("bg_notifications_watch", bg_notifications_watch);
        PersistentStore.setBoolean("persistent_high_alert_enabled_watch", persistent_high_alert_enabled_watch);

        if (/*enable_wearG5 &&*/ enable_wearG5 != mPrefs.getBoolean("enable_wearG5", false)) {
            change = true;
            prefs.putBoolean("enable_wearG5", enable_wearG5);
            Log.d(TAG, "syncPrefData commit enable_wearG5: " + enable_wearG5);
        }

        if (show_wear_treatments != mPrefs.getBoolean("show_wear_treatments", false)) {
            change = true;
            prefs.putBoolean("show_wear_treatments", show_wear_treatments);
            Log.d(TAG, "syncPrefData show_wear_treatments:" + show_wear_treatments);
        }

        if (change) {
            prefs.apply();
        } else if (!dex_txid.equals(mPrefs.getString("dex_txid", "default"))) {
            sendPrefSettings();
            processConnect();
        }
    }

    //Assumes Wear is connected to phone
    private void processConnect() {//KS
        Log.d(TAG, "processConnect enter");
        wear_integration = mPrefs.getBoolean("wear_sync", false);
        boolean enable_wearG5 = mPrefs.getBoolean("enable_wearG5", false);
        boolean force_wearG5 = mPrefs.getBoolean("force_wearG5", false);

        if (wear_integration) {
            initWearData();
            if (enable_wearG5) {
                if (force_wearG5) {
                    Log.d(TAG, "processConnect force_wearG5=true - stopBtService");
                    stopBtService();
                } else {
                    Log.d(TAG, "processConnect force_wearG5=false - startBtService");
                    startBtService();
                }
            } else {
                Log.d(TAG, "processConnect enable_wearG5=false - startBtService");
                startBtService();
                if (mPrefs.getBoolean("show_wear_treatments", false))
                    initWearTreatments();
            }
        } else {
            Log.d(TAG, "processConnect wear_integration=false - startBtService");
            startBtService();
        }
    }

    private synchronized void syncBgReadingsData(DataMap dataMap) {
        Log.d(TAG, "sync-precalculated-bg-readings-Data");

        final int calibration_state = dataMap.getInt("native_calibration_state", 0);
        Ob1G5CollectionService.processCalibrationState(CalibrationState.parse(calibration_state));
        Ob1G5StateMachine.injectDexTime(dataMap.getString("dextime", null));

        final boolean queue_drained = dataMap.getBoolean(PREF_QUEUE_DRAINED);
        if (queue_drained) {
            Ob1G5StateMachine.emptyQueue();
        }


        final ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
        if (entries != null) {
            final Gson gson = new GsonBuilder()
                    .excludeFieldsWithoutExposeAnnotation()
                    .registerTypeAdapter(Date.class, new DateTypeAdapter())
                    .serializeSpecialFloatingPointValues()
                    .create();


            final int count = entries.size();

            if (count > 0) {

                final Sensor current_sensor = Sensor.currentSensor();
                if (current_sensor == null) {
                    UserError.Log.e(TAG, "Cannot sync wear BG readings because sensor is marked stopped on phone");
                    return;
                }

                Log.d(TAG, "syncTransmitterData add BgReading Table entries count=" + count);
                int idx = 0;
                long timeOfLastBG = 0;
                for (DataMap entry : entries) {
                    if (entry != null) {
                        idx++;
                        final String bgrecord = entry.getString("bgs");
                        if (bgrecord != null) {

                            final BgReading bgData = gson.fromJson(bgrecord, BgReading.class);

                            final BgReading uuidexists = BgReading.findByUuid(bgData.uuid);
                            if (uuidexists == null) {

                                final BgReading exists = BgReading.getForTimestamp(bgData.timestamp);
                                if (exists == null) {
                                    Log.d(TAG, "Saving new synced pre-calculated bg-reading: " + JoH.dateTimeText(bgData.timestamp) + " last entry: " + (idx == count) + " " + BgGraphBuilder.unitized_string_static(bgData.calculated_value));
                                    bgData.sensor = current_sensor;
                                    bgData.save();
                                    BgSendQueue.handleNewBgReading(bgData, "create", xdrip.getAppContext(), Home.get_follower(), idx != count);
                                } else {
                                    Log.d(TAG, "BgReading for timestamp already exists: " + JoH.dateTimeText(bgData.timestamp));
                                }
                            } else {
                                Log.d(TAG, "BgReading with uuid: " + bgData.uuid + " already exists: " + JoH.dateTimeText(bgData.timestamp));
                            }

                            timeOfLastBG = Math.max(bgData.timestamp + 1, timeOfLastBG);
                        }
                    }
                }
                sendDataReceived(DATA_ITEM_RECEIVED_PATH, "DATA_RECEIVED_BGS count=" + entries.size(), timeOfLastBG, "BG", -1);

            } else {
                UserError.Log.e(TAG, "Not acknowledging wear BG readings as count was 0");
            }
        } else {
            UserError.Log.d(TAG, "Null entries list - should only happen with native status update only");
        }
    }

    private synchronized void syncTransmitterData(DataMap dataMap, boolean bBenchmark) {//KS
        Log.d(TAG, "syncTransmitterData");

        ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
        long timeOfLastBG = 0;
        Log.d(TAG, "syncTransmitterData add BgReading Table");
        if (entries != null) {

            Gson gson = new GsonBuilder()
                    .excludeFieldsWithoutExposeAnnotation()
                    .registerTypeAdapter(Date.class, new DateTypeAdapter())
                    .serializeSpecialFloatingPointValues()
                    .create();

            int idx = 0;
            int count = entries.size();
            Log.d(TAG, "syncTransmitterData add BgReading Table entries count=" + count);
            for (DataMap entry : entries) {
                if (entry != null) {
                    //Log.d(TAG, "syncTransmitterData add BgReading Table entry=" + entry);
                    idx++;
                    String bgrecord = entry.getString("bgs");
                    if (bgrecord != null) {//for (TransmitterData bgData : bgs) {
                        //Log.d(TAG, "syncTransmitterData add TransmitterData Table bgrecord=" + bgrecord);
                        TransmitterData bgData = gson.fromJson(bgrecord, TransmitterData.class);
                        //TransmitterData bgData = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(bgrecord, TransmitterData.class);
                        TransmitterData exists = TransmitterData.getForTimestamp(bgData.timestamp);
                        TransmitterData uuidexists = TransmitterData.findByUuid(bgData.uuid);
                        timeOfLastBG = bgData.timestamp + 1;
                        if (exists != null || uuidexists != null) {
                            Log.d(TAG, "syncTransmitterData BG already exists for uuid=" + bgData.uuid + " timestamp=" + bgData.timestamp + " timeString=" + JoH.dateTimeText(bgData.timestamp) + " raw_data=" + bgData.raw_data);
                        } else {
                            Log.d(TAG, "syncTransmitterData add BG; does NOT exist for uuid=" + bgData.uuid + " timestamp=" + bgData.timestamp + " timeString=" + JoH.dateTimeText(bgData.timestamp) + " raw_data=" + bgData.raw_data);
                            if (!bBenchmark) {
                                bgData.save();

                                //Check
                                if (TransmitterData.findByUuid(bgData.uuid) != null)
                                    Log.d(TAG, "syncTransmitterData: TransmitterData was saved for uuid:" + bgData.uuid);
                                else {
                                    Log.e(TAG, "syncTransmitterData: TransmitterData was NOT saved for uuid:" + bgData.uuid);
                                    return;
                                }

                                //KS the following is from G5CollectionService processNewTransmitterData()
                                Sensor sensor = Sensor.currentSensor();
                                if (sensor == null) {
                                    Log.e(TAG, "syncTransmitterData: No Active Sensor, Data only stored in Transmitter Data");
                                    return;
                                }
                                //TODO : LOG if unfiltered or filtered values are zero
                                Sensor.updateBatteryLevel(sensor, bgData.sensor_battery_level);
                                Log.i(TAG, "syncTransmitterData: BG timestamp create " + Long.toString(bgData.timestamp));//android.util.Log.i
                                BgReading bgExists;

                                //KS TODO wear implements limited alerts, therefore continue to process all alerts on phone for last entry
                                if (count > 1 && idx < count) {
                                    bgExists = BgReading.create(bgData.raw_data, bgData.filtered_data, this, bgData.timestamp, true);//Disable Notifications for bulk insert
                                } else {
                                    bgExists = BgReading.create(bgData.raw_data, bgData.filtered_data, this, bgData.timestamp);
                                }
                                if (bgExists != null)
                                    Log.d(TAG, "syncTransmitterData BG GSON saved BG: " + bgExists.toS());
                                else
                                    Log.e(TAG, "syncTransmitterData BG GSON NOT saved");
                            }
                        }
                    }
                }
            }
            sendDataReceived(DATA_ITEM_RECEIVED_PATH, "DATA_RECEIVED_BGS count=" + entries.size(), timeOfLastBG, bBenchmark ? "BM" : "BG", -1);
        }
    }

    private synchronized void syncLogData(DataMap dataMap, boolean bBenchmark) {//KS
        Log.d(TAG, "syncLogData");
        long watch_syncLogsRequested = dataMap.getLong("syncLogsRequested", -1);
        ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
        long timeOfLastEntry = 0;
        int saved = 0;
        if (entries != null) {

            Gson gson = new GsonBuilder()
                    .excludeFieldsWithoutExposeAnnotation()
                    .registerTypeAdapter(Date.class, new DateTypeAdapter())
                    .serializeSpecialFloatingPointValues()
                    .create();

            Log.d(TAG, "syncLogData add Table entries count=" + entries.size() + " watch_syncLogsRequested=" + watch_syncLogsRequested);
            for (DataMap entry : entries) {
                if (entry != null) {
                    String record = entry.getString("entry");
                    if (record != null) {
                        UserError data = gson.fromJson(record, UserError.class);
                        if (data != null) {
                            timeOfLastEntry = (long) data.timestamp + 1;
                            if (data.shortError != null && !data.shortError.isEmpty()) { //add wear prefix
                                if (!data.shortError.startsWith("wear")) {
                                    data.shortError = mPrefs.getString("wear_logs_prefix", "wear") + data.shortError;
                                }
                            }
                            UserError exists = UserError.getForTimestamp(data);
                            if (exists == null && !bBenchmark) {
                                data.save();
                                saved++;
                            } else {
                                //Log.d(TAG, "syncLogData Log entry already exists with shortError=" + data.shortError + " timestamp=" + JoH.dateTimeText((long)data.timestamp));
                            }
                        }
                    }
                }
            }
            if (saved > 0) {
                Log.d(TAG, "syncLogData Saved timeOfLastEntry=" + JoH.dateTimeText(timeOfLastEntry) + " saved=" + saved);
            } else {
                Log.d(TAG, "syncLogData No records saved due to being duplicates! timeOfLastEntry=" + JoH.dateTimeText(timeOfLastEntry) + " count=" + entries.size());
            }
            sendDataReceived(DATA_ITEM_RECEIVED_PATH, "DATA_RECEIVED_LOGS count=" + entries.size(), timeOfLastEntry, bBenchmark ? "BM" : "LOG", watch_syncLogsRequested);
        }
    }

    private synchronized void syncStepSensorData(DataMap dataMap, boolean bBenchmark) {
        Log.d(TAG, "syncStepSensorData");

        ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
        long timeOfLastEntry = 0;
        Log.d(TAG, "syncStepSensorData add to Table");
        if (entries != null) {

            Gson gson = new GsonBuilder()
                    .excludeFieldsWithoutExposeAnnotation()
                    .registerTypeAdapter(Date.class, new DateTypeAdapter())
                    .serializeSpecialFloatingPointValues()
                    .create();

            StepCounter pm = StepCounter.last();
            Log.d(TAG, "syncStepSensorData add Table entries count=" + entries.size());
            for (DataMap entry : entries) {
                if (entry != null) {
                    Log.d(TAG, "syncStepSensorData add Table entry=" + entry);
                    String record = entry.getString("entry");
                    if (record != null) {
                        Log.d(TAG, "syncStepSensorData add Table record=" + record);
                        StepCounter data = gson.fromJson(record, StepCounter.class);
                        if (data != null) {
                            timeOfLastEntry = (long) data.timestamp + 1;
                            Log.d(TAG, "syncStepSensorData add Entry Wear=" + data.toString());
                            Log.d(TAG, "syncStepSensorData WATCH data.metric=" + data.metric + " timestamp=" + JoH.dateTimeText((long) data.timestamp));
                            if (!bBenchmark)
                                data.saveit();
                        }
                    }
                }
            }
            sendDataReceived(DATA_ITEM_RECEIVED_PATH, "DATA_RECEIVED_LOGS count=" + entries.size(), timeOfLastEntry, bBenchmark ? "BM" : "STEP", -1);
        }
    }

    private synchronized void syncHeartSensorData(DataMap dataMap, boolean bBenchmark) {
        Log.d(TAG, "syncHeartSensorData");

        ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
        long timeOfLastEntry = 0;
        Log.d(TAG, "syncHeartSensorData add to Table");
        if (entries != null) {

            final Gson gson = JoH.defaultGsonInstance();

            //final HeartRate pm = HeartRate.last();
            Log.d(TAG, "syncHeartSensorData add Table entries count=" + entries.size());
            for (DataMap entry : entries) {
                if (entry != null) {
                    Log.d(TAG, "syncHeartSensorData add Table entry=" + entry);
                    String record = entry.getString("entry");
                    if (record != null) {
                        Log.d(TAG, "syncHeartSensorData add Table record=" + record);
                        final HeartRate data = gson.fromJson(record, HeartRate.class);
                        if (data != null) {
                            timeOfLastEntry = (long) data.timestamp + 1;
                            Log.d(TAG, "syncHeartSensorData add Entry Wear=" + data.toString() + " " + record);
                            Log.d(TAG, "syncHeartSensorData WATCH data.metric=" + data.bpm + " timestamp=" + JoH.dateTimeText((long) data.timestamp));
                            if (!bBenchmark)
                                data.saveit();
                        }
                    }
                }
            }
            sendDataReceived(DATA_ITEM_RECEIVED_PATH, "DATA_RECEIVED_LOGS count=" + entries.size(), timeOfLastEntry, bBenchmark ? "BM" : "HEART", -1);
        }
    }


    private synchronized void syncTreatmentsData(DataMap dataMap, boolean bBenchmark) {
        Log.d(TAG, "syncTreatmentsData");

        ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
        long timeOfLastEntry = 0;
        if (entries != null) {
            Log.d(TAG, "syncTreatmentsData count=" + entries.size());
            for (DataMap entry : entries) {
                if (entry != null) {
                    Log.d(TAG, "syncTreatmentsData entry=" + entry);
                    String record = entry.getString("entry");
                    if (record != null && record.length() > 1) {
                        Log.d(TAG, "Received wearable 2: voice payload: " + record);
                        long timestamp = entry.getLong("timestamp");
                        if (timestamp <= PersistentStore.getLong(LAST_RECORD_TIMESTAMP)) {
                            Log.e(TAG, "Ignoring repeated or older sync timestamp");
                            continue;
                        }
                        final long since = JoH.msSince(timestamp);
                        if ((since < -(Constants.SECOND_IN_MS * 5)) || (since > Constants.HOUR_IN_MS * 72)) {
                            JoH.static_toast_long("Rejecting wear treatment as time out of range!");
                            UserError.Log.e(TAG, "Rejecting wear treatment due to time: " + record + " since: " + since);
                        } else {
                            if (record.contains("uuid null")) {
                                Log.e(TAG, "Skipping xx uuid null record!");
                                continue;
                            }
                            receivedText(getApplicationContext(), record);
                            PersistentStore.setLong(LAST_RECORD_TIMESTAMP, timestamp);
                        }
                        Log.d(TAG, "syncTreatmentsData add Table record=" + record);
                        timeOfLastEntry = (long) timestamp + 1;
                        Log.d(TAG, "syncTreatmentsData WATCH treatments timestamp=" + JoH.dateTimeText(timestamp));
                    }
                }
            }
            sendDataReceived(DATA_ITEM_RECEIVED_PATH, "DATA_RECEIVED_LOGS count=" + entries.size(), timeOfLastEntry, bBenchmark ? "BM" : "TREATMENTS", -1);
        }
    }

    public static void sendWearToast(String msg, int length) {
        if ((googleApiClient != null) && (googleApiClient.isConnected())) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WEARABLE_TOAST_NOTIFICATON);
            dataMapRequest.setUrgent();
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putInt("length", length);
            dataMapRequest.getDataMap().putString("msg", msg);
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e(TAG, "No connection to wearable available for toast! " + msg);
        }
    }

    public static void sendWearLocalToast(String msg, int length) {
        if ((googleApiClient != null) && (googleApiClient.isConnected())) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WEARABLE_TOAST_LOCAL_NOTIFICATON);
            dataMapRequest.setUrgent();
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putInt("length", length);
            dataMapRequest.getDataMap().putString("msg", msg);
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e(TAG, "No connection to wearable available for toast! " + msg);
        }
    }

    public static void sendTreatment(double carbs, double insulin, double bloodtest, String injectionJSON, double timeoffset, String timestring) {
        if ((googleApiClient != null) && (googleApiClient.isConnected())) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WEARABLE_TREATMENT_PAYLOAD);
            //unique content
            dataMapRequest.setUrgent();
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putDouble("carbs", carbs);
            dataMapRequest.getDataMap().putDouble("insulin", insulin);
            dataMapRequest.getDataMap().putDouble("bloodtest", bloodtest);
            dataMapRequest.getDataMap().putDouble("timeoffset", timeoffset);
            dataMapRequest.getDataMap().putString("timestring", timestring);
            dataMapRequest.getDataMap().putString("injectionJSON", injectionJSON);
            dataMapRequest.getDataMap().putBoolean("ismgdl", doMgdl(PreferenceManager.getDefaultSharedPreferences(xdrip.getAppContext())));
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e(TAG, "No connection to wearable available for send treatment!");
        }
    }

    private static boolean doMgdl(SharedPreferences sPrefs) {
        String unit = sPrefs.getString("units", "mgdl");
        if (unit.compareTo("mgdl") == 0) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void onCreate() {
        mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        wear_integration = mPrefs.getBoolean("wear_sync", false);
        //is_using_g5 = (getDexCollectionType() == DexCollectionType.DexcomG5);
        is_using_bt = DexCollectionType.hasBluetooth();
        if (wear_integration) {
            googleApiConnect();
        }
        setSettings();
        listenForChangeInSettings();
    }

    private void listenForChangeInSettings() {
        mPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {

                pebble_integration = mPrefs.getBoolean("pebble_sync", false);
                if (key.compareTo("bridge_battery") != 0 && key.compareTo("nfc_sensor_age") != 0 &&
                        key.compareTo("bg_notifications_watch") != 0 && key.compareTo("persistent_high_alert_enabled_watch") != 0) {

                    Log.d(TAG, "Triggering Wear Settings Update due to key=" + key);

                    Inevitable.task("wear-update-settings", 2000, () -> {
                        sendPrefSettings();
                        processConnect();
                    });

                }
            }
        };
        mPrefs.registerOnSharedPreferenceChangeListener(mPreferencesListener);
    }

    private void setSettings() {
        Log.d(TAG, "setSettings enter");
        pebble_integration = mPrefs.getBoolean("pebble_sync", false);
        processConnect();
        if (wear_integration) {
            if (googleApiClient == null) googleApiConnect();
            Log.d(TAG, "setSettings wear_sync changed to True.");
            sendPrefSettings();
        }
    }

    private void googleApiConnect() {
        if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) {
            googleApiClient.disconnect();
        }
        googleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Wearable.API)
                .build();
        Wearable.MessageApi.addListener(googleApiClient, this);
        if (googleApiClient.isConnected()) {
            Log.d("WatchUpdater", "API client is connected");
        } else {
            googleApiClient.connect();
        }
    }

    private void forceGoogleApiConnect() {
        if ((googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) || googleApiClient == null) {
            try {
                Log.d(TAG, "forceGoogleApiConnect: forcing google api reconnection");
                googleApiConnect();
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Log.d(TAG, "forceGoogleApiConnect: exception:" + e);
            }
        }
    }

    @Override
    public void onPeerConnected(com.google.android.gms.wearable.Node peer) {//KS onPeerConnected and onPeerDisconnected deprecated at the same time as BIND_LISTENER

        super.onPeerConnected(peer);
        String id = peer.getId();
        String name = peer.getDisplayName();
        Log.d(TAG, "onPeerConnected peer name & ID: " + name + "|" + id);
        sendPrefSettings();
        if (mPrefs.getBoolean("enable_wearG5", false)) {//watch_integration
            Log.d(TAG, "onPeerConnected call initWearData for node=" + peer.getDisplayName());
            initWearData();
            //Only stop service if Phone will rely on Wear Collection Service
            if (mPrefs.getBoolean("force_wearG5", false)) {
                Log.d(TAG, "onPeerConnected force_wearG5=true Phone stopBtService and continue to use Wear G5 BT Collector");
                stopBtService();
            } else {
                Log.d(TAG, "onPeerConnected onPeerConnected force_wearG5=false Phone startBtService");
                startBtService();
            }
        }
    }

    @Override
    public void onPeerDisconnected(com.google.android.gms.wearable.Node peer) {//KS onPeerConnected and onPeerDisconnected deprecated at the same time as BIND_LISTENER
        super.onPeerDisconnected(peer);
        String id = peer.getId();
        String name = peer.getDisplayName();
        Log.d(TAG, "onPeerDisconnected peer name & ID: " + name + "|" + id);
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        if (sharedPrefs.getBoolean("watch_integration", false)) {
            Log.d(TAG, "onPeerDisconnected watch_integration=true Phone startBtService");
            startBtService();
        }
    }

    // Custom method to determine whether a service is running
    private boolean isServiceRunning(Class<?> serviceClass) {//Class<?> serviceClass
        if (serviceClass != null) {
            ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            // Loop through the running services
            for (ActivityManager.RunningServiceInfo service : activityManager.getRunningServices(Integer.MAX_VALUE)) {
                //Log.d(TAG, "isServiceRunning: getClassName=" + service.service.getClassName() + " getShortClassName=" + service.service.getShortClassName());
                if (serviceClass.getName().equals(service.service.getClassName())) return true;
            }
        }
        return false;
    }

    private boolean isCollectorRunning() {
        Class<?> serviceClass = DexCollectionType.getCollectorServiceClass();
        if (serviceClass != null) {
            Log.d(TAG, "DexCollectionType.getCollectorServiceClass(): " + serviceClass.getName());
            return isServiceRunning(serviceClass);
        }
        return false;
    }

    private void startBtService() {//KS
        Log.d(TAG, "startBtService");
        is_using_bt = DexCollectionType.hasBluetooth();//(getDexCollectionType() == DexCollectionType.DexcomG5)
        if (is_using_bt) {
            if (!isCollectorRunning()) {
                CollectionServiceStarter.startBtService(getApplicationContext());
                Log.d(TAG, "startBtService startService");
            } else {
                Log.d(TAG, "startBtService collector already running!");
            }
        } else {
            Log.d(TAG, "Not starting any BT Collector service as it is not our data source");
        }
    }

    private void stopBtService() {
        Log.d(TAG, "stopService call stopService");
        CollectionServiceStarter.stopBtService(getApplicationContext());
        Log.d(TAG, "stopBtService should have called onDestroy");
    }

    private void startBtG5Service() {//KS
        Log.d(TAG, "startBtG5Service");
        //is_using_g5 = (getDexCollectionType() == DexCollectionType.DexcomG5);
        is_using_bt = DexCollectionType.hasBluetooth();
        if (is_using_bt) {
            Context myContext = getApplicationContext();
            Log.d(TAG, "startBtG5Service start G5CollectionService");
            myContext.startService(new Intent(myContext, G5CollectionService.class));
            Log.d(TAG, "startBtG5Service AFTER startService G5CollectionService");
        } else {
            Log.d(TAG, "Not starting any G5 service as it is not our data source");
        }
    }

    private void stopBtG5Service() {//KS
        Log.d(TAG, "stopBtG5Service");
        Context myContext = getApplicationContext();
        myContext.stopService(new Intent(myContext, G5CollectionService.class));
    }

    public static void startSelf() {
        Inevitable.task("wear-startself", 2000, () -> {
            if (JoH.ratelimit("start-wear", 5)) {
                startServiceAndResendData(0);
            }
        });
    }

    public static void startServiceAndResendData(long since) {
        UserError.Log.d(TAG, "Requesting to resend data");
        xdrip.getAppContext().startService(new Intent(xdrip.getAppContext(), WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_RESEND).putExtra("resend-since", since));
    }

    public static void startServiceAndResendDataIfNeeded(final long since) {
        if (isEnabled()) {
            if (JoH.ratelimit("wear-resend-data", 60)) {
                startServiceAndResendData(since);
            }
        }
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        final PowerManager.WakeLock wl = JoH.getWakeLock("watchupdate-onstart", 60000);
        wear_integration = mPrefs.getBoolean("wear_sync", false);

        String action = null;
        if (intent != null) {
            action = intent.getAction();
        }

        if (wear_integration) {
            is_using_bt = DexCollectionType.hasBluetooth();//(getDexCollectionType() == DexCollectionType.DexcomG5)
            if (googleApiClient != null) {
                if (googleApiClient.isConnected()) {
                    if (ACTION_RESEND.equals(action)) {
                        resendData(intent.getLongExtra("resend-since", 0));
                    } else if (ACTION_OPEN_SETTINGS.equals(action)) {
                        Log.d(TAG, "onStartCommand Action=ACTION_OPEN_SETTINGS");
                        sendNotification(OPEN_SETTINGS, "openSettings");
                    } else if (ACTION_SEND_TOAST.equals(action)) {
                        Log.d(TAG, "onStartCommand Action=ACTION_SEND_TOAST msg=" + intent.getStringExtra("msg"));
                        sendWearLocalToast(intent.getStringExtra("msg"), Toast.LENGTH_LONG);
                    } else if (ACTION_SEND_STATUS.equals(action)) {//KS added for HAPP
                        //https://github.com/StephenBlackWasAlreadyTaken/xDrip-Experimental
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SEND_STATUS + " externalStatusString=" + intent.getStringExtra("externalStatusString"));
                        sendRequestExtra(NEW_STATUS_PATH, "externalStatusString", intent.getStringExtra("externalStatusString"));
                    } else if (ACTION_SNOOZE_ALERT.equals(action)) {
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SNOOZE_ALERT + " repeatTime=" + intent.getStringExtra("repeatTime"));
                        sendRequestExtra(WEARABLE_SNOOZE_ALERT, "repeatTime", intent.getStringExtra("repeatTime"));
                    } else if (ACTION_SYNC_DB.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SYNC_DB + " Path=" + SYNC_DB_PATH);
                        sendNotification(SYNC_DB_PATH, "syncDB");
                        initWearData();
                    } else if (ACTION_RESET_DB.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_RESET_DB + " Path=" + RESET_DB_PATH);
                        sendNotification(RESET_DB_PATH, "resetDB");
                        //TODO Rm!
                        //UserError.cleanup(JoH.tsl());//TODO Rm!
                        //Log.d(TAG, "onStartCommand RESET_DB_PATH cleanup timestamp=" + JoH.dateTimeText(JoH.tsl()));
                        //TODO Rm!
                        initWearData();
                    } else if (ACTION_DISABLE_FORCE_WEAR.equals(action)) {//KS
                        int bg_wear_missed_minutes = readPrefsInt(mPrefs, "disable_wearG5_on_missedreadings_level", 30);
                        Log.d(TAG, "onStartCommand Action=ACTION_DISABLE_FORCE_WEAR");
                        Pref.setBoolean("force_wearG5", false);
                        final String msgDisableWear = getResources().getString(R.string.notify_disable_wearG5_on_missedreadings, bg_wear_missed_minutes);
                        JoH.static_toast_long(msgDisableWear);
                        Log.e(TAG, "wearIsConnected disable force_wearG5:" + Pref.getBooleanDefaultFalse("force_wearG5") + " msg=" + msgDisableWear);
                        sendWearLocalToast(msgDisableWear, Toast.LENGTH_LONG);
                    } else if (ACTION_START_COLLECTOR.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_START_COLLECTOR + " Path=" + START_COLLECTOR_PATH);
                        sendNotification(START_COLLECTOR_PATH, "startCOLLECTOR");
                    } else if (ACTION_STATUS_COLLECTOR.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=ACTION_STATUS_COLLECTOR Path=STATUS_COLLECTOR_PATH getBatteryStatusNow=" + intent.getBooleanExtra("getBatteryStatusNow", false));
                        //sendNotification(STATUS_COLLECTOR_PATH, "statusCOLLECTOR");
                        sendRequestExtra(STATUS_COLLECTOR_PATH, "getBatteryStatusNow", intent.getBooleanExtra("getBatteryStatusNow", false));
                    } else if (ACTION_SYNC_LOGS.equals(action)) {
                        //sendNotification(SYNC_LOGS_PATH, "syncLOG");
                        long rate = (syncLogsRequested == 0 ? 2 : syncLogsRequested * 10);//in seconds
                        Log.d(TAG, "onStartCommand Action ACTION_SYNC_LOGS=ACTION_SYNC_LOGS SYNC_LOGS_PATH syncLogsRequested=" + syncLogsRequested);
                        if (JoH.ratelimit("sync-logs-requested", (int) rate)) {
                            syncLogsRequested++;
                            sendRequestExtra(SYNC_LOGS_PATH, "syncLogsRequested", String.valueOf(syncLogsRequested));
                        }
                        Log.d(TAG, "onStartCommand Action ACTION_SYNC_LOGS=ACTION_SYNC_LOGS SYNC_LOGS_PATH syncLogsRequested=" + syncLogsRequested + " ratelimit=" + rate);
                    } else if (ACTION_CLEAR_LOGS.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_CLEAR_LOGS + " Path=" + CLEAR_LOGS_PATH);
                        sendNotification(CLEAR_LOGS_PATH, "clearLOG");
                    } else if (ACTION_SYNC_SENSOR.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SYNC_SENSOR + " Path=" + WEARABLE_SENSOR_DATA_PATH);
                        sendSensorData();
                    } else if (ACTION_SYNC_ACTIVEBTDEVICE.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SYNC_ACTIVEBTDEVICE + " Path=" + WEARABLE_ACTIVEBTDEVICE_DATA_PATH);
                        sendActiveBtDeviceData();
                    } else if (ACTION_SYNC_ALERTTYPE.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SYNC_ALERTTYPE + " Path=" + WEARABLE_ALERTTYPE_DATA_PATH);
                        sendAlertTypeData();
                    } else if (ACTION_SYNC_CALIBRATION.equals(action)) {//KS
                        Log.d(TAG, "onStartCommand Action=" + ACTION_SYNC_CALIBRATION + " Path=" + WEARABLE_CALIBRATION_DATA_PATH);

                        sendWearCalibrationData(sendCalibrationCount);
                        final boolean adjustPast = mPrefs.getBoolean("rewrite_history", true);
                        Log.d(TAG, "onStartCommand adjustRecentBgReadings for rewrite_history=" + adjustPast);
                        sendWearBgData(adjustPast ? 30 : 2);//wear may not have all BGs if force_wearG5=false, so send BGs from phone
                        sendData();//ensure BgReading.Last is displayed on watch
                    } else if (ACTION_SEND_G5_QUEUE.equals(action)) {
                        Log.d(TAG, "onStartCommand Action = " + ACTION_SEND_G5_QUEUE + " PAth= " + WEARABLE_G5_QUEUE_PATH);
                        sendG5QueueData(intent.getStringExtra("queueData"));

                    } else {
                        // default
                        /*if (!mPrefs.getBoolean("force_wearG5", false)//handled by UploaderQueue
                                && mPrefs.getBoolean("enable_wearG5", false)
                                && (is_using_bt)) { //KS only send BGs if using Phone's G5 Collector Server
                            sendWearBgData(1);
                        }*/
                        Log.d(TAG, "onStartCommand Action=" + " Path=" + WEARABLE_DATA_PATH);
                        sendData();//ensure BgReading.Last is displayed on watch
                    }
                } else {
                    googleApiClient.connect();
                }
            } else {
                Log.wtf(TAG, "GoogleAPI client is null!");
            }
        }

        if (pebble_integration) {
            sendData();
        }

        //if ((!wear_integration)&&(!pebble_integration))
        if (!wear_integration)    // only wear sync starts this service, pebble features are not used?
        {
            Log.i(TAG, "Stopping service");
            startBtService();
            stopSelf();
            JoH.releaseWakeLock(wl);
            return START_NOT_STICKY;
        }

        JoH.releaseWakeLock(wl);
        return START_STICKY;
    }

    private void updateWearSyncBgsCapability() {
        CapabilityApi.GetCapabilityResult capabilityResult =
                Wearable.CapabilityApi.getCapability(
                        googleApiClient, CAPABILITY_WEAR_APP,
                        CapabilityApi.FILTER_REACHABLE).await(GET_CAPABILITIES_TIMEOUT_MS, TimeUnit.MILLISECONDS);
        CapabilityInfo nodes;
        if (!capabilityResult.getStatus().isSuccess()) {
            Log.e(TAG, "updateWearSyncBgsCapability Failed to get capabilities, status: " + capabilityResult.getStatus().getStatusMessage());
            nodes = null;
        } else {
            nodes = capabilityResult.getCapability();
        }

        if (nodes != null && nodes.getNodes().size() > 0) {
            Log.d(TAG, "Updating wear sync nodes");
            updateWearSyncBgsCapability(nodes);
        }
    }


    private void updateWearSyncBgsCapability(CapabilityInfo capabilityInfo) {
        Set<Node> connectedNodes = capabilityInfo.getNodes();
        mWearNodeId = pickBestNodeId(connectedNodes);
    }

    private String pickBestNodeId(Set<Node> nodes) {
        String bestNodeId = null;
        // Find a nearby node or pick one arbitrarily
        for (Node node : nodes) {
            if (node.isNearby()) {
                return node.getId();
            }
            bestNodeId = node.getId();
        }
        return bestNodeId;
    }

    private void setLocalNodeName() {
        forceGoogleApiConnect();
        NodeApi.GetLocalNodeResult localnodes = Wearable.NodeApi.getLocalNode(googleApiClient).await(60, TimeUnit.SECONDS);
        Node getnode = localnodes.getNode();
        localnode = getnode != null ? getnode.getDisplayName() + "|" + getnode.getId() : "";
        UserError.Log.d(TAG, "setLocalNodeName.  localnode=" + localnode);
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        Log.d(TAG, "onConnected entered");//KS
        /*CapabilityApi.CapabilityListener capabilityListener =
                new CapabilityApi.CapabilityListener() {
                    @Override
                    public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
                        updateWearSyncBgsCapability(capabilityInfo);
                        Log.d(TAG, "onConnected onCapabilityChanged mWearNodeID:" + mWearNodeId);
                        new CheckWearableConnected().execute();
                    }
                };

        Wearable.CapabilityApi.addCapabilityListener(
                googleApiClient,
                capabilityListener,
                CAPABILITY_WEAR_APP);*/
        sendData();
    }

    @Override
    public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
        updateWearSyncBgsCapability(capabilityInfo);
        Log.d(TAG, "onConnected onCapabilityChanged mWearNodeID:" + mWearNodeId);
        new CheckWearableConnected().execute();
    }

    private class CheckWearableConnected extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            if (googleApiClient.isConnected()) {
                if (System.currentTimeMillis() - lastRequest > 20 * 1000) { // enforce 20-second debounce period
                    lastRequest = System.currentTimeMillis();
                    //NodeApi.GetConnectedNodesResult nodes =
                    //        Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
                    if (localnode == null || (localnode != null && localnode.isEmpty()))
                        setLocalNodeName();
                    CapabilityApi.GetCapabilityResult capabilityResult =
                            Wearable.CapabilityApi.getCapability(
                                    googleApiClient, CAPABILITY_WEAR_APP,
                                    CapabilityApi.FILTER_REACHABLE).await(GET_CAPABILITIES_TIMEOUT_MS, TimeUnit.MILLISECONDS);
                    CapabilityInfo nodes;
                    if (!capabilityResult.getStatus().isSuccess()) {
                        Log.e(TAG, "doInBackground Failed to get capabilities, status: " + capabilityResult.getStatus().getStatusMessage());
                        nodes = null;
                    } else {
                        nodes = capabilityResult.getCapability();
                    }
                    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                    SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit();
                    boolean enable_wearG5 = sharedPrefs.getBoolean("enable_wearG5", false);
                    boolean force_wearG5 = sharedPrefs.getBoolean("force_wearG5", false);
                    String node_wearG5 = mPrefs.getString("node_wearG5", "");

                    if (nodes != null && nodes.getNodes().size() > 0) {
                        updateWearSyncBgsCapability(nodes);
                        int count = nodes.getNodes().size();
                        Log.d(TAG, "doInBackground connected.  CapabilityApi.GetCapabilityResult mWearNodeID=" + (mWearNodeId != null ? mWearNodeId : "") + " count=" + count);//KS
                        boolean isConnectedToWearable = false;
                        for (Node peer : nodes.getNodes()) {

                            //onPeerConnected
                            String wearNode = peer.getDisplayName() + "|" + peer.getId();
                            Log.d(TAG, "CheckWearableConnected onPeerConnected peer name & ID: " + wearNode);
                            if (wearNode.equals(node_wearG5)) {
                                isConnectedToWearable = true;
                                sendPrefSettings();
                                break;
                            } else if (node_wearG5.equals("")) {
                                isConnectedToWearable = true;
                                prefs.putString("node_wearG5", wearNode);
                                prefs.apply();
                                break;
                            }

                        }
                        sendPrefSettings();
                        initWearData();
                        if (enable_wearG5) {
                            //Only stop service if Phone will rely on Wear Collection Service
                            if (force_wearG5 && isConnectedToWearable) {
                                Log.d(TAG, "CheckWearableConnected onPeerConnected force_wearG5=true Phone stopBtService and continue to use Wear BT Collector");
                                stopBtService();
                            } else {
                                Log.d(TAG, "CheckWearableConnected onPeerConnected force_wearG5=false Phone startBtService");
                                startBtService();
                            }
                        }
                    } else {
                        //onPeerDisconnected
                        Log.d(TAG, "CheckWearableConnected onPeerDisconnected");
                        if (sharedPrefs.getBoolean("wear_sync", false)) {
                            Log.d(TAG, "CheckWearableConnected onPeerDisconnected wear_sync=true Phone startBtService");
                            startBtService();
                        }
                    }
                } else {
                    Log.d(TAG, "Debounce limit hit - not sending");
                }
            } else {
                Log.d(TAG, "Not connected for sending");
                googleApiClient.connect();
            }
            return null;
        }
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {//KS does not seem to get triggered; therefore use OnMessageReceived instead

        DataMap dataMap;

        for (DataEvent event : dataEvents) {

            if (event.getType() == DataEvent.TYPE_CHANGED) {

                String path = event.getDataItem().getUri().getPath();

                switch (path) {
                    case WEARABLE_PREF_DATA_PATH:
                        dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
                        if (dataMap != null) {
                            Log.d(TAG, "onDataChanged WEARABLE_PREF_DATA_PATH dataMap=" + dataMap);
                            syncPrefData(dataMap);
                        }
                        break;
                    default:
                        Log.d(TAG, "Unknown wearable path: " + path);
                        break;
                }
            }
        }
    }

    // incoming messages from wear device
    @Override
    public void onMessageReceived(MessageEvent event) {
        DataMap dataMap;
        byte[] decomprBytes;
        Log.d(TAG, "onMessageReceived enter");
        if (wear_integration) {
            final PowerManager.WakeLock wl = JoH.getWakeLock("watchupdate-msgrec", 60000);//KS test with 120000
            if (event != null) {
                Log.d(TAG, "onMessageReceived wearable event path: " + event.getPath());
                switch (event.getPath()) {
                    case WEARABLE_RESEND_PATH:
                        resendData(0);
                        break;
                    case WEARABLE_VOICE_PAYLOAD:
                        String eventData = "";
                        try {
                            eventData = new String(event.getData(), "UTF-8");
                        } catch (UnsupportedEncodingException e) {
                            eventData = "error";
                        }
                        Log.d(TAG, "Received wearable: voice payload: " + eventData);
                        if (eventData.length() > 1)
                            receivedText(getApplicationContext(), eventData);
                        break;
                    case WEARABLE_APPROVE_TREATMENT:
                        approveTreatment(getApplicationContext(), "");
                        break;
                    case WEARABLE_CANCEL_TREATMENT:
                        cancelTreatment(getApplicationContext(), "");
                        break;
                    case WEARABLE_SNOOZE_ALERT:
                        try {
                            eventData = new String(event.getData(), "UTF-8");
                        } catch (UnsupportedEncodingException e) {
                            eventData = "30";
                        }
                        int snooze;
                        try {
                            snooze = Integer.parseInt(eventData);
                        } catch (NumberFormatException e) {
                            snooze = 30;
                        }
                        Log.d(TAG, "Received wearable: snooze payload: " + snooze);
                        AlertPlayer.getPlayer().Snooze(xdrip.getAppContext(), snooze, true);
                        JoH.static_toast_long(getResources().getString(R.string.alert_snoozed_by_watch));
                        break;
                    case SYNC_BGS_PATH + "_BM"://TEST ignore only for benchmark
                    case SYNC_LOGS_PATH + "_BM":
                    case SYNC_BGS_PATH + "_BM_DUP":
                    case SYNC_LOGS_PATH + "_BM_DUP":
                    case SYNC_BGS_PATH + "_BM_RAND":
                    case SYNC_LOGS_PATH + "_BM_RAND":
                        Log.d(TAG, "onMessageReceived Ignore, just for test!");
                        decomprBytes = event.getData();
                        if (decomprBytes != null) {
                            //Log.d(TAG, "Benchmark: " + event.getPath() + "event.getData().length=" + decomprBytes.length);
                        }
                        break;
                    case SYNC_BGS_PATH + "_BM_COMPRESS"://TEST ignore only for benchmark
                    case SYNC_BGS_PATH + "_BM_DUP_COMPRESS":
                        Log.d(TAG, "onMessageReceived Ignore, just for test!");
                        decomprBytes = decompressBytes(event.getPath(), event.getData(), true);//bBenchmark
                        dataMap = DataMap.fromByteArray(decomprBytes);
                        if (dataMap != null) {
                            syncTransmitterData(dataMap, true);//bBenchmark=true
                        }
                        break;
                    case SYNC_LOGS_PATH + "_BM_COMPRESS":
                    case SYNC_LOGS_PATH + "_BM_DUP_COMPRESS":
                        Log.d(TAG, "onMessageReceived Ignore, just for test!");
                        decomprBytes = decompressBytes(event.getPath(), event.getData(), true);
                        dataMap = DataMap.fromByteArray(decomprBytes);
                        if (dataMap != null) {
                            syncLogData(dataMap, true);//bBenchmark=true
                        }
                        break;
                    case SYNC_BGS_PATH + "_BM_RAND_COMPRESS":
                    case SYNC_LOGS_PATH + "_BM_RAND_COMPRESS":
                        Log.d(TAG, "onMessageReceived Ignore, just for test!");
                        decomprBytes = decompressBytes(event.getPath(), event.getData(), true);
                        break;
                    case SYNC_BGS_PATH://KS
                        Log.d(TAG, "onMessageReceived SYNC_BGS_PATH");
                        if (event.getData() != null) {
                            dataMap = DataMap.fromByteArray(event.getData());
                            if (dataMap != null) {
                                Log.d(TAG, "onMessageReceived SYNC_BGS_PATH dataMap=" + dataMap);
                                syncTransmitterData(dataMap, false);
                            }
                        }
                        break;

                    case SYNC_BGS_PRECALCULATED_PATH:
                        Log.d(TAG, "onMessageReceived " + SYNC_BGS_PRECALCULATED_PATH);
                        decomprBytes = decompressBytes(event.getPath(), event.getData(), false);
                        dataMap = DataMap.fromByteArray(decomprBytes);
                        if (dataMap != null) {
                            final DataMap fdataMap = dataMap;
                            new LowPriorityThread(() -> {
                                syncBgReadingsData(fdataMap);
                                Home.staticRefreshBGCharts();
                            }, "inbound-precalculated-bg").start();
                        }
                        break;

                    case SYNC_LOGS_PATH:
                        Log.d(TAG, "onMessageReceived SYNC_LOGS_PATH");
                        if (event.getData() != null) {
                            decomprBytes = decompressBytes(event.getPath(), event.getData(), false);
                            dataMap = DataMap.fromByteArray(decomprBytes);//event.getData()
                            if (dataMap != null) {
                                Log.d(TAG, "onMessageReceived SYNC_LOGS_PATH");
                                syncLogData(dataMap, false);
                            }
                        }
                        break;
                    case SYNC_LOGS_REQUESTED_PATH:
                        dataMap = DataMap.fromByteArray(event.getData());
                        if (dataMap != null) {
                            syncLogsRequested = dataMap.getLong("syncLogsRequested", -1);
                            Log.d(TAG, "onMessageReceived SYNC_LOGS_REQUESTED_PATH syncLogsRequested=" + syncLogsRequested);
                        }
                        break;
                    case SYNC_STEP_SENSOR_PATH:
                        Log.d(TAG, "onMessageReceived SYNC_STEP_SENSOR_PATH");
                        if (event.getData() != null) {
                            dataMap = DataMap.fromByteArray(event.getData());
                            if (dataMap != null) {
                                Log.d(TAG, "onMessageReceived SYNC_STEP_SENSOR_PATH dataMap=" + dataMap);
                                syncStepSensorData(dataMap, false);
                            }
                        }
                        break;
                    case SYNC_HEART_SENSOR_PATH:
                        Log.d(TAG, "onMessageReceived SYNC_HEART_SENSOR_PATH");
                        if (event.getData() != null) {
                            dataMap = DataMap.fromByteArray(event.getData());
                            if (dataMap != null) {
                                Log.d(TAG, "onMessageReceived SYNC_HEART_SENSOR_PATH dataMap=" + dataMap);
                                syncHeartSensorData(dataMap, false);
                            }
                        }
                        break;
                    case SYNC_TREATMENTS_PATH:
                        Log.d(TAG, "onMessageReceived SYNC_TREATMENTS_PATH");
                        if (event.getData() != null) {
                            dataMap = DataMap.fromByteArray(event.getData());
                            if (dataMap != null) {
                                Log.d(TAG, "onMessageReceived SYNC_TREATMENTS_PATH dataMap=" + dataMap);
                                syncTreatmentsData(dataMap, false);
                            }
                        }
                        break;
                    case WEARABLE_INITDB_PATH:
                        Log.d(TAG, "onMessageReceived WEARABLE_INITDB_PATH");
                        initWearData();
                        break;
                    case WEARABLE_INITTREATMENTS_PATH:
                        Log.d(TAG, "onMessageReceived WEARABLE_INITTREATMENTS_PATH");
                        initWearTreatments();
                        break;
                    case WEARABLE_REPLYMSG_PATH:
                        Log.d(TAG, "onMessageReceived WEARABLE_REPLYMSG_PATH");
                        dataMap = DataMap.fromByteArray(event.getData());
                        if (dataMap != null) {
                            Log.d(TAG, "onMessageReceived WEARABLE_REPLYMSG_PATH dataMap=" + dataMap);
                            String action_path = dataMap.getString("action_path", "");
                            if (action_path != null && !action_path.isEmpty()) {
                                switch (action_path) {
                                    case START_COLLECTOR_PATH:
                                        String msg = dataMap.getString("msg", "");
                                        JoH.static_toast_short(msg);
                                        break;
                                    case STATUS_COLLECTOR_PATH:
                                        Log.d(TAG, "onMessageReceived WEARABLE_REPLYMSG_PATH send LocalBroadcastManager ACTION_BLUETOOTH_COLLECTION_SERVICE_UPDATE=" + ACTION_BLUETOOTH_COLLECTION_SERVICE_UPDATE);
                                        final Intent intent = new Intent(ACTION_BLUETOOTH_COLLECTION_SERVICE_UPDATE);
                                        intent.putExtra("data", dataMap.toBundle());//msg
                                        LocalBroadcastManager.getInstance(xdrip.getAppContext()).sendBroadcast(intent);
                                        break;
                                }
                            }
                        }
                        break;
                    case WEARABLE_G5BATTERY_PAYLOAD:
                        dataMap = DataMap.fromByteArray(event.getData());
                        if (dataMap != null) {
                            Log.d(TAG, "onMessageReceived WEARABLE_FIELD_SENDPATH dataMap=" + dataMap);
                            syncFieldData(dataMap);
                        }
                        break;
                    case WEARABLE_INITPREFS_PATH:
                        Log.d(TAG, "onMessageReceived WEARABLE_INITPREFS_PATH");
                        sendPrefSettings();
                        break;
                    case WEARABLE_LOCALE_CHANGED_PATH:
                        Log.d(TAG, "onMessageReceived WEARABLE_LOCALE_CHANGED_PATH");
                        sendLocale();
                        break;
                    case WEARABLE_PREF_DATA_PATH:
                        dataMap = DataMap.fromByteArray(event.getData());
                        if (dataMap != null) {
                            Log.d(TAG, "onMessageReceived WEARABLE_PREF_DATA_PATH dataMap=" + dataMap);
                            syncPrefData(dataMap);
                        }
                        break;


                    default:

                        if (event.getPath().startsWith(WEARABLE_REQUEST_APK)) {
                            // rate limit at this end just needs to de-bounce but allow retries
                            if (JoH.ratelimit(WEARABLE_REQUEST_APK, 15)) {
                                JoH.static_toast_short("Updating wear app");
                                int startAt = 0;
                                final String[] split = event.getPath().split("\\^");
                                if (split.length == 2) {
                                    startAt = Integer.parseInt(split[1]);
                                }
                                if (startAt == 0) {
                                    UserError.Log.uel(TAG, "VUP: Sending latest apk version to watch");
                                    JoH.static_toast_long("Sending latest version to watch");
                                }
                                final int finalStartAt = startAt;
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (mWearNodeId == null) {
                                            UserError.Log.d(TAG, "VUP: nodeid is null");
                                            updateWearSyncBgsCapability(); // try to populate
                                        }

                                        if (mWearNodeId != null) {
                                            // TODO limit to 120
                                            UserError.Log.d(TAG, "VUP: nodeid is now not null");
                                            if (apkBytes == null) {
                                                UserError.Log.d(TAG, "VUP: getting bytes");
                                                apkBytes = GetWearApk.getBytes();
                                            }
                                            if (apkBytes != null) {
                                                UserError.Log.d(TAG, "VUP: Trying to open channel to send apk");
                                                ChannelApi.OpenChannelResult result = Wearable.ChannelApi.openChannel(googleApiClient, mWearNodeId, "/updated-apk").await();

                                                final Channel channel = result.getChannel();
                                                if (channel != null) {

                                                    channel.getOutputStream(googleApiClient).setResultCallback(new ResultCallback<Channel.GetOutputStreamResult>() {
                                                        @Override
                                                        public void onResult(final Channel.GetOutputStreamResult getOutputStreamResult) {
                                                            Log.d(TAG, "VUP: channel get outputstream onResult:");


                                                            // TODO recurse/retry a few times if we haven't sent anything?
                                                            new Thread(new Runnable() {
                                                                @Override
                                                                public void run() {

                                                                    OutputStream output = null;
                                                                    try {
                                                                        output = getOutputStreamResult.getOutputStream();
                                                                        Log.d(TAG, "VUP: output stream opened");
                                                                        // this protocol can never be changed
                                                                        output.write((BuildConfig.VERSION_NAME + "\n").getBytes("UTF-8")); // version name
                                                                        output.write((apkBytes.length + "\n").getBytes("UTF-8")); // total length
                                                                        output.write((finalStartAt + "\n").getBytes("UTF-8")); // data starting from position
                                                                        // send data
                                                                        JoH.threadSleep(5000);
                                                                        Log.d(TAG, "VUP: sending data");
                                                                        // TODO stagger write?  await confirmation from far end to start xmit??
                                                                        output.write(apkBytes, finalStartAt, apkBytes.length - finalStartAt);
                                                                        output.flush();
                                                                        output.write(new byte[64000]); // seems to need some kind of padding
                                                                        JoH.threadSleep(5000);
                                                                        Log.d(TAG, "VUP: sent bytes: " + (apkBytes.length - finalStartAt));
                                                                    } catch (final IOException e) {
                                                                        Log.w(TAG, "VUP: could not send message: " + "Node: " + channel.getNodeId() + "Path: " + channel.getPath() + " Error message: " + e.getMessage() + " Error cause: " + e.getCause());
                                                                    } finally {
                                                                        try {
                                                                            Log.w(TAG, "VUP: Closing output stream");
                                                                            if (output != null) {
                                                                                output.close();
                                                                            }
                                                                        } catch (final IOException e) {
                                                                            Log.w(TAG, "VUP: could not close Output Stream: " + "Node ID: " + channel.getNodeId() + " Path: " + channel.getPath() + " Error message: " + e.getMessage() + " Error cause: " + e.getCause());
                                                                        } finally {
                                                                            channel.close(googleApiClient);
                                                                        }
                                                                    }

                                                                }
                                                            }).start();

                                                        }
                                                    });
                                                } else {
                                                    UserError.Log.d(TAG, "VUP: Could not send wearable apk as Channel result was null!");
                                                }
                                            }
                                        } else {
                                            Log.d(TAG, "VUP: Could not send wearable apk as nodeid is currently null");
                                        }
                                    }
                                }).start();
                            }
                        } else {
                            Log.d(TAG, "Unknown wearable path: " + event.getPath());
                            super.onMessageReceived(event);
                        }
                }
            }
            JoH.releaseWakeLock(wl);
        } else {
            super.onMessageReceived(event);
        }
    }

    private byte[] decompressBytes(String pathdesc, byte[] bytes, boolean bBenchmark) {
        byte[] decomprBytes;
        if ((bytes.length > 8)
                && (bytes[0] == (byte) 0x1F)
                && (bytes[1] == (byte) 0x8B)
                && (bytes[2] == (byte) 0x08)
                && (bytes[3] == (byte) 0x00)) {
            if (bBenchmark) {
                double benchmark_time = ts();
                JoH.benchmark(null);
                decomprBytes = JoH.decompressBytesToBytes(bytes);
                String msg = pathdesc + " JoH.decompressBytesToBytes from length=" + bytes.length + " to length=" + decomprBytes.length;
                JoH.benchmark(msg);
                msg = msg + " " + (ts() - benchmark_time) + " ms";
                sendDataReceived(DATA_ITEM_RECEIVED_PATH, msg, 1, "BM", -1);//"DATA_RECEIVED"
                return decomprBytes;
            } else {
                decomprBytes = JoH.decompressBytesToBytes(bytes);
                Log.d(TAG, pathdesc + " JoH.decompressBytesToBytes from length=" + bytes.length + " to length=" + decomprBytes.length);
                return decomprBytes;
            }
        } else {
            Log.d(TAG, "Benchmark: decompressBytesToBytes DataMap is not compressed!  Process as normal. length=" + bytes.length);
            return bytes;
        }
    }

    private void sendG5QueueData(String queueData) {
        if ((wear_integration) && (queueData != null)) {
            forceGoogleApiConnect();
            new SendToDataLayerThread(WEARABLE_G5_QUEUE_PATH, googleApiClient).executeOnExecutor(xdrip.executor, dataMap("queueData", queueData));
        }
    }

    private void sendData() {
        BgReading bg = BgReading.last();
        if (bg != null) {
            forceGoogleApiConnect();
            if (wear_integration) {
                final int battery = PowerStateReceiver.getBatteryLevel(getApplicationContext());
                new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, dataMap(bg, mPrefs, new BgGraphBuilder(getApplicationContext()), battery));
            }
        }
    }

    private void resendData(long since) {
        Log.d(TAG, "resendData ENTER");
        forceGoogleApiConnect();
        final long startTime = since == 0 ? new Date().getTime() - (60000 * 60 * 24) : since;
        Log.d(TAG, "resendData googleApiClient connected ENTER, sending since: " + JoH.dateTimeText(startTime));
        final BgReading last_bg = BgReading.last();
        if (last_bg != null) {
            List<BgReading> graph_bgs = BgReading.latestForGraph(60, startTime);
            BgGraphBuilder bgGraphBuilder = new BgGraphBuilder(getApplicationContext());
            if (!graph_bgs.isEmpty()) {
                final int battery = PowerStateReceiver.getBatteryLevel(getApplicationContext());
                DataMap entries = dataMap(last_bg, mPrefs, bgGraphBuilder, battery);
                final ArrayList<DataMap> dataMaps = new ArrayList<>(graph_bgs.size());
                for (BgReading bg : graph_bgs) {
                    dataMaps.add(dataMap(bg, mPrefs, bgGraphBuilder, battery));
                }
                entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                entries.putDataMapArrayList("entries", dataMaps);
                if (mPrefs.getBoolean("extra_status_line", false)) {
                    entries.putString("extra_status_line", StatusLine.extraStatusLine());
                }

                new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
            }
        }
    }

    private void sendNotification(String path, String notification) {//KS add args
        forceGoogleApiConnect();
        if (googleApiClient.isConnected()) {
            Log.d(TAG, "sendNotification Notification=" + notification + " Path=" + path);
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);
            //unique content
            dataMapRequest.setUrgent();
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putString(notification, notification);
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e(TAG, "sendNotification No connection to wearable available!");
        }
    }

    private void sendRequestExtra(String path, String key, String value) {
        forceGoogleApiConnect();
        if (googleApiClient.isConnected()) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);//NEW_STATUS_PATH
            //unique content
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putString(key, value);//"externalStatusString"
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e("sendRequestExtra", "No connection to wearable available!");
        }
    }

    private void sendRequestExtra(String path, String key, boolean value) {
        forceGoogleApiConnect();
        if (googleApiClient.isConnected()) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);//NEW_STATUS_PATH
            //unique content
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putBoolean(key, value);//"externalStatusString"
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
        } else {
            Log.e("sendRequestExtra", "No connection to wearable available!");
        }
    }

    private void sendRequestExtra(String path, String key, byte[] value) {
        forceGoogleApiConnect();
        if (googleApiClient.isConnected()) {
            PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);
            dataMapRequest.getDataMap().putDouble("timestamp", System.currentTimeMillis());
            dataMapRequest.getDataMap().putByteArray(key, value);
            PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
            dataMapRequest.setUrgent();
            Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
            Log.d(TAG, "Sending bytes path: " + path + " " + value.length);

        } else {
            Log.e("sendRequestExtra", "No connection to wearable available!");
        }
    }

    private void sendBlob(String path, final byte[] blob) {
        forceGoogleApiConnect();
        if (googleApiClient.isConnected()) {
            final Asset asset = Asset.createFromBytes(blob);
            Log.d(TAG, "sendBlob asset size: " + asset.getData().length);
            final PutDataMapRequest request = PutDataMapRequest.create(path);
            request.getDataMap().putLong("time", new Date().getTime());
            request.getDataMap().putByteArray("asset", blob);
            request.setUrgent();

            final PendingResult result = Wearable.DataApi.putDataItem(googleApiClient, request.asPutDataRequest());

            result.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
                @Override
                public void onResult(DataApi.DataItemResult sendMessageResult) {
                    if (!sendMessageResult.getStatus().isSuccess()) {
                        UserError.Log.e(TAG, "ERROR: failed to sendblob Status=" + sendMessageResult.getStatus().getStatusMessage());
                    } else {
                        UserError.Log.i(TAG, "Sendblob  Status=: " + sendMessageResult.getStatus().getStatusMessage());
                    }
                }
            });

            Log.d(TAG, "sendBlob: Sending asset of size " + blob.length);

        } else {
            Log.e(TAG, "sendBlob: No connection to wearable available!");
        }
    }


    // sending to watch - beware we munge the calculated value and replace with display glucose
    private DataMap dataMap(BgReading bg, SharedPreferences sPrefs, BgGraphBuilder bgGraphBuilder, int battery) {
        Double highMark = Double.parseDouble(sPrefs.getString("highValue", "170"));
        Double lowMark = Double.parseDouble(sPrefs.getString("lowValue", "70"));
        DataMap dataMap = new DataMap();

        //int battery = BgSendQueue.getBatteryLevel(getApplicationContext());

        // TODO this is inefficent when we are called in a loop instead should be passed in or already stored in bgreading
        final BestGlucose.DisplayGlucose dg = BestGlucose.getDisplayGlucose(); // current best


        dataMap.putString("sgvString", dg != null && bg.dg_mgdl > 0 ? dg.unitized : bgGraphBuilder.unitized_string(bg.calculated_value));

        dataMap.putString("slopeArrow", bg.slopeArrow());
        dataMap.putDouble("timestamp", bg.timestamp); //TODO: change that to long (was like that in NW)

        // This delta string only applies to the last reading even if we are processing historical data here
        if (dg != null) {
            dataMap.putString("delta", dg.unitized_delta);
        } else {
            dataMap.putString("delta", bgGraphBuilder.unitizedDeltaString(true, true, true));
        }
        dataMap.putString("battery", "" + battery);
        dataMap.putLong("sgvLevel", sgvLevel(bg.dg_mgdl > 0 ? bg.dg_mgdl : bg.calculated_value, sPrefs, bgGraphBuilder));
        dataMap.putInt("batteryLevel", (battery >= 30) ? 1 : 0);
        dataMap.putDouble("sgvDouble", bg.dg_mgdl > 0 ? bg.dg_mgdl : bg.calculated_value);
        dataMap.putDouble("high", inMgdl(highMark, sPrefs));
        dataMap.putDouble("low", inMgdl(lowMark, sPrefs));
        dataMap.putInt("bridge_battery", mPrefs.getInt("bridge_battery", -1));//Used in DexCollectionService
        //if (sPrefs.getBoolean("extra_status_line", false)) {
        //    dataMap.putString("extra_status_line", Home.extraStatusLine());
        //}

        //TODO: Add raw again
        //dataMap.putString("rawString", threeRaw((prefs.getString("units", "mgdl").equals("mgdl"))));
        return dataMap;
    }

    private void sendLocale() {
        final Locale locale = Locale.getDefault();
        Log.d(TAG, "ACTION_LOCALE_CHANGED Locale changed to " + locale);
        String country = locale.getCountry();
        sendRequestExtra(WEARABLE_LOCALE_CHANGED_PATH, "locale", locale.getLanguage() + (country != null && !country.isEmpty() ? "_" + country : ""));
    }

    // These are the settings which get sent to Wear device
    private void sendPrefSettings() {//KS
        forceGoogleApiConnect();
        DataMap dataMap = new DataMap();
        String dexCollector = "None";
        boolean enable_wearG5 = false;
        boolean force_wearG5 = false;
        String node_wearG5 = "";

        // add booleans that default to false to this list
        final List<String> defaultFalseBooleansToSend = WearSyncBooleans.getBooleansToSync();
        // add persistent store strings that default to ""
        final List<String> defaultBlankPersistentStringsToSend = WearSyncPersistentStrings.getPersistentStrings();

        wear_integration = mPrefs.getBoolean("wear_sync", false);
        if (wear_integration) {
            Log.d(TAG, "sendPrefSettings wear_sync=true");
            dexCollector = mPrefs.getString(DexCollectionType.DEX_COLLECTION_METHOD, "DexcomG5");
            enable_wearG5 = mPrefs.getBoolean("enable_wearG5", false);
            force_wearG5 = mPrefs.getBoolean("force_wearG5", false);
            node_wearG5 = mPrefs.getString("node_wearG5", "");
            dataMap.putString("dex_collection_method", dexCollector);
            dataMap.putBoolean("rewrite_history", mPrefs.getBoolean("rewrite_history", true));
            dataMap.putBoolean("enable_wearG5", enable_wearG5);
            dataMap.putBoolean("force_wearG5", force_wearG5);
            dataMap.putString("node_wearG5", node_wearG5);
            dataMap.putString("share_key", mPrefs.getString("share_key", "SM00000000"));//Used by DexShareCollectionService
            //Advanced Bluetooth Settings used by G4+xBridge DexCollectionService - temporarily just use the Phone's settings
            dataMap.putBoolean("use_transmiter_pl_bluetooth", mPrefs.getBoolean("use_transmiter_pl_bluetooth", false));
            dataMap.putBoolean("use_rfduino_bluetooth", mPrefs.getBoolean("use_rfduino_bluetooth", false));
            dataMap.putBoolean("automatically_turn_bluetooth_on", mPrefs.getBoolean("automatically_turn_bluetooth_on", true));
            dataMap.putBoolean("bluetooth_excessive_wakelocks", mPrefs.getBoolean("bluetooth_excessive_wakelocks", true));
            dataMap.putBoolean("close_gatt_on_ble_disconnect", mPrefs.getBoolean("close_gatt_on_ble_disconnect", true));
            dataMap.putBoolean("bluetooth_frequent_reset", mPrefs.getBoolean("bluetooth_frequent_reset", false));
            dataMap.putBoolean("bluetooth_watchdog", mPrefs.getBoolean("bluetooth_watchdog", false));
            dataMap.putString("bluetooth_watchdog_timer", mPrefs.getString("bluetooth_watchdog_timer", "20"));
            dataMap.putInt("bridge_battery", mPrefs.getInt("bridge_battery", -1));
            dataMap.putInt("nfc_sensor_age", mPrefs.getInt("nfc_sensor_age", -1));

            dataMap.putBoolean("sync_wear_logs", mPrefs.getBoolean("sync_wear_logs", false));
            //Alerts:
            dataMap.putString("persistent_high_repeat_mins", mPrefs.getString("persistent_high_repeat_mins", "20"));
            dataMap.putString("persistent_high_threshold_mins", mPrefs.getString("persistent_high_threshold_mins", "60"));
            dataMap.putBoolean("falling_alert", mPrefs.getBoolean("falling_alert", false));
            dataMap.putString("falling_bg_val", mPrefs.getString("falling_bg_val", "2"));
            dataMap.putBoolean("rising_alert", mPrefs.getBoolean("rising_alert", false));
            dataMap.putString("rising_bg_val", mPrefs.getString("rising_bg_val", "2"));
            dataMap.putBoolean("aggressive_service_restart", mPrefs.getBoolean("aggressive_service_restart", false));

            //Extra Status Line
            dataMap.putBoolean("extra_status_line", mPrefs.getBoolean("extra_status_line", false));
            dataMap.putBoolean("extra_status_stats_24h", Pref.getBooleanDefaultFalse("extra_status_stats_24h"));
            dataMap.putBoolean("status_line_calibration_long", mPrefs.getBoolean("status_line_calibration_long", false));
            dataMap.putBoolean("status_line_calibration_short", mPrefs.getBoolean("status_line_calibration_short", false));
            dataMap.putBoolean("status_line_avg", mPrefs.getBoolean("status_line_avg", false));
            dataMap.putBoolean("status_line_a1c_dcct", mPrefs.getBoolean("status_line_a1c_dcct", false));
            dataMap.putBoolean("status_line_a1c_ifcc", mPrefs.getBoolean("status_line_a1c_ifcc", false));
            dataMap.putBoolean("status_line_in", mPrefs.getBoolean("status_line_in", false));
            dataMap.putBoolean("status_line_high", mPrefs.getBoolean("status_line_high", false));
            dataMap.putBoolean("status_line_low", mPrefs.getBoolean("status_line_low", false));
            dataMap.putBoolean("status_line_carbs", mPrefs.getBoolean("status_line_carbs", false));
            dataMap.putBoolean("status_line_insulin", mPrefs.getBoolean("status_line_insulin", false));
            dataMap.putBoolean("status_line_stdev", mPrefs.getBoolean("status_line_stdev", false));
            dataMap.putBoolean("status_line_royce_ratio", mPrefs.getBoolean("status_line_royce_ratio", false));
            dataMap.putBoolean("status_line_capture_percentage", mPrefs.getBoolean("status_line_capture_percentage", false));
            dataMap.putBoolean("status_line_realtime_capture_percentage", mPrefs.getBoolean("status_line_realtime_capture_percentage", false));

            //Calibration plugin
            dataMap.putBoolean("extra_status_calibration_plugin", mPrefs.getBoolean("extra_status_calibration_plugin", false));
            dataMap.putBoolean("display_glucose_from_plugin", Pref.getBooleanDefaultFalse("display_glucose_from_plugin"));
            dataMap.putBoolean("use_pluggable_alg_as_primary", Pref.getBooleanDefaultFalse("use_pluggable_alg_as_primary"));
            if (Pref.getBooleanDefaultFalse("engineering_mode")) {
                dataMap.putBoolean("old_school_calibration_mode", Pref.getBooleanDefaultFalse("old_school_calibration_mode"));
            }

            dataMap.putBoolean("show_wear_treatments", Pref.getBooleanDefaultFalse("show_wear_treatments"));
            dataMap.putBoolean("use_ob1_g5_collector_service", Pref.getBooleanDefaultFalse("use_ob1_g5_collector_service"));
            dataMap.putString(Blukon.BLUKON_PIN_PREF, Pref.getStringDefaultBlank(Blukon.BLUKON_PIN_PREF));

            final String dex_time_keeper = Ob1G5StateMachine.extractDexTime();
            if (dex_time_keeper != null) {
                dataMap.putString("dex-timekeeping", dex_time_keeper);
            }

        }
        //Step Counter
        // note transmutes use_pebble_health -> use_wear_health
        dataMap.putBoolean("use_wear_health", mPrefs.getBoolean("use_pebble_health", true));
        is_using_bt = DexCollectionType.hasBluetooth();

        Double highMark = Double.parseDouble(mPrefs.getString("highValue", "170"));
        Double lowMark = Double.parseDouble(mPrefs.getString("lowValue", "70"));
        Log.d(TAG, "sendPrefSettings enable_wearG5: " + enable_wearG5 + " force_wearG5:" + force_wearG5 + " node_wearG5:" + node_wearG5 + " dex_collection_method:" + dexCollector);
        dataMap.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
        dataMap.putString("dex_txid", mPrefs.getString("dex_txid", "ABCDEF"));
        dataMap.putString("units", mPrefs.getString("units", "mgdl"));
        dataMap.putDouble("high", highMark);//inMgdl(highMark, mPrefs));//KS Fix for mmol on graph Y-axis in wear standalone mode
        dataMap.putDouble("low", lowMark);//inMgdl(lowMark, mPrefs));//KS Fix for mmol on graph Y-axis in wear standalone mode
        dataMap.putBoolean("g5_non_raw_method", mPrefs.getBoolean("g5_non_raw_method", false));
        dataMap.putString("extra_tags_for_logging", Pref.getStringDefaultBlank("extra_tags_for_logging"));
        //dataMap.putBoolean("engineering_mode",  Pref.getBooleanDefaultFalse("engineering_mode"));
        dataMap.putBoolean("bridge_battery_alerts", Pref.getBooleanDefaultFalse("bridge_battery_alerts"));
        dataMap.putString("bridge_battery_alert_level", Pref.getString("bridge_battery_alert_level", "30"));
        final Locale locale = Locale.getDefault();
        String country = locale.getCountry();
        dataMap.putString("locale", locale.getLanguage() + (country != null && !country.isEmpty() ? "_" + country : ""));

        dataMap.putString("build-version-name", getVersionID());

        for (String pref : defaultFalseBooleansToSend) {
            dataMap.putBoolean(pref, Pref.getBooleanDefaultFalse(pref));
        }
        for (String str : defaultBlankPersistentStringsToSend) {
            dataMap.putString(str, PersistentStore.getString(str));
        }

        new SendToDataLayerThread(WEARABLE_PREF_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, dataMap);
    }

    private boolean sendSensorData() {//KS
        try {

            if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
                googleApiClient.connect();
            }
            if (googleApiClient != null) {
                Sensor sensor = Sensor.currentSensor();
                if (sensor != null) {
                    if (wear_integration) {
                        DataMap dataMap = new DataMap();
                        Log.d(TAG, "Sensor sendSensorData uuid=" + sensor.uuid + " started_at=" + sensor.started_at + " active=" + Sensor.isActive() + " battery=" + sensor.latest_battery_level + " location=" + sensor.sensor_location + " stopped_at=" + sensor.stopped_at);
                        String json = sensor.toS();
                        Log.d(TAG, "dataMap sendSensorData GSON: " + json);

                        dataMap.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP

                        dataMap.putString("dex_txid", mPrefs.getString("dex_txid", "ABCDEF"));//KS
                        dataMap.putLong("started_at", sensor.started_at);
                        dataMap.putString("uuid", sensor.uuid);
                        dataMap.putInt("latest_battery_level", sensor.latest_battery_level);
                        dataMap.putString("sensor_location", sensor.sensor_location);

                        new SendToDataLayerThread(WEARABLE_SENSOR_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, dataMap);
                        return true;
                    }
                } else
                    Log.e(TAG, "sendSensorData current sensor is null!");
            } else {
                Log.e(TAG, "sendSensorData No connection to wearable available for send Sensor!");
                return false;
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Nullpointer exception in sendWearCalibrationData: " + e);
            return false;
        }
        return true;
    }

    private void sendActiveBtDeviceData() {//KS
        if (is_using_bt) {//only required for Collector running on watch
            forceGoogleApiConnect();
            ActiveBluetoothDevice btDevice = ActiveBluetoothDevice.first();
            if (btDevice != null) {
                if (wear_integration) {
                    DataMap dataMap = new DataMap();
                    Log.d(TAG, "sendActiveBtDeviceData name=" + btDevice.name + " address=" + btDevice.address + " connected=" + btDevice.connected);

                    dataMap.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP

                    dataMap.putString("name", btDevice.name);
                    dataMap.putString("address", btDevice.address);
                    dataMap.putBoolean("connected", btDevice.connected);

                    new SendToDataLayerThread(WEARABLE_ACTIVEBTDEVICE_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, dataMap);
                }
            }
        } else {
            Log.d(TAG, "Not sending activebluetoothdevice data as we are not using bt");
        }
    }

    private void sendAlertTypeData() {//KS
        try {
            forceGoogleApiConnect();
            List<AlertType> alerts = AlertType.getAllActive();
            if (alerts != null) {
                if (wear_integration) {
                    Log.d(TAG, "sendAlertTypeData latest count = " + alerts.size());
                    final DataMap entries = new DataMap();
                    final ArrayList<DataMap> dataMaps = new ArrayList<>(alerts.size());
                    for (AlertType alert : alerts) {
                        if (alert != null) {
                            dataMaps.add(dataMap(alert, "alert"));
                        }
                    }
                    entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                    entries.putDataMapArrayList("entries", dataMaps);
                    new SendToDataLayerThread(WEARABLE_ALERTTYPE_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
                } else
                    Log.d(TAG, "sendAlertTypeData latest count = 0");
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Nullpointer exception in sendAlertTypeData: " + e);
        }
    }

    private DataMap dataMap(String key, String value) {
        final DataMap dataMap = new DataMap();
        dataMap.putString(key, value);
        return dataMap;
    }

    private DataMap dataMap(AlertType alert, String type) {//KS
        DataMap dataMap = new DataMap();
        String json = alert.toS();
        Log.d(TAG, "dataMap BG GSON: " + json);
        dataMap.putString(type, json);
        return dataMap;
    }

    public static boolean sendWearUpload(List<BgReading> bgs, List<Calibration> cals, List<BloodTest> bts, List<Treatments> treatsAdd, List<String> treatsDel) {
        boolean statusCals = sendWearCalibrationData(0, 0, cals);
        boolean statusBgs = sendWearBgData(0, 0, bgs);
        boolean statusBts = sendWearBloodTestData(0, 0, bts);
        boolean statusTreats = sendWearTreatmentsData(0, 0, treatsAdd);
        boolean statusTreatsDel = sendWearTreatmentsDataDelete(treatsDel);
        return (statusCals && statusBts && statusTreats && statusTreatsDel && statusBgs);
    }

    public static boolean sendWearTreatmentsDataDelete(List<String> list) {
        if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
            googleApiClient.connect();
        }
        if (googleApiClient != null) {
            if (!list.isEmpty()) {
                Log.d(TAG, "sendWearTreatmentsDataDelete graph size=" + list.size());
                DataMap entries = new DataMap();
                entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                entries.putString("action", "delete");
                entries.putStringArrayList("entries", (new ArrayList<String>(list)));
                new SendToDataLayerThread(WEARABLE_TREATMENTS_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
            } else
                Log.d(TAG, "sendWearTreatmentsDataDelete treatments count = 0");
        } else {
            Log.e(TAG, "sendWearTreatmentsData No connection to wearable available for send treatment!");
            return false;
        }
        return true;
    }

    public static boolean sendWearTreatmentsData(Integer count, long startTime) {
        return sendWearTreatmentsData(count, startTime, null);
    }

    public static boolean sendWearTreatmentsData(Integer count, long startTime, List<Treatments> list) {
        try {
            if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
                googleApiClient.connect();
            }
            if (googleApiClient != null) {
                Treatments last = list != null && list.size() > 0 ? list.get(0) : Treatments.last();
                if (last != null) {
                    Log.d(TAG, "sendWearTreatmentsData last.timestamp:" + JoH.dateTimeText(last.timestamp));
                } else {
                    Log.d(TAG, "sendWearTreatmentsData no treatments exist");
                    return true;
                }
                List<Treatments> graph;
                if (list != null)
                    graph = list;
                else if (startTime == 0)
                    graph = Treatments.latest(count);
                else
                    graph = Treatments.latestForGraph(count, startTime);
                if (!graph.isEmpty()) {
                    Log.d(TAG, "sendWearTreatmentsData graph size=" + graph.size());
                    final ArrayList<DataMap> dataMaps = new ArrayList<>(graph.size());
                    DataMap entries = dataMap(last);
                    for (Treatments data : graph) {
                        dataMaps.add(dataMap(data));
                    }
                    Log.d(TAG, "sendWearTreatmentsData entries=" + entries);
                    entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                    entries.putString("action", "insert");
                    entries.putDataMapArrayList("entries", dataMaps);
                    new SendToDataLayerThread(WEARABLE_TREATMENTS_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
                } else
                    Log.d(TAG, "sendWearTreatmentsData treatments count = 0");
            } else {
                Log.e(TAG, "sendWearTreatmentsData No connection to wearable available for send treatment!");
                return false;
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Nullpointer exception in sendWearTreatmentsData: " + e);
            return false;
        }
        return true;
    }

    private static DataMap dataMap(Treatments data) {
        DataMap dataMap = new DataMap();
        String json = data.toS();
        Log.d(TAG, "dataMap BG GSON: " + json);
        dataMap.putString("data", json);
        return dataMap;
    }

    public static boolean sendWearBloodTestData(Integer count, long startTime) {
        return sendWearBloodTestData(count, startTime, null);
    }

    public static boolean sendWearBloodTestData(Integer count, long startTime, List<BloodTest> list) {//DataMap
        try {
            if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
                googleApiClient.connect();
            }
            if (googleApiClient != null) {
                BloodTest last = list != null && list.size() > 0 ? list.get(0) : BloodTest.last();
                if (last != null) {
                    Log.d(TAG, "sendWearBloodTestData last.timestamp:" + JoH.dateTimeText(last.timestamp));
                } else {
                    Log.d(TAG, "sendWearBloodTestData no BloodTest exist");
                    return true;
                }
                List<BloodTest> graph;
                if (list != null)
                    graph = list;
                else if (startTime == 0)
                    graph = BloodTest.last(count);
                else
                    graph = BloodTest.latestForGraph(count, startTime);
                if (!graph.isEmpty()) {
                    Log.d(TAG, "sendWearBloodTestData graph size=" + graph.size());
                    final ArrayList<DataMap> dataMaps = new ArrayList<>(graph.size());
                    DataMap entries = dataMap(last);
                    for (BloodTest data : graph) {
                        dataMaps.add(dataMap(data));
                    }
                    Log.d(TAG, "sendWearBloodTestData entries=" + entries);
                    entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                    entries.putDataMapArrayList("entries", dataMaps);
                    new SendToDataLayerThread(WEARABLE_BLOODTEST_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
                } else
                    Log.d(TAG, "sendWearBloodTestData BloodTest count = 0");
            } else {
                Log.e(TAG, "sendWearBloodTestData No connection to wearable available for send BloodTest!");
                return false;
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Nullpointer exception in sendWearBloodTestData: " + e);
            return false;
        }
        return true;
    }

    private static DataMap dataMap(BloodTest data) {
        DataMap dataMap = new DataMap();
        String json = data.toS();
        Log.d(TAG, "dataMap BG GSON: " + json);
        dataMap.putString("data", json);
        return dataMap;
    }

    private boolean sendWearCalibrationData(Integer count) {
        return sendWearCalibrationData(count, 0);
    }

    private boolean sendWearCalibrationData(Integer count, long startTime) {
        return sendWearCalibrationData(count, startTime, null);
    }

    private static boolean sendWearCalibrationData(Integer count, long startTime, List<Calibration> list) {
        try {
            if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
                googleApiClient.connect();
            }
            //if ((googleApiClient != null) && (googleApiClient.isConnected())) {
            if (googleApiClient != null) {
                Log.d(TAG, "sendWearCalibrationData");
                final Sensor sensor = Sensor.currentSensor();
                final Calibration last = list != null && list.size() > 0 ? list.get(0) : Calibration.last();

                List<Calibration> latest;
                BgReading lastBgReading = BgReading.last();
                //From BgReading: if (lastBgReading.calibration_flag == true && ((lastBgReading.timestamp + (60000 * 20)) > bgReading.timestamp) && ((lastBgReading.calibration.timestamp + (60000 * 20)) > bgReading.timestamp))
                //From BgReading:     lastBgReading.calibration.rawValueOverride()
                if (list != null)
                    latest = list;
                else if (startTime != 0)
                    latest = Calibration.latestForGraphSensor(count, startTime, Long.MAX_VALUE);
                else if (lastBgReading != null && lastBgReading.calibration != null && lastBgReading.calibration_flag == true) {
                    Log.d(TAG, "sendWearCalibrationData lastBgReading.calibration_flag=" + lastBgReading.calibration_flag + " lastBgReading.timestamp: " + lastBgReading.timestamp + " lastBgReading.calibration.timestamp: " + lastBgReading.calibration.timestamp);
                    latest = Calibration.allForSensor();
                } else {
                    latest = Calibration.latest(count);
                }

                if ((sensor != null) && (last != null) && (latest != null && !latest.isEmpty())) {
                    Log.d(TAG, "sendWearCalibrationData latest count = " + latest.size());
                    final DataMap entries = dataMap(last);
                    final ArrayList<DataMap> dataMaps = new ArrayList<>(latest.size());
                    if (sensor.uuid != null) {
                        for (Calibration calibration : latest) {
                            if ((calibration != null) && (calibration.sensor_uuid != null) && (calibration.sensor_uuid.equals(sensor.uuid))) {
                                dataMaps.add(dataMap(calibration));
                            }
                        }
                    }
                    entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                    entries.putDataMapArrayList("entries", dataMaps);
                    new SendToDataLayerThread(WEARABLE_CALIBRATION_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
                } else
                    Log.d(TAG, "sendWearCalibrationData latest count = 0");
            } else {
                Log.e(TAG, "sendWearCalibrationData No connection to wearable available for send treatment!");
                return false;
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Nullpointer exception in sendWearCalibrationData: " + e);
            return false;
        }
        return true;
    }

    private static DataMap dataMap(Calibration calibration) {//KS
        DataMap dataMap = new DataMap();
        String json = calibration.toS();
        Log.d(TAG, "dataMap Calibration GSON: " + json);
        dataMap.putString("bgs", json); // should be refactored to avoid confusion!
        return dataMap;
    }

    private boolean sendWearBgData(Integer count) {
        return sendWearBgData(count, 0);
    }

    private boolean sendWearBgData(Integer count, long startTime) {
        return sendWearBgData(count, startTime, null);
    }


    private static boolean sendWearBgData(Integer count, long startTime, List<BgReading> list) {
        try {
            if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
                //googleApiConnect();
                googleApiClient.connect();
            }
            if (googleApiClient != null) {
                Log.d(TAG, "sendWearBgData");
                final BgReading last = BgReading.last();
                List<BgReading> latest;
                if (list != null)
                    latest = list;
                else if (startTime != 0)
                    latest = BgReading.latestForGraphSensor(count, startTime, Long.MAX_VALUE);
                else
                    latest = BgReading.latest(count);
                if ((last != null) && (latest != null && !latest.isEmpty())) {
                    final int battery = PowerStateReceiver.getBatteryLevel(xdrip.getAppContext());
                    Log.d(TAG, "sendWearBgData latest count = " + latest.size() + " battery=" + battery);
                    final DataMap entries = dataMap(last);
                    final ArrayList<DataMap> dataMaps = new ArrayList<>(latest.size());
                    final Sensor sensor = Sensor.currentSensor();
                    if ((sensor != null) && (sensor.uuid != null)) {
                        for (BgReading bg : latest) {
                            // if we have no sensor data, typically follower then add one in to pass tests.
                            if (bg != null && bg.sensor_uuid == null) {
                                bg.sensor_uuid = sensor.uuid;
                            }
                            if ((bg != null) && (bg.sensor_uuid != null) && (bg.sensor_uuid.equals(sensor.uuid) && (bg.calibration_uuid != null))) {
                                dataMaps.add(dataMap(bg));
                            } else {
                                if (bg.sensor_uuid == null) {
                                    Log.d(TAG, "sendWearBgData: sensor uuid is null on record to send");
                                }
                                if (bg.calibration_uuid == null) {
                                    Log.d(TAG, "sendWearBgData: calibration uuid is null on record to send");
                                }
                            }
                        }
                    } else {
                        Log.d(TAG, "sendWearBgData Not queueing data due to sensor: " + (sensor != null ? sensor.uuid : "null sensor object"));
                    }
                    entries.putLong("time", new Date().getTime()); // MOST IMPORTANT LINE FOR TIMESTAMP
                    entries.putInt("battery", battery);
                    entries.putDataMapArrayList("entries", dataMaps);
                    new SendToDataLayerThread(WEARABLE_BG_DATA_PATH, googleApiClient).executeOnExecutor(xdrip.executor, entries);
                } else
                    Log.d(TAG, "sendWearBgData lastest count = 0");
            } else {
                Log.e(TAG, "sendWearBgData No connection to wearable available for send BG!");
                return false;
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Nullpointer exception in sendWearBgData: " + e);
            return false;
        }
        return true;
    }

    private static DataMap dataMap(BgReading bg) {//KS
        DataMap dataMap = new DataMap();
        //KS Fix for calibration_uuid not being set in Calibration.create which updates bgReading to new calibration ln 497
        //if (bg.calibration_flag == true) {
        //    bg.calibration_uuid = bg.calibration.uuid;
        //}
        try {
            dataMap.putString("calibrationUuid", bg.calibration.uuid);
        } catch (NullPointerException e) {
            Log.d(TAG, "Calibration uuid is not set in dataMap(BgReading)");
        }
        String json = bg.toS();
        Log.d(TAG, "dataMap BG GSON: " + json);
        dataMap.putString("bgs", json);
        return dataMap;
    }

    private void initWearData() {
        if (JoH.ratelimit("watch_init_wear_data", 120)) {
            wear_integration = mPrefs.getBoolean("wear_sync", false);
            if (wear_integration) {//is_using_bt
                Log.d(TAG, "***initWearData***");
                sendSensorData();
                sendActiveBtDeviceData();
                sendAlertTypeData();
                if (mPrefs.getBoolean("show_wear_treatments", false)) {
                    initWearTreatments();
                } else {
                    sendWearCalibrationData(sendCalibrationCount);
                    sendWearBgData(sendBgCount);
                }
                sendData();//ensure BgReading.Last is displayed on watch
            } else {
                Log.d(TAG, "Skip initWearData as wear integration is disabled");
            }
        } else
            Log.d(TAG, "Skip initWearData due to exceeding ratelimit");
    }

    private void initWearTreatments() {
        long startTime = new Date().getTime() - (60000 * 60 * 24 * 3);//3 days
        if (JoH.ratelimit("watch_init_wear_treatments_data", 60)) {
            Log.d(TAG, "initWearTreatments clear treatments and re-init from startTime=" + JoH.dateTimeText(startTime));
            sendNotification(CLEAR_TREATMENTS_PATH, "clearTreatments");//this necessary to ensure deleted treatments are cleared
            sendWearTreatmentsData(sendTreatmentsCount, startTime);
            sendWearBloodTestData(sendTreatmentsCount, startTime);
            sendWearCalibrationData(sendTreatmentsCount, startTime);
            sendWearBgData(sendTreatmentsCount, startTime);
        } else
            Log.d(TAG, "Skip initWearTreatments due to exceeding ratelimit");
    }

    private long sgvLevel(double sgv_double, SharedPreferences prefs, BgGraphBuilder bgGB) {
        Double highMark = Double.parseDouble(prefs.getString("highValue", "170"));
        Double lowMark = Double.parseDouble(prefs.getString("lowValue", "70"));
        if (bgGB.unitized(sgv_double) >= highMark) {
            return 1;
        } else if (bgGB.unitized(sgv_double) >= lowMark) {
            return 0;
        } else {
            return -1;
        }
    }

    private double inMgdl(double value, SharedPreferences sPrefs) {
        if (!doMgdl(sPrefs)) {
            return value * Constants.MMOLL_TO_MGDL;
        } else {
            return value;
        }

    }

    static public int readPrefsInt(SharedPreferences prefs, String name, int defaultValue) {
        try {
            return Integer.parseInt(prefs.getString(name, "" + defaultValue));

        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Override
    public void onDestroy() {
        if (googleApiClient != null && googleApiClient.isConnected()) {
            googleApiClient.disconnect();
        }
        if (mPrefs != null && mPreferencesListener != null) {
            mPrefs.unregisterOnSharedPreferenceChangeListener(mPreferencesListener);
        }
    }

    @Override
    public void onConnectionSuspended(int cause) {
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
    }

    public static boolean isEnabled() {
        return Pref.getBooleanDefaultFalse("wear_sync");
    }

    // any change must be replicated on wear
    private static String getVersionID() {
        return BuildConfig.VERSION_NAME;
    }


}