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.os.SystemClock; 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.concurrent.atomic.AtomicBoolean; 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"; private static final String ACTION_MP_HEARTBEAT_MANUALLY = "com.xypower.mpapp.ACT_HB_M"; 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; public final static int BROADCAST_REQUEST_CODE_NOTIFICATION = 11; public final static int BROADCAST_REQUEST_CODE_NOTIFICATION_STOP = 12; public final static int BROADCAST_REQUEST_CODE_HEARTBEAT = 15; public final static int BROADCAST_REQUEST_CODE_REAL_HEARTBEAT = 16; public final static int BROADCAST_REQUEST_CODE_SYS_KEEPALIVE = 17; public final static int BROADCAST_REQUEST_CODE_RELAUNCH = 18; //用于创建单例线程保证该线程在项目中唯一 private static final AtomicBoolean created = new AtomicBoolean(false); public MpMasterService() { } @Override public void onLowMemory() { final Context context = getApplicationContext(); try { Intent intent = new Intent(this, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this, BROADCAST_REQUEST_CODE_RELAUNCH, 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(); Log.i(TAG, "MpMasterService::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; 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); intentFilter.addAction(MicroPhotoContext.ACTION_TAKEPHOTO_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) { } } Log.i(TAG, "MpMasterService::onDestroy"); 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 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() { final MpMasterService thisObj = this; Thread th = new Thread(new Runnable() { @Override public void run() { thisObj.detectMpAppAliveImpl(); } }); th.start(); } private boolean isMpAppAlive(Context context) { if (Build.TIME < 1744905600000L) { // 2025-04-18 old firmware // Check Log file time File file = new File(MicroPhotoContext.buildMpAppDir(context) + "logs/log.txt"); if (file.exists()) { return ((System.currentTimeMillis() - file.lastModified()) < 1800000); } else { return false; } } return MicroPhotoContext.isAppAlive(context, MicroPhotoContext.PACKAGE_NAME_MPAPP, MicroPhotoContext.SERVICE_NAME_MPSERVICE); } private void detectMpAppAliveImpl() { try { final Context context = getApplicationContext(); long ts = System.currentTimeMillis(); try { int detectionCnt = 4; if (SystemClock.elapsedRealtime() < 180000) { // In 3 minutes after device reboot detectionCnt = 16; } boolean isMpAppRunning = false; for (int idx = 0; idx < detectionCnt; idx++) { isMpAppRunning = isMpAppAlive(context); if (isMpAppRunning) { break; } sleep(1000); } if (!isMpAppRunning) { // Restart MpApp MpMasterService.restartMpApp(context, "MpMST Alive Detection: NO Service Running"); logger.warning("Restart MpAPP as NO Service Running"); mTimeToStartMpApp = ts; return; } } catch (Exception ex) { ex.printStackTrace(); } long tempduration = mMpHeartbeatDuration; if (mMpHeartbeatDuration < 600000) { tempduration = 600000; } if (mPreviousMpHbTime <= ts && (ts - mPreviousMpHbTime) > tempduration * 3) { // MpApp is not running if (ts - mTimeToStartMpApp >= 1800000) { // 30 minutes 30 * 60 * 1000 MpMasterService.restartMpApp(context.getApplicationContext(), "MpMST Keep Alive Detection"); String prevMpHBTime = ""; String prevRestartTime = ""; try { SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss"); Date date = new Date(mPreviousMpHbTime); prevMpHBTime = format.format(date); Date date2 = new Date(mTimeToStartMpApp); prevRestartTime = format.format(date2); } catch (Exception ex) { ex.printStackTrace(); } logger.warning("Restart MpAPP as it is NOT Running Prev MPAPP HB=" + Long.toString((ts - mPreviousMpHbTime) / 1000) + " MPAPP HBDuration=" + Long.toString(tempduration) + " Prev MpRestart Time=" + prevRestartTime + " last MPAPPHB =" + prevMpHBTime); 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(); } } 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(final Context context, final Intent intent) { mService.mHander.postDelayed(new Runnable() { @Override public void run() { processAction(context, intent); } }, 0); } public void processAction(final Context context, final 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(); 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(); if (!mService.mSeparateNetwork && (!mService.mMntnMode)) { mService.startMaster(true); } mService.detectMpAppAlive(); } else if (TextUtils.equals(MicroPhotoContext.ACTION_TAKEPHOTO_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(); 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) { MpMasterService.restartMpMasterApp(context, "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); } int reqCode = keepAlive ? BROADCAST_REQUEST_CODE_HEARTBEAT : BROADCAST_REQUEST_CODE_REAL_HEARTBEAT; PendingIntent pendingIntent = PendingIntent.getBroadcast(this, reqCode, 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); Intent relaunchIntent = new Intent(); relaunchIntent.putExtra("cmd", "forceLaunch"); relaunchIntent.putExtra("pkname", MicroPhotoContext.PACKAGE_NAME_MPMASTER); relaunchIntent.setAction("com.xy.xsetting.action"); relaunchIntent.setPackage("com.android.systemui"); // getApplicationContext().sendBroadcast(restartIntent); long launchTs = System.currentTimeMillis() + (mHeartbeatDuration + 120) * 1000; PendingIntent sysKAPendingIntent = PendingIntent.getBroadcast(this, BROADCAST_REQUEST_CODE_SYS_KEEPALIVE, relaunchIntent, PendingIntent.FLAG_UPDATE_CURRENT); alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, launchTs, sysKAPendingIntent); logger.info(String.format("Register KeepAlive Launch Clock: " + format.format(new Date(launchTs)))); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "MpMasterService::onStartCommand"); 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); }*/ PendingIntent pendingIntent = PendingIntent.getActivity(this, BROADCAST_REQUEST_CODE_NOTIFICATION, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); // make a stop intent Intent stopIntent = new Intent(this, MpMasterService.class); stopIntent.setAction(ACTION_STOP); PendingIntent pendingStopIntent = PendingIntent.getService(this, BROADCAST_REQUEST_CODE_NOTIFICATION_STOP, 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 String getAndResetMaxBCV() { String val = Integer.toString(mMaxBCV / 1000) + "." + Integer.toString((mMaxBCV % 1000) / 100) + "/" + Long.toString(mMaxBCVTime / 1000); mMaxBCV = 0; mMaxBCVTime = 0; return val; } 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(); MpMasterService.restartMpMasterApp(context, 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); } if (num == 1) { // If it's back to card 1, let MpAPP send heartbeat manully after 10s mHander.postDelayed(new Runnable() { @Override public void run() { Intent intent = new Intent(); intent.setAction(ACTION_MP_HEARTBEAT_MANUALLY); intent.setPackage(MicroPhotoContext.PACKAGE_NAME_MPAPP); sendBroadcast(intent); } }, 10000); } } 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 restartMpMasterApp(Context context, String reason) { Intent intent = new Intent(context, MainActivity.class); if (intent != null) { if (!TextUtils.isEmpty(reason)) { intent.putExtra("reason", reason); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } context.startActivity(intent); System.exit(0); } //重启MpApp应用 public static void restartMpApp(Context context, String reason) { forceStopMpApp(context); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //// 然后启动目标应用 try { Intent intent = context.getPackageManager().getLaunchIntentForPackage(MicroPhotoContext.PACKAGE_NAME_MPAPP); if (intent != null) { intent.putExtra("noDelay", 1); if (!TextUtils.isEmpty(reason)) { intent.putExtra("reason", reason); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } } catch (Exception e) { e.printStackTrace(); } } private static void forceStopMpApp(Context context) { if (Build.TIME < 1744905600000L) { SysApi.forceStopApp(context, MicroPhotoContext.PACKAGE_NAME_MPAPP); } else { int pid = MicroPhotoContext.getProcessIdOfService(context, MicroPhotoContext.PACKAGE_NAME_MPAPP, MicroPhotoContext.SERVICE_NAME_MPSERVICE); if (pid != 0) { android.os.Process.killProcess(pid); } } } //根据包名重启应用 public static void restartAppByPackage(Context context, String packagename, String reason) { forceStopMpApp(context); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //// 然后启动目标应用 try { Intent intent = context.getPackageManager().getLaunchIntentForPackage(packagename); if (intent != null) { intent.putExtra("noDelay", 1); if (!TextUtils.isEmpty(reason)) { intent.putExtra("reason", reason); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } } catch (Exception e) { e.printStackTrace(); } } 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() { sleep(5000); 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()); MpMasterService.restartMpApp(context.getApplicationContext(), "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/data", 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); } } private static void sleep(long ms) { try { Thread.sleep(ms); } catch (Exception ex) { } } 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 long[] getBatteryInfo(); public native static String getSystemProperty(String key); public native static void rebootDevice(); //用于将差分包跟老app合并生成新App public native static boolean applyPatch(String oldFile, String patchFile, String newFile); //用于将新app跟老app计算生成差分包 public native static boolean applyDiff(String oldFile, String patchFile, String newFile); ////////////////////////GPS//////////////////// }