package com.xypower.mpmaster;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

import com.dev.devapi.api.SysApi;
import com.xypower.common.FileDownloader;
import com.xypower.common.FilesUtils;
import com.xypower.common.JSONUtils;
import com.xypower.common.LogFormatter;
import com.xypower.common.LogcatHandler;
import com.xypower.common.MicroPhotoContext;
import com.xypower.common.RotatingHandler;
import com.xypower.mpmaster.sms.SimUtil;
import com.xypower.mpmaster.sms.SmsSendReceiver;

import org.json.JSONObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.channels.FileLock;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.atomic.AtomicInteger;;

public class MpMasterService extends Service {
    static {
        System.loadLibrary("mpmaster");
    }

    public static final String TAG = "MPMST";

    public Logger logger;

    public static final int NOTIFICATION_ID_FOREGROUND_SERVICE = 8466503;
    public static final String ACTION_MSG_BROADCAST = "ACT_MSG_BROADCAST";

    public static final String ACTION_START = "com.xypower.mpmaster.ACT_START";
    public static final String ACTION_STOP = "com.xypower.mpmaster.ACT_STOP";
    public static final String ACTION_MAIN = "com.xypower.mpmaster.ACT_MAIN";

    public static final String ACTION_REQ_RESTART_APP = "com.xypower.mpmaster.ACT_REQ_RST_APP";

    public static final String ACTION_UPD_OTA = SysApi.OTA_RESULT_ACTION;
    public static final String ACTION_INSTALL_RESULT = SysApi.INSTALL_RESULT_ACTION;
    public static final String ACTION_UNINSTALL_RESULT = SysApi.UNINSTALL_RESULT_ACTION;

    private static final String ACTION_UPDATE_CONFIGS = "com.xypower.mpmaster.ACT_UPD_CFG";

    private static final String ACTION_HEARTBEAT = "com.xypower.mpmaster.ACT_HB";
    private static final String ACTION_TAKE_PHOTO = "com.xypower.mpapp.ACT_TP";

    public static final String ACTION_MP_RESTART = "com.xypower.mpapp.ACT_RESTART";
    public static final String ACTION_IMP_PUBKRY = "com.xypower.mpapp.ACT_IMP_PUBKEY";

    private static final String FOREGROUND_CHANNEL_ID = "fg_mpmst";
    private SmsSendReceiver mSmsSnedReceiver;

    private int mPrevDateForLogs = 0;
    private int mMasterTimers = 0;

    public static class STATE_SERVICE {
        public static final int CONNECTED = 10;
        public static final int NOT_CONNECTED = 0;
    }

    private static int mStateService = STATE_SERVICE.NOT_CONNECTED;

    private long mMpHeartbeatDuration = 600000;  // = 10minutes  Unit: millssecond

    private boolean mMntnMode = false;
    private boolean mQuickHbMode = false;
    private boolean mUsingAbsHbTime = false;
    private String mCmdid = "";
    private NotificationManager mNotificationManager;
    private int mQuickHeartbeatDuration = 60; // Unit: second
    private int mHeartbeatDuration = 600; // Unit: second 10m = 10 * 60s

    private long mTimeForKeepingLogs = 86400000 * 15;   // 15 days
    private boolean mSyncTime = false;

    private AlarmReceiver mAlarmReceiver = null;
    private ScreenActionReceiver mScreenaAtionReceiver = null;
    private UpdateReceiver mUpdateReceiver = null;

    private Handler mHander = null;

    private String mModelName = null;

    private static String mMpAppVersion = null;
    private static String mMpMasterVersion = null;

    private String mSerialNo = null;

    private long mTimeToStartMpApp = 0;
    private long mTimeOfMpAppAlive = 1800000; // 30minutes
    private int mAbsHeartbeatTimes[] = null;

    private boolean mSeparateNetwork = false;

    private long mDelayedRestartMpTime = 60000;
    private PendingIntent mPreviousHB = null;
    private long mPreviousHeartbeatTime = 0;

    private long mPreviousMpHbTime = 0;

    private int mMaxBCV = 0;
    private long mMaxBCVTime = 0;

    private String mIccid1 = null;
    private String mIccid2 = null;

    AtomicInteger reqCode = new AtomicInteger(0);

    public MpMasterService() {
    }

    @Override
    public void onLowMemory() {
        final Context context = getApplicationContext();
        try {
            Intent intent = new Intent(this, MainActivity.class);
            int uniqueReqCode = reqCode.getAndIncrement();
            PendingIntent pi = PendingIntent.getActivity(this,uniqueReqCode, intent,0);
            AlarmManager alarmManager=(AlarmManager)getSystemService(ALARM_SERVICE);
            alarmManager.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis() + 5000, pi);
            logger.info("Restart MpApp after 5s as for LowMemory");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        loadConfig();
        loadIccid();

        logger = Logger.getLogger("com.xypower.mpmaster.logger");
        logger.setLevel(Level.ALL);
        // LogFormatter.installFormatter(logger);
        LogFormatter logFormatter = new LogFormatter();

        if (BuildConfig.DEBUG) {
            LogcatHandler logcatHandler = new LogcatHandler(TAG);
            logcatHandler.setFormatter(logFormatter);
            logger.addHandler(logcatHandler);
        }

        RotatingHandler rotatingHandler = null;
        try {
            String appPath = MicroPhotoContext.buildMasterAppDir(getApplicationContext());
            String logPath = appPath + "logs";
            File fi = new File(logPath);
            if (!fi.exists()) {
                fi.mkdirs();
            }

            File logFile = new File(fi, "mlog.txt");

            rotatingHandler = new RotatingHandler(logFile.getAbsolutePath(), logFormatter);

            logger.addHandler(rotatingHandler);

        } catch (Throwable e) {
            System.out.println("Failed to create directory:" + e.getMessage());
        }

        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                mSerialNo = Build.getSerial();
            } else {
                mSerialNo = Build.SERIAL;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        mMpMasterVersion = MicroPhotoContext.getVersionName(getApplicationContext());
        PackageManager packageManager = getPackageManager();
        PackageInfo packageInfo = null;
        try {
            packageInfo = packageManager.getPackageInfo(MicroPhotoContext.PACKAGE_NAME_MPAPP, 0);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        mMpAppVersion = packageInfo == null ? "" : packageInfo.versionName;

        mPreviousMpHbTime = System.currentTimeMillis();
        mTimeToStartMpApp = mPreviousMpHbTime;

        buildChargingBatteryVoltage(System.currentTimeMillis());

        logger.info("MpMaster started version=" + mMpMasterVersion);

        mHander = new Handler();

        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        mStateService = STATE_SERVICE.NOT_CONNECTED;

        mScreenaAtionReceiver = new ScreenActionReceiver();

        // 注册广播接受者
        {
            mAlarmReceiver = new AlarmReceiver(this);
            IntentFilter intentFilter = new IntentFilter(ACTION_HEARTBEAT);
            intentFilter.addAction(ACTION_MSG_BROADCAST);
            intentFilter.addAction(ACTION_UPDATE_CONFIGS);
            intentFilter.addAction(ACTION_UPD_OTA);
            intentFilter.addAction(ACTION_INSTALL_RESULT);
            intentFilter.addAction(ACTION_UNINSTALL_RESULT);
            intentFilter.addAction(ACTION_REQ_RESTART_APP);
            intentFilter.addAction(MicroPhotoContext.ACTION_HEARTBEAT_MP);

            registerReceiver(mAlarmReceiver, intentFilter);
        }

        {
            mUpdateReceiver = new UpdateReceiver();
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
            intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
            intentFilter.addDataScheme("package");
            registerReceiver(mUpdateReceiver, intentFilter);
        }

        // SMS broadcast receiver
        mSmsSnedReceiver = new SmsSendReceiver();
        IntentFilter intentFilter;
        intentFilter = new IntentFilter(SimUtil.SMS_SEND_ACTION);
        registerReceiver(mSmsSnedReceiver, intentFilter);
    }

    public String getIccid(int number) {
        if (number == 1) {
            return mIccid1 == null ? "" : mIccid1;
        } else {
            return mIccid2 == null ? "" : mIccid2;
        }
    }

    public void cleanLogFiles() {

        try {
            Date dt = new Date();

            if (dt.getDate() == mPrevDateForLogs) {
                return;
            }

            String appPath = MicroPhotoContext.buildMasterAppDir(getApplicationContext());
            String logPath = appPath + "logs";
            File fi = new File(logPath);
            if (!fi.exists()) {
                return;
            }

            mPrevDateForLogs = dt.getDate();
            dt.setHours(0);
            dt.setMinutes(0);
            long millis = dt.getTime() - mTimeForKeepingLogs;
            File[] subFiles = fi.listFiles();
            for (File f : subFiles) {
                if (f.lastModified() < millis) {
                    f.delete();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void onDestroy() {

        mStateService = STATE_SERVICE.NOT_CONNECTED;

        logger.warning("MicroPhotoService::onDestroy called");

        unregisterReceiver(mAlarmReceiver);
        unregisterReceiver(mScreenaAtionReceiver);
        unregisterReceiver(mUpdateReceiver);
        unregisterReceiver(mSmsSnedReceiver);

        for (java.util.logging.Handler h : logger.getHandlers()) {
            try {
                h.close();
            } catch (Exception ex) {
            }
        }

        super.onDestroy();
    }

    protected void loadConfig() {
        MicroPhotoContext.MasterConfig masterConfig = MicroPhotoContext.getMasterConfig(getApplicationContext());
        mMntnMode = masterConfig.mntnMode != 0;
        mQuickHbMode = masterConfig.quickHbMode != 0;
        mUsingAbsHbTime = masterConfig.usingAbsHbTime != 0;
        mHeartbeatDuration = masterConfig.heartbeat * 60;   // minute to second
        mQuickHeartbeatDuration = masterConfig.quickHeartbeat;
        mAbsHeartbeatTimes = masterConfig.absHeartbeats;
        if (mAbsHeartbeatTimes != null && mAbsHeartbeatTimes.length > 0) {
            Arrays.sort(mAbsHeartbeatTimes);
        }
        mSeparateNetwork = masterConfig.separateNetwork != 0;
        mTimeOfMpAppAlive = masterConfig.mpappMonitorTimeout;
        if (masterConfig.timeForKeepingLogs > 0) {
            mTimeForKeepingLogs = masterConfig.timeForKeepingLogs * 86400000;
        }
        mSyncTime = masterConfig.syncTime;
    }

    private void loadIccid() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
            try {
                SubscriptionManager sm = SubscriptionManager.from(this);
                List<SubscriptionInfo> sis = sm.getActiveSubscriptionInfoList();
                if (sis.size() >= 1) {
                    SubscriptionInfo si1 = sis.get(0);
                    mIccid1 = si1.getIccId();
                    // String phoneNum1 = si1.getNumber();
                }
                if (sis.size() >= 2) {
                    SubscriptionInfo si2 = sis.get(1);
                    mIccid2 = si2.getIccId();
                    // String phoneNum2 = si2.getNumber();
                }
                // int count = sm.getActiveSubscriptionInfoCount();// real cards
                // int max   = sm.getActiveSubscriptionInfoCountMax();// Slot number

            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    // public boolean useSeparater
    public String getCmdid() {
        return mCmdid;
    }

    public boolean isSeparateNetwork() {
        return mSeparateNetwork;
    }

    public boolean isMntnMode() {
        return mMntnMode;
    }

    public boolean shouldSyncTime() { return mSyncTime; }

    public void detectMpAppAlive() {
        try {
            final Context context = getApplicationContext();
            long ts = System.currentTimeMillis();

            try {
                boolean isMpAppRunning = detectMpAppRunning();
                if (!isMpAppRunning) {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                    // Check twice
                    isMpAppRunning = detectMpAppRunning();
                }

                if (!isMpAppRunning) {
                    // Restart MpApp
                    MicroPhotoContext.restartMpApp(context, "MpMST Keep Alive Detection: NO Lock");
                    logger.warning("Restart MpAPP as There is NO Lock");
                    mTimeToStartMpApp = ts;
                    return;
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            long tempduration = mMpHeartbeatDuration;
            if(mMpHeartbeatDuration < 600000)
                tempduration = 290000;
            if (mPreviousMpHbTime <= ts && ts - mPreviousMpHbTime > tempduration * 2) {
                // MpApp is not running
                if (ts - mTimeToStartMpApp >= 1800000) { // 30 minutes 30 * 60 * 1000
                    MicroPhotoContext.restartMpApp(context, "MpMST Keep Alive Detection");

                    logger.warning("Restart MpAPP as it is NOT Running Prev MPAPP HB=" +
                            Long.toString((ts - mPreviousMpHbTime) / 1000) + " MPAPP HBDuration=" + Long.toString(mMpHeartbeatDuration)
                            + " Prev MpRestart Time=" + Long.toString(mTimeToStartMpApp) + " last MPAPPHB =" + (mPreviousMpHbTime/1000));

                    mTimeToStartMpApp = ts;
                } else {
                    logger.warning("MpAPP has restarted during 30min, skip the check.");
                }
                return;
            }

            // MpApp is alive
            final String appPath = MicroPhotoContext.buildMpAppDir(context);
            final File mpappHb = new File(appPath + "data/alive/hb");
            final long modifiedTimeOfHb = getFileModificationTime(appPath + "data/alive/hb");
            final long modifiedTimeOfPhoto = getFileModificationTime(appPath + "data/alive/taking");
            final long modifiedTimeOfUpload = getFileModificationTime(appPath + "data/alive/upload");

            if (((ts - modifiedTimeOfHb) > mTimeOfMpAppAlive) //||
//                    ((ts - modifiedTimeOfPhoto) > mTimeOfMpAppAlive * 4) ||
//                    ((ts - modifiedTimeOfUpload) > mTimeOfMpAppAlive * 4)
            ) {

                if (ts - mTimeToStartMpApp >= 1800000) { // 30 minutes 30 * 60 * 1000
                    String msg = "Restart MpAPP as it is NOT Running hb=" + Long.toString(ts - modifiedTimeOfHb) +
                            " taking=" + Long.toString(ts - modifiedTimeOfPhoto) + " sending=" + Long.toString(ts - modifiedTimeOfUpload) +
                            " Will restart MpApp in " + Long.toString(mDelayedRestartMpTime / 1000) + " seconds";
                    logger.warning(msg);

                    MicroPhotoContext.restartMpApp(context, msg, mDelayedRestartMpTime);

                    mTimeToStartMpApp = ts + mDelayedRestartMpTime / 1000;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private boolean detectMpAppRunning() {
        boolean isMpAppRunning = true;
        FileOutputStream runningFile = null;
        FileLock fileLock = null;
        try {
            String mpappDir = MicroPhotoContext.buildMpAppDir(getApplicationContext());
            File file = new File(mpappDir);
            file = new File(file, "data/alive/running");
            if (file.exists()) {
                runningFile = new FileOutputStream(file);
                fileLock = runningFile.getChannel().tryLock();
                if (fileLock != null && fileLock.isValid()) {
                    isMpAppRunning = false;
                }
                try {
                    if (fileLock != null) {
                        fileLock.close();
                        fileLock = null;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                FilesUtils.closeFriendly(runningFile);
            } else {
                isMpAppRunning = false;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (fileLock != null) {
                    fileLock.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            FilesUtils.closeFriendly(runningFile);
        }

        return isMpAppRunning;
    }

    long getFileModificationTime(String path) {
        File file = new File(path);
        long mt = 0;
        if (file.exists()) {
            mt = file.lastModified();
        }

        return mt;
    }

    public String getMpAppVersion() {
        if (!TextUtils.isEmpty(mMpAppVersion)) {
            return mMpAppVersion;
        }

        String version = buildMpAppVersion(getApplicationContext());
        mMpAppVersion = version;
        return version;
    }

    public static String buildMpAppVersion(Context context) {
        String version = null;
        PackageManager packageManager = context.getPackageManager();
        PackageInfo packageInfo = null;
        try {
            packageInfo = packageManager.getPackageInfo(MicroPhotoContext.PACKAGE_NAME_MPAPP, 0);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        version = packageInfo == null ? "" : packageInfo.versionName;
        return version;
    }

    public static void resetVersions(Context context) {
        mMpAppVersion = null;
        mMpMasterVersion = null;
        mMpAppVersion = buildMpAppVersion(context);
    }

    public String getMasterAppVersion() {
        if (TextUtils.isEmpty(mMpMasterVersion)) {
            mMpMasterVersion = MicroPhotoContext.getVersionName(getApplicationContext());
        }
        return mMpMasterVersion;
    }

    public String getSerialNo() {
        return mSerialNo;
    }

    static String convertSwitch(boolean swtch) {
        return swtch ? "ON" : "OFF";
    }

    public void setMntnMode(boolean mntnMode, boolean quickHbMode) {
        boolean oldMntnMode = mMntnMode;
        boolean oldQuickHbMode = mQuickHbMode;

        mMntnMode = mntnMode;
        mQuickHbMode = quickHbMode;

        if (oldMntnMode != mntnMode || oldQuickHbMode != quickHbMode) {
            MicroPhotoContext.MasterConfig masterConfig = MicroPhotoContext.getMasterConfig(getApplicationContext());

            masterConfig.mntnMode = mntnMode ? 1 : 0;
            masterConfig.quickHbMode = quickHbMode ? 1 : 0;

            MicroPhotoContext.saveMasterConfig(getApplicationContext(), masterConfig);

            logger.warning("MNTN Mode Changed from " + convertSwitch(oldMntnMode) + " to " + convertSwitch(mntnMode)
                    + " Quick Heartbeat from " + convertSwitch(oldQuickHbMode) + " to " + convertSwitch(quickHbMode));
        }

        if (oldQuickHbMode != quickHbMode) {
            // Cancel cuurent job first

            if (quickHbMode) {
                registerHeartbeatTimer();
            }
        }
    }

    public boolean isCriticalTime() {
        if (mSeparateNetwork) {
            return true;
        }

        return (mMasterTimers % 12) == 0;
    }

    private void startMaster(boolean bundleWithMpApp) {
        String masterUrl = MicroPhotoContext.DEFAULT_MASTER_URL;

        MicroPhotoContext.MasterConfig masterConfig = MicroPhotoContext.getMasterConfig(getApplicationContext());
        String url = masterConfig.getUrl();
        if (!TextUtils.isEmpty(url)) {
            masterUrl = url;
        }

        MicroPhotoContext.AppConfig appConfig = MicroPhotoContext.getMpAppConfig(getApplicationContext());
        if (appConfig.heartbeat > 0) {
            mMpHeartbeatDuration = appConfig.heartbeat * 60000;
        }

        logger.warning("Start Mntn report: " + masterUrl + " MntnMode=" + (mMntnMode ? "1" : "0") + " QuickHB=" + (mQuickHbMode ? "1" : "0"));

        String cmdid = appConfig.cmdid;
        if (TextUtils.isEmpty(cmdid)) {
            cmdid = getSerialNo();
        }
        AppMaster appMaster = new AppMaster(this, masterUrl, cmdid, bundleWithMpApp);
        appMaster.start(isCriticalTime());
        mMasterTimers++;
    }

    public static class AlarmReceiver extends BroadcastReceiver {
        private MpMasterService mService;
        public AlarmReceiver() {
            mService = null;
        }
        public AlarmReceiver(MpMasterService service) {
            mService = service;
        }
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (TextUtils.equals(ACTION_HEARTBEAT, action)) {
                boolean keepAlive = intent.getBooleanExtra("keepAlive", false);
                if (keepAlive) {
                    mService.logger.info("KeepAlive Heartbeat Timer Fired ACTION=" + action);
                } else {
                    mService.logger.info("Heartbeat Timer Fired ACTION=" + action);
                }

                mService.mPreviousHB = null;
                mService.mPreviousHeartbeatTime = 0;
                mService.registerHeartbeatTimer();

                mService.buildChargingBatteryVoltage(System.currentTimeMillis());

                if (!keepAlive) {
                    mService.startMaster(false);
                }
                mService.detectMpAppAlive();

            } else if (TextUtils.equals(MicroPhotoContext.ACTION_HEARTBEAT_MP, action)) {
                if (intent.hasExtra("HeartbeatDuration")) {
                    int hbDuration = intent.getIntExtra("HeartbeatDuration", 600000);
                    mService.mMpHeartbeatDuration = hbDuration > 0 ? hbDuration : 600000;
                }
                mService.mPreviousMpHbTime = intent.getLongExtra("HeartbeatTime", System.currentTimeMillis());
                mService.logger.info("Heartbeat Timer Fired By MpAPP ACTION=" + action + " MpHB=" + Long.toString(mService.mMpHeartbeatDuration) + " HBTime =" + mService.mPreviousMpHbTime);

                mService.registerHeartbeatTimer();

                mService.buildChargingBatteryVoltage(System.currentTimeMillis());
                if (!mService.mSeparateNetwork && (!mService.mMntnMode)) {
                    mService.startMaster(true);
                }
                mService.detectMpAppAlive();
            } else if (TextUtils.equals(ACTION_UPDATE_CONFIGS, action)) {
                int restart = intent.getIntExtra("restart", 0);
                mService.logger.info("Update Config Fired ACTION=" + action + " restart=" + restart);
                if (restart != 0) {
                    MicroPhotoContext.restartApp(context, context.getPackageName(), "Config Updated");
                } else {
                    mService.loadConfig();
                    mService.registerHeartbeatTimer();
                }
            } else if (TextUtils.equals(ACTION_UPD_OTA, action)) {

                String cmd = intent.getStringExtra("cmd");
                String msg = intent.getStringExtra("msg");
                // mService.logger.info("cmd=" + cmd + " msg=" + msg);
                if("write".equals(cmd)) {
                    // int progress = Integer.parseInt(msg);
                }
                else if("update".equals(cmd)) {
                    // int progress = Integer.parseInt(msg);
                }
                else if("info".equals(cmd)) {
                }
                else if("error".equals(cmd)) {
                    mService.logger.warning("UPD OTA Failed");
                }
                else if("success".equals(cmd)) {
                    //confirm to reboot device ??
                    mService.logger.warning("UPD OTA Succeeded, will REBOOT device");
                    Handler handler = new Handler();
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            SysApi.reboot(context);
                        }
                    }, 1000);
                }
            } else if (TextUtils.equals(ACTION_INSTALL_RESULT, action)) {
                boolean bSucc = intent.getBooleanExtra("succ", false);
                String pkname = intent.getStringExtra("pkname");
                String msg = intent.getStringExtra("msg");
                // Log.e("_otg_","install result bsuc="+bSucc+",pkname="+pkname+",msg="+msg);
                mService.logger.warning("INSTALL APP result =" + bSucc + ",pkname=" + pkname + ",msg=" + msg);
            } else if (TextUtils.equals(ACTION_UNINSTALL_RESULT, action)) {
                boolean bSucc = intent.getBooleanExtra("succ", false);
                String pkname = intent.getStringExtra("pkname");
                String msg = intent.getStringExtra("msg");
                mService.logger.warning("UNINSTALL APP result =" + bSucc + ",pkname=" + pkname + ",msg=" + msg);
            } else if (TextUtils.equals(ACTION_REQ_RESTART_APP, action)) {

                try {
                    String packageName = intent.getStringExtra("packageName");
                    Intent restartIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);

                    Bundle bundle = intent.getExtras();
                    bundle.remove("packageName");
                    restartIntent.putExtras(bundle);
                    restartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                    context.startActivity(restartIntent);

                } catch (Exception ex) {
                    ex.printStackTrace();
                }


            }
        }
    }

    public void installOTA(final String path) {
        final Context context = getApplicationContext();
        mHander.post(new Runnable() {
            @Override
            public void run() {
                SysApi.installOTA(context, context.getPackageName(), path);
            }
        });
    }

    private void registerHeartbeatTimer() {

        long timeout = mHeartbeatDuration;
        boolean keepAlive = false;
        long currentTimeMs = System.currentTimeMillis();
        if (mMntnMode) {
            if (mQuickHbMode) {
                timeout = mQuickHeartbeatDuration;
            }
            registerHeartbeatTimer(currentTimeMs + timeout * 1000, keepAlive);
        } else {
            long closestTime = -1;
            if (mUsingAbsHbTime) {
                Date dt = new Date();
                long ts = dt.getTime();
                ts -= ts % 1000;

                dt.setHours(0);
                dt.setMinutes(0);
                dt.setSeconds(0);

                long zeroPoint = dt.getTime();
                zeroPoint -= zeroPoint % 1000;
                long offsetTs = (ts - zeroPoint) / 1000;

                if (mAbsHeartbeatTimes != null && mAbsHeartbeatTimes.length > 0) {

                    for (int i = 0; i < mAbsHeartbeatTimes.length; i++) {
                        if (mAbsHeartbeatTimes[i] > offsetTs) {
                            closestTime = mAbsHeartbeatTimes[i];
                            break;
                        }
                    }

                    if (closestTime == -1) {
                        // next day
                        closestTime = mAbsHeartbeatTimes[0] + 86400;
                    }
                } else {
                    closestTime = 9 * 3600 + 13 * 60;
                    if (offsetTs > closestTime) {
                        closestTime += 86400;
                    }
                }

                if (zeroPoint + closestTime * 1000 > currentTimeMs + mMpHeartbeatDuration) {
                    keepAlive = true;
                    registerHeartbeatTimer(currentTimeMs + mMpHeartbeatDuration + 5000, keepAlive);
                } else {
                    registerHeartbeatTimer(zeroPoint + closestTime * 1000, keepAlive);
                }
            } else {
                // mUsingAbsHbTime: false
                if ((mPreviousHeartbeatTime != 0) && (mPreviousHeartbeatTime - currentTimeMs < mHeartbeatDuration * 1000)) {
                    registerHeartbeatTimer(mPreviousHeartbeatTime + mHeartbeatDuration * 1000, keepAlive);
                } else {
                    registerHeartbeatTimer(currentTimeMs + timeout * 1000, keepAlive);
                }
            }
        }
    }

    private void registerHeartbeatTimer(long triggerTime, boolean keepAlive) {

        AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        if (mPreviousHB != null) {
            try {
                logger.info(String.format("Cancel Previos HB: " + format.format(new Date(mPreviousHeartbeatTime))));
                alarmManager.cancel(mPreviousHB);
                mPreviousHB = null;
            } catch (Exception ex) {
            }
            mPreviousHeartbeatTime = 0;
        }
        Intent alarmIntent = new Intent();
        alarmIntent.setAction(ACTION_HEARTBEAT);
        if (keepAlive) {
            alarmIntent.putExtra("keepAlive", keepAlive);
        }
//        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uniqueReqCode, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, keepAlive ? 0 : 1, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        Date dt = new Date(triggerTime);

        if (keepAlive) {
            logger.info(String.format("Register KeepAlive HB: " + format.format(dt)) + " MntnMode=" + (mMntnMode ? "1" : "0") + " QuickHb=" + (mQuickHbMode ? "1" : "0"));
        } else {
            logger.info(String.format("Register HB: " + format.format(dt)) + " MntnMode=" + (mMntnMode ? "1" : "0") + " QuickHb=" + (mQuickHbMode ? "1" : "0"));
        }
        mPreviousHB = pendingIntent;
        mPreviousHeartbeatTime = triggerTime;
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent == null) {
            stopForeground(true);
            stopSelf();
            return START_NOT_STICKY;
        }

        // if user starts the service
        switch (intent.getAction()) {
            case ACTION_START:
                Log.d(TAG, "Received user starts foreground intent");
                startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification());

                connect();

                registerReceiver(mScreenaAtionReceiver, mScreenaAtionReceiver.getFilter());

                String appPath = MicroPhotoContext.buildAppDir(this.getApplicationContext());

                String cmdid = intent.getStringExtra("cmdid");
                if (TextUtils.isEmpty(cmdid)) {
                    mCmdid = SysApi.getSerialNo(getApplicationContext());
                } else {
                    mCmdid = cmdid;
                }

                logger.info("AppPath=" + appPath + " cmdid=" + cmdid);

                // startMaster(false);
                mHander.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        detectMpAppAlive();
                    }
                }, 5000);

                registerHeartbeatTimer();

                startMaster(false);

                break;
            case ACTION_STOP:
                unregisterReceiver(mScreenaAtionReceiver);

                stopForeground(true);
                stopSelf();
                break;
            default:
                stopForeground(true);
                stopSelf();
        }

        return START_NOT_STICKY;
    }

    private void connect() {
        // after 10 seconds its connected
        mHander.postDelayed(
                new Runnable() {
                    public void run() {
                        // Log.d(TAG, "Bluetooth Low Energy device is connected!!");
                        Toast.makeText(getApplicationContext(), "MST Connected!", Toast.LENGTH_SHORT).show();
                        mStateService = STATE_SERVICE.CONNECTED;
                        startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification());
                    }
                }, 10000);

    }

    private Notification prepareNotification() {
        // handle build version above android oreo
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O &&
                mNotificationManager.getNotificationChannel(FOREGROUND_CHANNEL_ID) == null) {
            CharSequence name = getString(R.string.text_name_notification);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(FOREGROUND_CHANNEL_ID, name, importance);
            channel.enableVibration(false);
            mNotificationManager.createNotificationChannel(channel);
        }

        Intent notificationIntent = new Intent(this, MainActivity.class);
        notificationIntent.setAction(ACTION_MAIN);
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        // if min sdk goes below honeycomb
        /*if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        } else {
            notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }*/

        int uniqueReqCode = reqCode.getAndIncrement();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, uniqueReqCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        // make a stop intent
        Intent stopIntent = new Intent(this, MpMasterService.class);
        stopIntent.setAction(ACTION_STOP);
        uniqueReqCode = reqCode.getAndIncrement();
        PendingIntent pendingStopIntent = PendingIntent.getService(this, uniqueReqCode, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification);
        remoteViews.setOnClickPendingIntent(R.id.btn_stop, pendingStopIntent);

        // if it is connected
        switch (mStateService) {
            case STATE_SERVICE.NOT_CONNECTED:
                remoteViews.setTextViewText(R.id.tv_state, "DISCONNECTED");
                break;
            case STATE_SERVICE.CONNECTED:
                remoteViews.setTextViewText(R.id.tv_state, "CONNECTED");
                break;
        }

        // notification builder
        NotificationCompat.Builder notificationBuilder;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            notificationBuilder = new NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID);
        } else {
            notificationBuilder = new NotificationCompat.Builder(this);
        }
        notificationBuilder
                .setContent(remoteViews)
                .setSmallIcon(R.drawable.ic_notification_mst)
                .setCategory(NotificationCompat.CATEGORY_SERVICE)
                .setOnlyAlertOnce(true)
                .setOngoing(true)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
        }

        return notificationBuilder.build();
    }

    public String buildAppDir() {

        String path = Environment.getExternalStorageDirectory().getAbsolutePath();
        if (!path.endsWith(File.separator)) {
            path += File.separator;
        }
        path += getApplicationContext().getPackageName() + File.separator;
        File pathFile = new File(path);
        if (!pathFile.exists() && !pathFile.mkdirs()) {
            return null;
        }

        return path;
    }

    public boolean updateMntn(String url) {
        if (TextUtils.isEmpty(url)) {
            return false;
        }
        String path = buildAppDir();
        path += "data/Master.json";
        JSONObject jsonObject = JSONUtils.loadJson(path);

        String oldUrl = null;
        try {
            oldUrl = jsonObject.getString("url");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        if (TextUtils.equals(url, oldUrl)) {
            return true;
        }

        try {
            jsonObject.put("url", url);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return JSONUtils.saveJson(path, jsonObject);
    }

    public static int getSignalLevel(int num) {
        String result = getSystemProperty("vendor.ril.nw.signalstrength.lte." + Integer.toString(num));
        if (TextUtils.isEmpty(result)) {
            return 0;
        }
        String[] items = result.split(",");
        int rsrp = -140;

        if (items != null && items.length > 0) {
            try {
                rsrp = Integer.parseInt(items[0]);
            } catch (Exception ex) {
            }
        }
        return getSignalLevel(rsrp, num);
    }

    public static int getSignalLevel(long ss, int num) {

        int signalLevel = 0;
        if (ss >= -97) {
            signalLevel = 4;
        } else if (ss >= -107) {
            signalLevel = 3;
        } else if (ss >= -117) {
            signalLevel = 2;
        } else if (ss >= -125) {
            signalLevel = 1;
        } else if (ss >= -140) {
            signalLevel = 0;
        }

        return signalLevel;
    }

    public static String getBatteryVoltage() {
        int val = 0;
        for (int idx = 0; idx < 3; idx++) {
            val = MpMasterService.getInt(115);
            if (val > 0) {
                return Integer.toString(val / 1000) + "." + Integer.toString((val % 1000) / 100);
            }
        }

        return "";
    }

    private void buildChargingBatteryVoltage(long ts) {
        int val = 0;
        for (int idx = 0; idx < 3; idx++) {
            val = MpMasterService.getInt(112);
            if (val > 0) {
                break;
            }
        }

        if (val > 0) {
            if (val > mMaxBCV) {
                mMaxBCV = val;
                mMaxBCVTime = ts;
            }
        }
    }

    public String getAndResetMaxBCV() {
        String val = Integer.toString(mMaxBCV / 1000) + "." + Integer.toString((mMaxBCV % 1000) / 100)
                + "/" + Long.toString(mMaxBCVTime / 1000);

        mMaxBCV = 0;
        mMaxBCVTime = 0;

        return val;
    }

    public static String getChargingBatteryVoltage() {
        int val = 0;
        for (int idx = 0; idx < 3; idx++) {
            val = MpMasterService.getInt(112);
            if (val > 0) {
                return Integer.toString(val / 1000) + "." + Integer.toString((val % 1000) / 100);
            }
        }

        return "";
    }

    public void downloadAndInstall(final String url) {

        final Context context = getApplicationContext();
        final String tempPath = MicroPhotoContext.buildAppDir(context) + File.separator + "tmp";
        File file = new File(tempPath);
        file.mkdirs();
        final String filePath = tempPath + File.separator + "mp.apk";
        Thread th =new Thread(new Runnable() {
            @Override
            public void run() {

                PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
                PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "com.xinyingpower.microphoto:Upgrader");

                FileDownloader fd = new FileDownloader();
                fd.download(url, filePath);

                SysApi.installApk(context, filePath, context.getPackageName(), true);

                try {
                    wl.setReferenceCounted(false);
                    wl.release();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        th.start();
    }

    public void reboot(final int rebootType, String reason) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (rebootType == 0) {
                    logger.warning("Recv REBOOT MpMst APP cmd");
                    Context context = MpMasterService.this.getApplicationContext();
                    MicroPhotoContext.restartApp(context, context.getPackageName(), reason);

                } else {
                    logger.warning("Recv RESET cmd");
                    SysApi.reboot(MpMasterService.this.getApplicationContext());
                }
            }
        };
        mHander.postDelayed(runnable, 1000);
    }

    public int getActiveSlotIndex() {
        Context context = getApplicationContext();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

            SubscriptionManager subscriptionManager = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
            int activeSubId = SubscriptionManager.getActiveDataSubscriptionId();
            int activeSlotIdx = SubscriptionManager.getSlotIndex(activeSubId);
            return activeSlotIdx + 1;
        }
        return 0;
    }

    public void selectSimCard(int num) {
        logger.info("Try to Switch To SimCard: " + Integer.toString(num));

        Context context = getApplicationContext();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

            SubscriptionManager subscriptionManager = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
            int activeSubId = SubscriptionManager.getActiveDataSubscriptionId();
            int activeSlotIdx = SubscriptionManager.getSlotIndex(activeSubId);
            if (activeSlotIdx == (num - 1)) {
                logger.info("Active SimCard is already " + Integer.toString(num));
            } else {
                int subIds[] = subscriptionManager.getSubscriptionIds(num - 1);
                if (subIds != null && subIds.length > 0) {
                    setDefaultDataSubId(subIds[0]);
                    logger.info("Switched To SimCard: " + Integer.toString(num));
                } else {
                    logger.warning("NO available network for simcard " + Integer.toString(num));
                }
            }
        } else {
            SysApi.selectSimCard4Data(context, num);
        }
    }

    private void setDefaultDataSubId(int subId) {
        SubscriptionManager subscriptionManager = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
        try {

            Method method = subscriptionManager.getClass().getDeclaredMethod("setDefaultDataSubId", int.class);
            try {
                method.invoke(subscriptionManager, subId);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                telephonyManager.setDataEnabled(true);
            }
            Method method1 = telephonyManager.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
            method1.invoke(telephonyManager, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private int getDefaultDataSubId() {
        SubscriptionManager subscriptionManager = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
        try {
            Method method = subscriptionManager.getClass().getDeclaredMethod("getDefaultDataSubscriptionId");
            return (int) method.invoke(subscriptionManager);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return 0;
    }

    public static void restartApp(Context context) {
        Intent intent = new Intent(context, MainActivity.class);

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);

        System.exit(0);
    }

    public void updateTime(long ms) {
        Intent intent = new Intent("com.xy.xsetting.action");
        intent.putExtra("cmd", "settime");
        intent.putExtra("timemills", ms);

        intent.setPackage("com.android.systemui");
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        sendBroadcast(intent);
    }

    public void reloadMpAppConfigs() {
        Intent intent = new Intent();
        intent.setAction(MicroPhotoContext.ACTION_UPDATE_CONFIGS_MP);
        intent.setPackage(MicroPhotoContext.PACKAGE_NAME_MPAPP);
        sendBroadcast(intent);
    }

    public static boolean initMpAppConfigurations(final Context context) {

        boolean existed = true;
        String destPath = MicroPhotoContext.buildMpAppDir(context);
        File destPathFile = new File(destPath);
        if (destPathFile.exists()) {
            File dataPath = new File(destPathFile, "data");
            if (dataPath.exists()) {
                File file = new File(dataPath, "App.json");
                if (file.exists()) {
                    return false;
                } else {
                    existed = false;
                }
            } else {
                existed = false;

                try {
                    dataPath.mkdirs();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        } else {
            existed = false;
            File dataPath = new File(destPathFile, "data");
            try {
                dataPath.mkdirs();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        if (existed) {
            return false;
        }

        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(5000);
                } catch (Exception ex) {

                }
                File tmpDestPath = new File(MicroPhotoContext.buildMasterAppDir(context));
                tmpDestPath = new File(tmpDestPath, "mpdata");
                if (tmpDestPath.exists()) {
                    try {
                        tmpDestPath.delete();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                copyAssetsDir(context, "mpapp/data", tmpDestPath.getAbsolutePath());
                MicroPhotoContext.restartMpApp(context, "FIRST Config Init");
            }
        };

        Thread th = new Thread(runnable);
        th.start();

        return true;
    }

    public static boolean initMpMasterConfigurations(final Context context) {
        String destPath = MicroPhotoContext.buildMasterAppDir(context);
        File destPathFile = new File(destPath);
        File dataPath = new File(destPathFile, "data");
        File file = new File(dataPath, "Master.json");
        if (file.exists()) {
            return false;
        }

        if (!dataPath.exists()) {
            try {
                dataPath.mkdirs();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        copyAssetsDir(context, "mpmst", dataPath.getAbsolutePath());
        return true;
    }

    public static void copyAssetsDir(Context context, String directory, String destPath) {
        try {
            AssetManager assetManager = context.getAssets();
            String[] fileList = assetManager.list(directory);
            if (fileList != null && fileList.length > 0) {
                File file = new File(destPath);
                if (!file.exists()) {
                    file.mkdirs();
                }

                if (!directory.endsWith(File.separator)) {
                    directory += File.separator;
                }
                if (!destPath.endsWith(File.separator)) {
                    destPath += File.separator;
                }

                for (String fileName : fileList) {
                    copyAssetsDir(context, directory + fileName, destPath + fileName);
                }
            } else {
                // Try to file
                copyAssetsFile(context, directory, destPath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //  else {//如果是文件
        //                InputStream inputStream=context.getAssets().open(filePath);
        //                File file=new File(context.getFilesDir().getAbsolutePath()+ File.separator+filePath);
        //                Log.i("copyAssets2Phone","file:"+file);
        //                if(!file.exists() || file.length()==0) {
        //                    FileOutputStream fos=new FileOutputStream(file);
        //                    int len=-1;
        //                    byte[] buffer=new byte[1024];
        //                    while ((len=inputStream.read(buffer))!=-1){
        //                        fos.write(buffer,0,len);
        //                    }
        //                    fos.flush();
        //                    inputStream.close();
        //                    fos.close();
        //                    showToast(context,"模型文件复制完毕");
        //                } else {
        //                    showToast(context,"模型文件已存在，无需复制");
        //                }
        //            }
    }


    public static void copyAssetsFile(Context context, String fileName, String destPath) {
        InputStream inputStream = null;
        FileOutputStream fos = null;
        try {
            inputStream = context.getAssets().open(fileName);
            //getFilesDir() 获得当前APP的安装路径 /data/data/包名/files 目录
            File file = new File(destPath);
            if (file.exists()) {
                file.delete();
            }

            fos = new FileOutputStream(file);
            int len=-1;
            byte[] buffer = new byte[1024];
            while ((len=inputStream.read(buffer)) != -1) {
                fos.write(buffer,0, len);
            }
            fos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            FilesUtils.closeFriendly(inputStream);
            FilesUtils.closeFriendly(fos);
        }
    }

    public native static int getInt(int cmd);
    public native static int setInt(int cmd, int val);
    public native static int[] getStats(long ts);
    public native static String getSystemProperty(String key);
    public native static void rebootDevice();
    public native static boolean isMpAppRunning();

    ////////////////////////GPS////////////////////
    
}