Explorar el Código

## [1.0.15] version 15 - 2020-07-16
### Added
- 增加保活模块
### Changed
- 恢复保活的背景音乐播放
- 断TCP时亮屏保活

allen hace 4 años
padre
commit
0b1a9c5e80
Se han modificado 51 ficheros con 4139 adiciones y 25 borrados
  1. 2 0
      .idea/gradle.xml
  2. 0 5
      app/src/main/AndroidManifest.xml
  3. 2 7
      app/src/main/code/com/wdkl/app/ncs/application/Application.kt
  4. 0 1
      common/build.gradle
  5. BIN
      common/libs/keepalive-release.aar
  6. 1 0
      home/build.gradle
  7. 5 0
      home/src/main/AndroidManifest.xml
  8. 10 0
      home/src/main/code/com/wdkl/ncs/android/component/home/activity/WatchHome2Activity.kt
  9. 1 0
      home/src/main/code/com/wdkl/ncs/android/component/home/service/TcpHandleService.kt
  10. 3 7
      app/src/main/code/com/wdkl/app/ncs/service/WdKeepAliveService.kt
  11. 4 4
      home/src/main/res/layout/activity_sip_voip_audio_ringing.xml
  12. 1 0
      keepalive/.gitignore
  13. 58 0
      keepalive/build.gradle
  14. 180 0
      keepalive/proguard-rules.pro
  15. 17 0
      keepalive/src/androidTest/java/com/wdkl/ncs/ExampleInstrumentedTest.java
  16. 104 0
      keepalive/src/main/AndroidManifest.xml
  17. 58 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/KeepAliveApplication.java
  18. 57 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/singlepixel/ScreenManager.java
  19. 58 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/singlepixel/ScreenReceiverUtil.java
  20. 88 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/singlepixel/SinglePixelActivity.java
  21. 74 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/Authenticator.java
  22. 27 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/AuthenticatorService.java
  23. 47 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/StubProvider.java
  24. 31 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/SyncAdapter.java
  25. 35 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/SyncService.java
  26. 187 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/AndroidUtil.java
  27. 67 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/AppUtils.java
  28. 109 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/FileUtils.java
  29. 101 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/ForegroundNotificationUtils.java
  30. 202 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/JumpWindowPemiManagement.java
  31. 114 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/NotificationSetUtil.java
  32. 203 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/RandomUtil.java
  33. 224 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/SPUtils.java
  34. 66 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/SpManager.java
  35. 579 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/TimeUtils.java
  36. 30 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/AbsServiceConnection.java
  37. 32 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/JobSchedulerService.java
  38. 109 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/PlayMusicService.java
  39. 44 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/WakeUpReceiver.java
  40. 220 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/WatchDogService.java
  41. 59 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/WatchProcessPrefHelper.java
  42. 215 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/work/AbsWorkService.java
  43. 227 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/work/DaemonEnv.java
  44. 447 0
      keepalive/src/main/java/com/wdkl/ncs/keepbackground/work/IntentWrapper.java
  45. BIN
      keepalive/src/main/res/drawable/icon1.webp
  46. BIN
      keepalive/src/main/res/raw/no_notice.mp3
  47. 3 0
      keepalive/src/main/res/values/strings.xml
  48. 16 0
      keepalive/src/main/res/values/styles.xml
  49. 4 0
      keepalive/src/main/res/xml/net_config.xml
  50. 17 0
      keepalive/src/test/java/com/wdkl/ncs/ExampleUnitTest.java
  51. 1 1
      settings.gradle

+ 2 - 0
.idea/gradle.xml

@@ -14,6 +14,7 @@
             <option value="$PROJECT_DIR$/app" />
             <option value="$PROJECT_DIR$/common" />
             <option value="$PROJECT_DIR$/home" />
+            <option value="$PROJECT_DIR$/keepalive" />
             <option value="$PROJECT_DIR$/libwebrtc" />
             <option value="$PROJECT_DIR$/middleware" />
             <option value="$PROJECT_DIR$/resource" />
@@ -24,5 +25,6 @@
         <option name="useQualifiedModuleNames" value="true" />
       </GradleProjectSettings>
     </option>
+    <option name="offlineMode" value="true" />
   </component>
 </project>

+ 0 - 5
app/src/main/AndroidManifest.xml

@@ -81,11 +81,6 @@
                     android:scheme="https"/>
             </intent-filter>
         </activity>
-        <service android:name=".service.WdKeepAliveService">
-            <intent-filter>
-                <action android:name="com.wdkl.app.ncs.service.WdKeepAliveService"/>
-            </intent-filter>
-        </service>
         <meta-data
             android:name="android.max_aspect"
             android:value="2.2" />

+ 2 - 7
app/src/main/code/com/wdkl/app/ncs/application/Application.kt

@@ -1,14 +1,12 @@
 package com.wdkl.app.ncs.application
 
 import com.enation.javashop.android.jrouter.JRouter
-import com.wdkl.ncs.android.lib.base.BaseApplication
 import com.enation.javashop.net.engine.config.NetEngineConfig
 import com.enation.javashop.net.engine.plugin.exception.RestfulExceptionInterceptor
 import com.enation.javashop.utils.base.config.BaseConfig
-import com.sdk.keepbackground.work.DaemonEnv
-import com.wdkl.app.ncs.service.WdKeepAliveService
 import com.wdkl.core.socket.SocketManager
 import com.wdkl.core.voip.VoipEvent
+import com.wdkl.ncs.android.lib.base.BaseApplication
 import com.wdkl.skywebrtc.SkyEngineKit
 
 /**
@@ -28,10 +26,7 @@ class Application : BaseApplication() {
      */
     override fun onCreate() {
         super.onCreate()
-        //保活守护进程
-        DaemonEnv.init(this);
-        //启动work服务
-        DaemonEnv.startServiceSafelyWithData(this,WdKeepAliveService::class.java)
+
         initRouter()
         initFrame()
         initLeaks()

+ 0 - 1
common/build.gradle

@@ -191,7 +191,6 @@ dependencies {
      * 汉字辅助
      */
     compile files('libs/chinese2py.jar')
-    compile files('libs/keepalive-release.aar')
 
     /**
      * 二维码扫描

BIN
common/libs/keepalive-release.aar


+ 1 - 0
home/build.gradle

@@ -124,6 +124,7 @@ dependencies {
     compile project(':webrtc')
     //compile project(':libwebrtc')
     compile project(':rtc-chat')
+    compile project(':keepalive')
 }
 
 /**

+ 5 - 0
home/src/main/AndroidManifest.xml

@@ -38,6 +38,11 @@
                 <action android:name="com.wdkl.ncs.android.component.home.service.TcpHandleService" />
             </intent-filter>
         </service>
+        <service android:name="com.wdkl.ncs.android.component.home.service.WdKeepAliveService">
+            <intent-filter>
+                <action android:name="com.wdkl.app.ncs.service.WdKeepAliveService"/>
+            </intent-filter>
+        </service>
 <!--        <receiver android:name=".broadcast.MyMediaButtonReceiver">-->
 <!--            <intent-filter>-->
 <!--                <action android:name="android.intent.action.MEDIA_BUTTON" />-->

+ 10 - 0
home/src/main/code/com/wdkl/ncs/android/component/home/activity/WatchHome2Activity.kt

@@ -31,6 +31,7 @@ import com.wdkl.ncs.android.component.home.broadcast.MyMediaButtonReceiver
 import com.wdkl.ncs.android.component.home.databinding.WatchActivityHome2Binding
 import com.wdkl.ncs.android.component.home.launch.HomeLaunch
 import com.wdkl.ncs.android.component.home.service.TcpHandleService
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
 import com.wdkl.ncs.android.component.home.settingconfig.SettingConfig
 import com.wdkl.ncs.android.component.home.util.NetHelper
 import com.wdkl.ncs.android.component.home.util.SpeechUtil
@@ -50,6 +51,7 @@ import com.wdkl.ncs.android.middleware.model.vo.DeviceVO
 import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
 import com.wdkl.ncs.android.middleware.tcp.TcpClient
 import com.wdkl.ncs.android.middleware.utils.MessageEvent
+import com.wdkl.ncs.keepbackground.work.DaemonEnv
 import io.reactivex.Observable
 import kotlinx.android.synthetic.main.watch_activity_home.battery_warning_tv
 import kotlinx.android.synthetic.main.watch_activity_home.call_records_linlyout
@@ -105,6 +107,14 @@ class WatchHome2Activity : BaseActivity<WatchHomeActivityPresenter, WatchActivit
         } else {
         }
 
+        //保活守护进程
+        DaemonEnv.init(this);
+        //請求用戶忽略电池优化
+        val reason = "轨迹跟踪服务的持续运行"
+        DaemonEnv.whiteListMatters(this, reason)
+        //启动work服务
+        DaemonEnv.startServiceSafelyWithData(this, WdKeepAliveService::class.java)
+
         Constants.imei = Util.getIMEI(this)
         Log.i(TAG, "IMEI " + Util.getIMEI(this))
 

+ 1 - 0
home/src/main/code/com/wdkl/ncs/android/component/home/service/TcpHandleService.kt

@@ -172,6 +172,7 @@ class TcpHandleService : Service(){
             }
             //网络断开,1000ms重连
             Constants.EVENT_TCP_BREAK->{
+                Util.wakeUpAndUnlock(this)
                 Log.w(TAG, "EVENT TCP BREAK")
             }
         }

+ 3 - 7
app/src/main/code/com/wdkl/app/ncs/service/WdKeepAliveService.kt

@@ -1,16 +1,14 @@
-package com.wdkl.app.ncs.service
+package com.wdkl.ncs.android.component.home.service
 
 import android.content.Intent
 import android.os.Handler
 import android.os.IBinder
-import android.os.Looper
 import android.os.Messenger
 import android.util.Log
-import com.sdk.keepbackground.work.AbsWorkService
+import com.wdkl.ncs.keepbackground.work.AbsWorkService
 import com.wdkl.ncs.android.component.home.util.NetHelper
 import com.wdkl.ncs.android.component.home.util.Util
 import com.wdkl.ncs.android.component.nursehome.common.Constants
-import com.wdkl.ncs.android.lib.utils.AppTool
 import com.wdkl.ncs.android.middleware.tcp.TcpClient
 import com.wdkl.ncs.android.middleware.tcp.TcpClientHandler
 import java.util.*
@@ -33,7 +31,6 @@ class WdKeepAliveService : AbsWorkService() {
     }
 
     override fun stopWork() {
-        TODO("Not yet implemented")
     }
 
     override fun onBindService(p0: Intent?, p1: Void?): IBinder {
@@ -41,7 +38,6 @@ class WdKeepAliveService : AbsWorkService() {
     }
 
     override fun onServiceKilled() {
-        TODO("Not yet implemented")
     }
 
     var checkNetStateTimer: Timer? = null
@@ -61,7 +57,7 @@ class WdKeepAliveService : AbsWorkService() {
                     TcpClient.getInstance().sendMsg("0")
                     if (NetHelper.getInstance().getNetworkState(this@WdKeepAliveService) == NetHelper.NETWORK_NONE || !TcpClientHandler.getConnected()) {
                         Log.w(TAG, "断网唤醒CPU")
-                        Util.getCpuWakeLock(this@WdKeepAliveService).acquire(3000)
+                        Util.wakeUpAndUnlock(this@WdKeepAliveService)
                     }
                 }
             }

+ 4 - 4
home/src/main/res/layout/activity_sip_voip_audio_ringing.xml

@@ -69,18 +69,18 @@
             android:id="@+id/change_over_relayout"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginRight="28px"
+            android:layout_marginRight="100dp"
             android:layout_gravity="center" >
             <ImageView
                 android:id="@+id/change_over_image"
-                android:layout_width="45px"
-                android:layout_height="45px"
+                android:layout_width="45dp"
+                android:layout_height="45dp"
                 android:src="@drawable/yu_yin_wei_chu_li" />
             <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_centerInParent="true"
-                android:textSize="14px"
+                android:textSize="14dp"
                 android:textColor="#ffffff"
                 android:text="转接"/>
 

+ 1 - 0
keepalive/.gitignore

@@ -0,0 +1 @@
+/build

+ 58 - 0
keepalive/build.gradle

@@ -0,0 +1,58 @@
+apply plugin: 'com.android.library'
+
+//aar名字
+//def SDK_NAME = "keepalive-1.1.5.aar"
+//
+////去除多余类配置
+//configurations.maybeCreate("exclude")
+//artifacts.add("exclude", file(SDK_NAME))
+//先生成aar后再加上去除配置
+//apply from: "${project.projectDir.absoluteFile}\\excludeAar.gradle"
+
+android {
+    compileSdkVersion 28
+    buildToolsVersion "26.0.2"
+
+
+    defaultConfig {
+        minSdkVersion 18
+        targetSdkVersion 28
+        versionCode 2
+        versionName "1.1"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+    }
+
+    buildTypes {
+        release {
+            //开启代码混淆
+            minifyEnabled true
+            //Zipalign优化
+            zipAlignEnabled true
+            // 移除无用的resource文件
+//            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+//可根据自己需求更改-+.693  gradlew againMakeJar
+//    task againMakeJar(type: Copy) {
+//        delete 'mylib/keeplive.aar' //删除之前的旧jar包
+//        from('build/outputs/aar/') //从这个目录下取出默认jar包
+//        into('/') //将jar包输出到指定目录下
+//        include('keepalive-release.aar')
+//        rename('keepalive-release.aar', SDK_NAME) //自定义jar包的名字
+//    }
+//    againMakeJar.dependsOn(build)
+}
+ext.rxJavaVersion = '2.1.2'
+ext.rxAndroidVersion = '2.0.1'
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    testImplementation 'junit:junit:4.12'
+    implementation "com.android.support:appcompat-v7:$support_library_version"
+
+//    implementation "io.reactivex.rxjava2:rxjava:${rxJavaVersion}"
+//    implementation "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}"
+}

+ 180 - 0
keepalive/proguard-rules.pro

@@ -0,0 +1,180 @@
+#-----------基本配置--------------
+# 代码混淆压缩比,在0~7之间,默认为5,一般不需要改
+-optimizationpasses 5
+
+# 混淆时不使用大小写混合,混淆后的类名为小写
+-dontusemixedcaseclassnames
+
+# 指定不去忽略非公共的库的类
+-dontskipnonpubliclibraryclasses
+
+# 指定不去忽略非公共的库的类的成员
+-dontskipnonpubliclibraryclassmembers
+
+# 不做预校验,可加快混淆速度
+# preverify是proguard的4个步骤之一
+# Android不需要preverify,去掉这一步可以加快混淆速度
+-dontpreverify
+
+# 不优化输入的类文件
+-dontoptimize
+
+# 混淆时生成日志文件,即映射文件
+-verbose
+
+# 指定映射文件的名称
+-printmapping proguardMapping.txt
+
+#混淆时所采用的算法
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+# 保护代码中的Annotation不被混淆
+-keepattributes *Annotation*
+
+# 忽略警告
+-ignorewarning
+
+# 保护泛型不被混淆
+-keepattributes Signature
+
+# 抛出异常时保留代码行号
+-keepattributes SourceFile,LineNumberTable
+
+#-----------需要保留的东西--------------
+# 保留所有的本地native方法不被混淆
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# 保留了继承自Activity、Application、Fragment这些类的子类
+-keep public class * extends android.app.Fragment
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class * extends android.view.View
+-keep public class com.android.vending.licensing.ILicensingService
+
+# support-v4
+-dontwarn android.support.v4.**
+-keep class android.support.v4.** { *; }
+-keep interface android.support.v4.** { *; }
+-keep public class * extends android.support.v4.**
+# support-v7
+-dontwarn android.support.v7.**                                             #去掉警告
+-keep class android.support.v7.** { *; }                                    #过滤android.support.v7
+-keep interface android.support.v7.app.** { *; }
+-keep public class * extends android.support.v7.**
+
+#----------------保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在------------------------------------
+-keepclasseswithmembernames class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembernames class * {
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+# 保持自定义控件类不被混淆,指定格式的构造方法不去混淆
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context);
+    public <init>(android.content.Context, android.util.AttributeSet);
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+# 保持自定义控件类不被混淆
+-keep public class * extends android.view.View {
+    public <init>(android.content.Context);
+    public <init>(android.content.Context, android.util.AttributeSet);
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+    public void set*(...);
+    *** get*();
+}
+
+# 保留在Activity中的方法参数是View的方法
+# 从而我们在layout里边编写onClick就不会被影响
+-keepclassmembers class * extends android.app.Activity {
+    public void *(android.view.View);
+}
+
+# 保留枚举 enum 类不被混淆
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# 保留 Parcelable 不被混淆
+-keep class * implements android.os.Parcelable {
+    public static final android.os.Parcelable$Creator *;
+}
+
+# 保留 Serializable 不被混淆
+-keepnames class * implements java.io.Serializable
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    private static final java.io.ObjectStreamField[] serialPersistentFields;
+    !static !transient <fields>;
+    !private <fields>;
+    !private <methods>;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+
+# 不混淆资源类
+-keepclassmembers class **.R$* { *; }
+
+# 对于带有回调函数onXXEvent()的,不能被混淆
+-keepclassmembers class * {
+    void *(**On*Event);
+}
+# WebView
+-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
+   public *;
+}
+-keepclassmembers class * extends android.webkit.WebViewClient {
+    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
+    public boolean *(android.webkit.WebView, java.lang.String);
+}
+-keepclassmembers class * extends android.webkit.WebViewClient {
+    public void *(android.webkit.WebView, jav.lang.String);
+}
+
+# 保留实体类和成员不被混淆(根据具体情况修改entity的路径)
+-keep class com.smart.tvpos.bean.**{*;}
+
+
+
+#高德地图混淆配置
+ # 3D 地图 V5.0.0之前:
+    -keep   class com.amap.api.maps.**{*;}
+    -keep   class com.autonavi.amap.mapcore.*{*;}
+    -keep   class com.amap.api.trace.**{*;}
+
+  #  3D 地图 V5.0.0之后:
+    -keep   class com.amap.api.maps.**{*;}
+    -keep   class com.autonavi.**{*;}
+    -keep   class com.amap.api.trace.**{*;}
+
+  #  定位
+    -keep class com.amap.api.location.**{*;}
+    -keep class com.amap.api.fence.**{*;}
+    -keep class com.autonavi.aps.amapapi.model.**{*;}
+
+   # 搜索
+    -keep   class com.amap.api.services.**{*;}
+
+   # 2D地图
+    -keep class com.amap.api.maps2d.**{*;}
+    -keep class com.amap.api.mapcore2d.**{*;}
+
+    #导航
+    -keep class com.amap.api.navi.**{*;}
+    -keep class com.autonavi.**{*;}
+
+-keep class com.wdkl.ncs.keepbackground.work.** { *; }
+

+ 17 - 0
keepalive/src/androidTest/java/com/wdkl/ncs/ExampleInstrumentedTest.java

@@ -0,0 +1,17 @@
+package com.wdkl.ncs;
+
+///**
+// * Instrumented test, which will execute on an Android device.
+// *
+// * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+// */
+//@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+//    @Test
+//    public void useAppContext() {
+//        // Context of the app under test.
+//        Context appContext = InstrumentationRegistry.getTargetContext();
+//
+//        assertEquals("com.sdk.keepbackground.test", appContext.getPackageName());
+//    }
+}

+ 104 - 0
keepalive/src/main/AndroidManifest.xml

@@ -0,0 +1,104 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.wdkl.ncs.keepbackground">
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <!--<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>-->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
+
+    <!--<uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"/>-->
+
+    <application
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/net_config"
+        android:allowBackup="true"
+        android:supportsRtl="true">
+        <receiver
+            android:name="com.wdkl.ncs.keepbackground.watch.WakeUpReceiver"
+            android:process=":watch">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+                <action android:name="android.intent.action.USER_PRESENT" />
+                <action android:name="android.intent.action.MEDIA_MOUNTED" />
+                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
+                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver
+            android:name="com.wdkl.ncs.keepbackground.watch.WakeUpReceiver$StartWatchReceiver"
+            android:process=":watch">
+            <intent-filter>
+                <action android:name="com.sdk.START_JOB_ALARM_SUB" />
+            </intent-filter>
+        </receiver>
+        <!-- 广播接收者  receiver 进程-->
+        <receiver
+            android:name="com.wdkl.ncs.keepbackground.watch.WakeUpReceiver$WakeUpAutoStartReceiver"
+            android:process=":watch">
+            <!-- 手机启动 -->
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+            </intent-filter>
+            <!-- 软件安装卸载-->
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_ADDED" />
+                <action android:name="android.intent.action.PACKAGE_REMOVED" />
+
+                <data android:scheme="package" />
+            </intent-filter>
+            <!-- 网络监听 -->
+            <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+                <action android:name="android.net.wifi.WIFI_STATE_CJANGED" />
+                <action android:name="android.net.wifi.STATE_CHANGE" />
+            </intent-filter>
+            <!-- 文件挂载 -->
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_EJECT" />
+                <action android:name="android.intent.action.MEDIA_MOUNTED" />
+
+                <data android:scheme="file" />
+            </intent-filter>
+
+        </receiver>
+
+
+        <!-- 守护进程 watch -->
+        <service
+            android:name="com.wdkl.ncs.keepbackground.watch.JobSchedulerService"
+            android:enabled="true"
+            android:exported="true"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:process=":watch" />
+
+        <service
+            android:name="com.wdkl.ncs.keepbackground.watch.WatchDogService"
+            android:process=":watch" />
+
+        <activity
+            android:name="com.wdkl.ncs.keepbackground.singlepixel.SinglePixelActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
+            android:excludeFromRecents="true"
+            android:finishOnTaskLaunch="false"
+            android:launchMode="singleInstance"
+            android:theme="@style/SingleActivityStyle" />
+
+        <service
+            android:name=".watch.PlayMusicService"
+            android:process=":watch" />
+
+    </application>
+</manifest>

+ 58 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/KeepAliveApplication.java

@@ -0,0 +1,58 @@
+package com.wdkl.ncs.keepbackground;
+
+import android.app.ActivityManager;
+import android.app.Application;
+import android.content.Context;
+import android.util.Log;
+
+
+import java.util.List;
+
+/**
+ * Desc    :
+ * Creator : sj
+ * Date    : 2019/8/13
+ * Modifier:
+ */
+public class KeepAliveApplication extends Application {
+    private static KeepAliveApplication instance;
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        instance = this;
+
+        String processName = getProcessName(this.getApplicationContext());
+        if ("com.sdk.keepbackground".equals(processName)){
+            // 主进程 进行一些其他的操作
+            Log.d("sj_keep", "启动主进程");
+        }else if ("com.sdk.keepbackground:work".equals(processName)){
+            Log.d("sj_keep", "启动了工作进程");
+        }else if ("com.sdk.keepbackground:watch".equals(processName)){
+            // 这里要设置下看护进程所启动的主进程信息
+            Log.d("sj_keep", "启动了观察进程");
+        }
+    }
+
+    //创建一个静态的方法,以便获取context对象
+    public static KeepAliveApplication getInstance() {
+        return instance;
+    }
+
+    public static String getProcessName(Context context){
+        int pid = android.os.Process.myPid();
+        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        if (am == null) {
+            return null;
+        }
+        List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
+        if (processes == null){
+            return null;
+        }
+        for (ActivityManager.RunningAppProcessInfo info : processes){
+            if (info.pid == pid){
+                return info.processName;
+            }
+        }
+        return null;
+    }
+}

+ 57 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/singlepixel/ScreenManager.java

@@ -0,0 +1,57 @@
+package com.wdkl.ncs.keepbackground.singlepixel;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * 对1像素Activity进行防止内存泄露的处理,新建一个ScreenManager类
+ */
+public class ScreenManager {
+
+    private static final String TAG = ScreenManager.class.getSimpleName();
+    private static ScreenManager sInstance;
+    private Context mContext;
+    private WeakReference<Activity> mActivity;
+
+    private ScreenManager(Context mContext) {
+        this.mContext = mContext;
+    }
+
+    public static ScreenManager getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new ScreenManager(context);
+        }
+        return sInstance;
+    }
+
+    /** 获得SinglePixelActivity的引用
+     * @param activity
+     */
+    public void setSingleActivity(Activity activity) {
+        mActivity = new WeakReference<>(activity);
+    }
+
+    /**
+     * 启动SinglePixelActivity
+     */
+    public void startActivity() {
+        Intent intent = new Intent(mContext, SinglePixelActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    /**
+     * 结束SinglePixelActivity
+     */
+    public void finishActivity() {
+        if (mActivity != null) {
+            Activity activity = mActivity.get();
+            if (activity != null) {
+                activity.finish();
+            }
+        }
+    }
+}

+ 58 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/singlepixel/ScreenReceiverUtil.java

@@ -0,0 +1,58 @@
+package com.wdkl.ncs.keepbackground.singlepixel;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+/**
+ * 对广播进行监听,封装为一个ScreenReceiverUtil类,进行锁屏解锁的广播动态注册监听
+ */
+public class ScreenReceiverUtil {
+    private Context mContext;
+    private SreenBroadcastReceiver mScreenReceiver;
+    private ScreenManager mScreenManager;
+
+    public ScreenReceiverUtil(Context mContext) {
+        this.mContext = mContext;
+    }
+
+    public void startScreenReceiverListener() {
+        // 动态启动广播接收器
+        this.mScreenReceiver = new SreenBroadcastReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        mContext.registerReceiver(mScreenReceiver, filter);
+        mScreenManager = ScreenManager.getInstance(mContext);
+    }
+
+    public void stopScreenReceiverListener() {
+        if (null != mScreenReceiver) {
+            mContext.unregisterReceiver(mScreenReceiver);
+            mScreenReceiver = null;
+        }
+        mScreenManager = null;
+    }
+
+    public class SreenBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
+                if (mScreenManager != null) {
+                    mScreenManager.startActivity();
+                }
+                Log.d("sj_keep", "打开了1像素Activity");
+            } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁
+                if (mScreenManager != null) {
+                    mScreenManager.finishActivity(); // 解锁
+                }
+                Log.d("sj_keep", "关闭了1像素Activity");
+            }
+        }
+    }
+}

+ 88 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/singlepixel/SinglePixelActivity.java

@@ -0,0 +1,88 @@
+package com.wdkl.ncs.keepbackground.singlepixel;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.Window;
+import android.view.WindowManager;
+
+
+import com.wdkl.ncs.keepbackground.watch.WatchDogService;
+
+
+
+/**
+ * 该Activity的View只要设置为1像素然后设置在Window对象上即可。在Activity的onDestroy周期中进行保活服务的存活判断从而唤醒服务。
+ * 运行在:watch进程, 为了提高watch进程的优先级 oom_adj值越小,优先级越高。
+ * copy from shihu wang
+ */
+public class SinglePixelActivity extends Activity {
+
+    private static final String TAG = SinglePixelActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate( Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Window mWindow = getWindow();
+        mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
+        WindowManager.LayoutParams attrParams = mWindow.getAttributes();
+        attrParams.x = 0;
+        attrParams.y = 0;
+        attrParams.height = 1;
+        attrParams.width = 1;
+        mWindow.setAttributes(attrParams);
+        ScreenManager.getInstance(this).setSingleActivity(this);
+    }
+
+    @Override
+    protected void onDestroy() {
+//        if (!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) {
+        Log.d("sj_keep", " 1 像素Activity --- onDestroy");
+            Intent intentAlive = new Intent(this, WatchDogService.class);
+            startService(intentAlive);
+//        }
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (isScreenOn()) {
+            finishSelf();
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent motionEvent) {
+        finishSelf();
+        return super.dispatchTouchEvent(motionEvent);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent motionEvent) {
+        finishSelf();
+        return super.onTouchEvent(motionEvent);
+    }
+
+    public void finishSelf() {
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+
+    private boolean isScreenOn() {
+        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
+            return powerManager.isInteractive();
+        } else {
+            return powerManager.isScreenOn();
+        }
+    }
+}

+ 74 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/Authenticator.java

@@ -0,0 +1,74 @@
+package com.wdkl.ncs.keepbackground.sync;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ */
+
+public class Authenticator extends AbstractAccountAuthenticator {
+
+    final Context context;
+
+    public Authenticator(Context context) {
+        super(context);
+        this.context = context;
+    }
+
+    @Override
+    public Bundle addAccount(AccountAuthenticatorResponse response,
+                             String accountType, String authTokenType,
+                             String[] requiredFeatures, Bundle options)
+            throws NetworkErrorException {
+        Intent intent = new Intent();
+        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
+                response);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+        return bundle;
+    }
+
+    @Override
+    public Bundle confirmCredentials(AccountAuthenticatorResponse response,
+                                     Account account, Bundle options) throws NetworkErrorException {
+        return null;
+    }
+
+    @Override
+    public Bundle editProperties(AccountAuthenticatorResponse response,
+                                 String accountType) {
+        return null;
+    }
+
+    @Override
+    public Bundle getAuthToken(AccountAuthenticatorResponse response,
+                               Account account, String authTokenType, Bundle options)
+            throws NetworkErrorException {
+        return null;
+    }
+
+    @Override
+    public String getAuthTokenLabel(String authTokenType) {
+        return null;
+    }
+
+    @Override
+    public Bundle hasFeatures(AccountAuthenticatorResponse response,
+                              Account account, String[] features)
+            throws NetworkErrorException {
+        return null;
+    }
+
+    @Override
+    public Bundle updateCredentials(AccountAuthenticatorResponse response,
+                                    Account account, String authTokenType, Bundle options)
+            throws NetworkErrorException {
+        return null;
+    }
+}

+ 27 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/AuthenticatorService.java

@@ -0,0 +1,27 @@
+package com.wdkl.ncs.keepbackground.sync;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * 授权此服务提供给SyncAdapter framework,用于调用Authenticator的方法
+ */
+
+public class AuthenticatorService extends Service {
+
+    private Authenticator mAuthenticator;
+    public AuthenticatorService() {
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mAuthenticator = new Authenticator(this);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mAuthenticator.getIBinder();
+    }
+}

+ 47 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/StubProvider.java

@@ -0,0 +1,47 @@
+package com.wdkl.ncs.keepbackground.sync;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+
+
+/**
+ * ContentProvider  跨进程
+ */
+
+public class StubProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}

+ 31 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/SyncAdapter.java

@@ -0,0 +1,31 @@
+package com.wdkl.ncs.keepbackground.sync;
+
+/**
+ */
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+import com.wdkl.ncs.keepbackground.watch.WatchDogService;
+
+
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+
+
+    private Context mContext;
+
+    public SyncAdapter(Context context, boolean autoInitialize) {
+        super(context, autoInitialize);
+        this.mContext = context;
+    }
+
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
+        DaemonEnv.startServiceSafely(mContext, WatchDogService.class);
+    }
+}

+ 35 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/sync/SyncService.java

@@ -0,0 +1,35 @@
+package com.wdkl.ncs.keepbackground.sync;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ *
+ * 此服务需能交给操作系统使用
+ */
+
+public class SyncService extends Service {
+
+
+    // Storage for an instance of the sync adapter
+    private static SyncAdapter sSyncAdapter = null;
+    // Object to use as a thread-safe lock
+    private static final Object sSyncAdapterLock = new Object();
+
+    public SyncService() {
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        synchronized (sSyncAdapterLock) {
+            sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sSyncAdapter.getSyncAdapterBinder();
+    }
+}

+ 187 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/AndroidUtil.java

@@ -0,0 +1,187 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.SystemClock;
+
+
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 手机信息 & MAC地址 & 开机时间等
+ */
+public class AndroidUtil {
+
+    /**
+     * 获取mac地址 适用android6.0以上
+     *
+     * @return
+     */
+    public static String getMacAddr() {
+        try {
+            List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
+            for (NetworkInterface nif : all) {
+                if (!nif.getName().equalsIgnoreCase("wlan0")) continue;
+
+                byte[] macBytes = nif.getHardwareAddress();
+                if (macBytes == null) {
+                    return "";
+                }
+
+                StringBuilder res1 = new StringBuilder();
+                for (byte b : macBytes) {
+                    res1.append(String.format("%02X:", b));
+                }
+
+                if (res1.length() > 0) {
+                    res1.deleteCharAt(res1.length() - 1);
+                }
+                return res1.toString();
+            }
+        } catch (Exception ex) {
+
+        }
+        return "02:00:00:00:00:00";
+    }
+
+    /**
+     * 获取 MAC 地址
+     * <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+     */
+    public static String getMacAddress(Context context) {
+
+        try {
+            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                return getMacAddr();
+            }
+            //wifi mac地址
+            WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+            WifiInfo info = wifi.getConnectionInfo();
+            String mac = info.getMacAddress();
+            return info.getMacAddress();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static String getSerialNumber(){
+
+        String serial = null;
+
+        try {
+
+            Class<?> c = Class.forName("android.os.SystemProperties");
+
+            Method get =c.getMethod("get", String.class);
+
+            serial = (String)get.invoke(c, "ro.serialno");
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return serial;
+
+    }
+
+    /**
+     * 获取本地Ip地址
+     *
+     * @param context
+     * @return
+     */
+    public static String getLocalIpAddress(Context context) {
+        try {
+            WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+            WifiInfo info = wifi.getConnectionInfo();
+            int ipAddress = info.getIpAddress();
+            String Ipv4Address = InetAddress
+                    .getByName(
+                            String.format("%d.%d.%d.%d", (ipAddress & 0xff),
+                                    (ipAddress >> 8 & 0xff),
+                                    (ipAddress >> 16 & 0xff),
+                                    (ipAddress >> 24 & 0xff))).getHostAddress()
+                    .toString();
+            return Ipv4Address;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 获取 开机时间
+     */
+    public static String getBootTimeString() {
+        long ut = SystemClock.elapsedRealtime() / 1000;
+        int h = (int) ((ut / 3600));
+        int m = (int) ((ut / 60) % 60);
+        return h + ":" + m;
+    }
+
+    public static String printSystemInfo() {
+        Date date = new Date(System.currentTimeMillis());
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String time = dateFormat.format(date);
+        StringBuilder sb = new StringBuilder();
+        sb.append("_______  系统信息  ").append(time).append(" ______________");
+        sb.append("\nID                 :").append(Build.ID);
+        sb.append("\nBRAND              :").append(Build.BRAND);
+        sb.append("\nMODEL              :").append(Build.MODEL);
+        sb.append("\nRELEASE            :").append(Build.VERSION.RELEASE);
+        sb.append("\nSDK                :").append(Build.VERSION.SDK);
+
+        sb.append("\n_______ OTHER _______");
+        sb.append("\nBOARD              :").append(Build.BOARD);
+        sb.append("\nPRODUCT            :").append(Build.PRODUCT);
+        sb.append("\nDEVICE             :").append(Build.DEVICE);
+        sb.append("\nFINGERPRINT        :").append(Build.FINGERPRINT);
+        sb.append("\nHOST               :").append(Build.HOST);
+        sb.append("\nTAGS               :").append(Build.TAGS);
+        sb.append("\nTYPE               :").append(Build.TYPE);
+        sb.append("\nTIME               :").append(Build.TIME);
+        sb.append("\nINCREMENTAL        :").append(Build.VERSION.INCREMENTAL);
+
+        sb.append("\n_______ CUPCAKE-3 _______");
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
+            sb.append("\nDISPLAY            :").append(Build.DISPLAY);
+        }
+
+        sb.append("\n_______ DONUT-4 _______");
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) {
+            sb.append("\nSDK_INT            :").append(Build.VERSION.SDK_INT);
+            sb.append("\nMANUFACTURER       :").append(Build.MANUFACTURER);
+            sb.append("\nBOOTLOADER         :").append(Build.BOOTLOADER);
+            sb.append("\nCPU_ABI            :").append(Build.CPU_ABI);
+            sb.append("\nCPU_ABI2           :").append(Build.CPU_ABI2);
+            sb.append("\nHARDWARE           :").append(Build.HARDWARE);
+            sb.append("\nUNKNOWN            :").append(Build.UNKNOWN);
+            sb.append("\nCODENAME           :").append(Build.VERSION.CODENAME);
+        }
+
+        sb.append("\n_______ GINGERBREAD-9 _______");
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+            sb.append("\nSERIAL             :").append(Build.SERIAL);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 判断gps是否开启
+     * @param context
+     * @return
+     */
+    public static final boolean gpsOpened(final Context context) {
+        LocationManager locationManager  = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+    }
+}

+ 67 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/AppUtils.java

@@ -0,0 +1,67 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.text.TextUtils;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/8/2
+ *     desc  : App相关工具类
+ * </pre>
+ */
+public class AppUtils {
+
+//    public static void installApk(Context context, File file) {
+//        Intent intent = new Intent(Intent.ACTION_VIEW);
+//        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+//        Uri data;
+//        // 判断版本大于等于7.0
+//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+//            // "net.csdn.blog.ruancoder.fileprovider"即是在清单文件中配置的authorities
+//            data = FileProvider.getUriForFile(context, "org.sdk.coolfar_sdk.fileprovider", file);
+//            // 给目标应用一个临时授权
+//            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+//        } else {
+//            data = Uri.fromFile(file);
+//        }
+//        intent.setDataAndType(data, "application/vnd.android.package-archive");
+//        context.startActivity(intent);
+//    }
+
+//    public static void upDateApp(Context context, File file) {
+//        installApk(context, file);
+//        android.os.Process.killProcess(android.os.Process.myPid());
+//    }
+
+    private static final String KEY_SERIALNUMBER = "key_serialnumber";
+
+    /**
+     * uuid  获取uuid
+     * @return
+     */
+    public static String getUUID() {
+        String uuid = AndroidUtil.getMacAddress(DaemonEnv.app);
+        if(TextUtils.isEmpty(uuid) || "02:00:00:00:00:00".equals(uuid)) {
+            uuid = AndroidUtil.getSerialNumber();
+        }
+        if(TextUtils.isEmpty(uuid)) {
+            uuid = SpManager.getInstance().getString(KEY_SERIALNUMBER);
+            if(TextUtils.isEmpty(uuid)) {
+                uuid = RandomUtil.getRandom(RandomUtil.NUMBERS_AND_LETTERS, 32);
+                SpManager.getInstance().putString(KEY_SERIALNUMBER, uuid);
+            }
+        }
+        return uuid;
+    }
+
+    public static void checkUUID() {
+        String localUUID = SpManager.getInstance().getString(KEY_SERIALNUMBER);
+        if(!TextUtils.isEmpty(localUUID) && !localUUID.equals(getUUID())) {
+            SpManager.getInstance().putString(KEY_SERIALNUMBER, null);
+        }
+    }
+
+}

+ 109 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/FileUtils.java

@@ -0,0 +1,109 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.RandomAccessFile;
+
+/**
+ * Created by Ray on 2020/1/6.
+ */
+public class FileUtils {
+    private static final String TAG = "FileUtils";
+
+    //包名地址
+    public static final String FILE_PKG_DIRRECT = "/mnt/sdcard/keepalive/";
+    public static final String FILE_SERVICE_NAME = "service.txt";
+    public static final String FILE_PKG_PATH = FILE_PKG_DIRRECT + FILE_SERVICE_NAME;
+
+    public static String  readTxtFile(String path) {
+        StringBuilder content = new StringBuilder();     //文件内容字符串
+        File file = new File(path);    //打开文件
+        if (!file.exists()) {
+            return content.toString();
+        }
+        if (file.isDirectory())    //如果path是传递过来的参数,可以做一个非目录的判断
+        {
+            Log.d("TestFile", "The File doesn't not exist.");
+        } else {
+            try {
+                InputStream instream = new FileInputStream(file);
+                InputStreamReader inputreader = new InputStreamReader(instream);
+                BufferedReader buffreader = new BufferedReader(inputreader);
+                String line;
+                //分行读取
+                while ((line = buffreader.readLine()) != null) {
+                    content.append(line);
+                }
+                instream.close();
+            } catch (java.io.FileNotFoundException e) {
+                Log.d("TestFile", "The File doesn't not exist.");
+            } catch (IOException e) {
+                Log.d("TestFile", e.getMessage());
+            }
+        }
+        return content.toString();
+    }
+
+
+    public static void writeTxtToFile(final String strcontent, final String path, final String name) {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                makeFilePath(path, name);
+                String strFilePath = path + name;
+                if (!strFilePath.contains(".txt")) {
+                    strFilePath += ".txt";
+                }
+//            String strContent = TimeUtils.milliseconds2String(System.currentTimeMillis()) + strcontent + "\r\n";
+                try {
+                    File file = new File(strFilePath);
+                    if (file.exists()) {
+                        file.delete();
+                    }
+                    file.getParentFile().mkdirs();
+                    file.createNewFile();
+                    RandomAccessFile raf = new RandomAccessFile(file, "rwd");
+                    raf.seek(file.length());
+                    raf.write(strcontent.getBytes("UTF-8"));
+                    raf.close();
+                } catch (Exception e) {
+                    Log.e("TestFile", "Error on write File:" + e);
+                }
+            }
+        }).start();
+    }
+
+
+    private static File makeFilePath(String filePath, String fileName) {
+        File file = null;
+        makeRootDirectory(filePath);
+        try {
+            file = new File(filePath + fileName);
+            if (!file.exists()) {
+                file.createNewFile();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return file;
+    }
+
+    private static void makeRootDirectory(String filePath) {
+        File file = null;
+        try {
+            file = new File(filePath);
+            if (!file.exists()) {
+                file.mkdir();
+            }
+        } catch (Exception e) {
+            Log.i("error:", e + "");
+        }
+    }
+
+}

+ 101 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/ForegroundNotificationUtils.java

@@ -0,0 +1,101 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.os.Build;
+
+import com.wdkl.ncs.keepbackground.R;
+
+public class ForegroundNotificationUtils {
+    // 通知渠道的id
+    private static final String CHANNEL_ID = "保活图腾";
+    private static final int CHANNEL_POSITION = 1;
+    public static void startForegroundNotification(Service service){
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
+            //启动前台服务而不显示通知的漏洞已在 API Level 25 修复
+            NotificationManager manager = (NotificationManager)service.getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationChannel Channel = new NotificationChannel(CHANNEL_ID,"主服务",NotificationManager.IMPORTANCE_DEFAULT);
+            Channel.enableLights(true);//设置提示灯
+            Channel.setLightColor(Color.GREEN);//设置提示灯颜色
+            Channel.setShowBadge(true);//显示logo
+            Channel.setDescription("");//设置描述
+            Channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); //设置锁屏可见 VISIBILITY_PUBLIC=可见
+            Channel.enableVibration(false);
+            Channel.setSound(null,null);
+            manager.createNotificationChannel(Channel);
+
+            Notification notification = new Notification.Builder(service,CHANNEL_ID)
+                    .setContentTitle("主服务")//标题
+                    .setContentText("运行中...")//内容
+                    .setWhen(System.currentTimeMillis())
+                    .setSmallIcon(R.drawable.icon1)//小图标一定需要设置,否则会报错(如果不设置它启动服务前台化不会报错,但是你会发现这个通知不会启动),如果是普通通知,不设置必然报错
+                    .setLargeIcon(BitmapFactory.decodeResource(service.getResources(),R.drawable.icon1))
+                    .build();
+            service.startForeground(CHANNEL_POSITION,notification);//服务前台化只能使用startForeground()方法,不能使用 notificationManager.notify(1,notification); 这个只是启动通知使用的,使用这个方法你只需要等待几秒就会发现报错了
+        }else {
+            //利用漏洞在 API Level 18 及以上的 Android 系统中,启动前台服务而不显示通知
+//            service.startForeground(Foreground_ID, new Notification());
+            Notification notification = new Notification.Builder(service)
+                    .setContentTitle("主服务")//设置标题
+                    .setContentText("运行中...")//设置内容
+                    .setWhen(System.currentTimeMillis())//设置创建时间
+                    .setSmallIcon(R.drawable.icon1)//设置状态栏图标
+                    .setLargeIcon(BitmapFactory.decodeResource(service.getResources(),R.drawable.icon1))//设置通知栏图标
+                    .build();
+            service.startForeground(CHANNEL_POSITION,notification);
+        }
+    }
+
+    public static void deleteForegroundNotification(Service service){
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            NotificationManager mNotificationManager = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationChannel mChannel = mNotificationManager.getNotificationChannel(CHANNEL_ID);
+            if (null != mChannel) {
+                mNotificationManager.deleteNotificationChannel(CHANNEL_ID);
+            }
+        }else {
+            NotificationManager notificationManager = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
+            notificationManager.cancel(CHANNEL_POSITION);
+        }
+    }
+
+
+//    private void oldStartForegNotify(){
+        //启动前台服务而不显示通知的漏洞已在 API Level 25 修复,大快人心!
+//        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
+//            //利用漏洞在 API Level 17 及以下的 Android 系统中,启动前台服务而不显示通知
+//            startForeground(HASH_CODE, new Notification());
+//            //利用漏洞在 API Level 18 及以上的 Android 系统中,启动前台服务而不显示通知
+//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+//                Boolean needStartWorkService = needStartWorkService(null, 0, 0);
+//                DaemonEnv.startServiceSafely(AbsWorkService.this,
+//                        WorkNotificationService.class,
+//                        needStartWorkService);
+//            }
+//        }
+//    }
+
+//    public static class WorkNotificationService extends Service {
+//
+//        /**
+//         * 利用漏洞在 API Level 18 及以上的 Android 系统中,启动前台服务而不显示通知
+//         */
+//        @Override
+//        public int onStartCommand(Intent intent, int flags, int startId) {
+//            startForeground(1, new Notification());
+//            stopSelf();
+//            return START_STICKY;
+//        }
+//
+//        @Override
+//        public IBinder onBind(Intent intent) {
+//            return null;
+//        }
+//    }
+}

+ 202 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/JumpWindowPemiManagement.java

@@ -0,0 +1,202 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+import android.support.v7.appcompat.BuildConfig;
+import android.util.Log;
+
+public class JumpWindowPemiManagement {
+    private static final String TAG = "JumpPermission";
+
+    /**
+     * Build.MANUFACTURER
+     */
+    private static final String MANUFACTURER_HUAWEI = "Huawei";//华为
+    private static final String MANUFACTURER_MEIZU = "Meizu";//魅族
+    private static final String MANUFACTURER_XIAOMI = "Xiaomi";//小米
+    private static final String MANUFACTURER_SONY = "Sony";//索尼
+    private static final String MANUFACTURER_OPPO = "OPPO";
+    private static final String MANUFACTURER_LG = "LG";
+    private static final String MANUFACTURER_VIVO = "vivo";
+    private static final String MANUFACTURER_SAMSUNG = "samsung";//三星
+    private static final String MANUFACTURER_LETV = "Letv";//乐视
+    private static final String MANUFACTURER_ZTE = "ZTE";//中兴
+    private static final String MANUFACTURER_YULONG = "YuLong";//酷派
+    private static final String MANUFACTURER_LENOVO = "LENOVO";//联想
+
+    /**
+     * 此函数可以自己定义
+     *
+     * @param activity
+     */
+    public static void GoToSetting(Activity activity) {
+        try {
+            switch (Build.MANUFACTURER) {
+                case MANUFACTURER_HUAWEI:
+                    Huawei(activity);
+                    break;
+                case MANUFACTURER_MEIZU:
+                    Meizu(activity);
+                    break;
+                case MANUFACTURER_XIAOMI:
+                    Xiaomi(activity);
+                    break;
+                case MANUFACTURER_SONY:
+                    Sony(activity);
+                    break;
+                case MANUFACTURER_OPPO:
+                    OPPO(activity);
+                    break;
+                case MANUFACTURER_LG:
+                    LG(activity);
+                    break;
+                case MANUFACTURER_LETV:
+                    Letv(activity);
+                    break;
+                default:
+                    ApplicationInfo(activity);
+                    Log.e(TAG, "目前暂不支持此系统");
+                    break;
+            }
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+            goToWindow(activity);
+        }
+    }
+
+    public static boolean hasWindowPei(Context context) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (context.checkSelfPermission(Manifest.permission.SYSTEM_ALERT_WINDOW) == PackageManager.PERMISSION_GRANTED || Settings.canDrawOverlays(context)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void Huawei(Activity activity) {
+        Intent intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    public static void Meizu(Activity activity) {
+        Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        activity.startActivity(intent);
+    }
+
+    public static void Xiaomi(Activity activity) {
+        try {
+            Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+            ComponentName componentName = new ComponentName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
+            intent.setComponent(componentName);
+            intent.putExtra("extra_pkgname", BuildConfig.APPLICATION_ID);
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            goToWindow(activity);
+        }
+    }
+
+    public static void Sony(Activity activity) {
+        Intent intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        ComponentName comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    public static void OPPO(Activity activity) {
+        Intent intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        ComponentName comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    public static void LG(Activity activity) {
+        Intent intent = new Intent("android.intent.action.MAIN");
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    public static void Letv(Activity activity) {
+        Intent intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        ComponentName comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    /**
+     * 只能打开到自带安全软件
+     *
+     * @param activity
+     */
+    public static void _360(Activity activity) {
+        Intent intent = new Intent("android.intent.action.MAIN");
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
+        ComponentName comp = new ComponentName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    public static final int CHECK_PEERISSION_CODE = 1001;
+
+    /**
+     * 应用信息界面
+     *
+     * @param context
+     */
+    public static void ApplicationInfo(Activity context) {
+        Uri packageURI = Uri.parse("package:" + context.getPackageName());
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
+        (context).startActivityForResult(intent, CHECK_PEERISSION_CODE);
+    }
+
+    /**
+     * 系统设置界面
+     *
+     * @param activity
+     */
+    public static void SystemConfig(Activity activity) {
+        Intent intent = new Intent(Settings.ACTION_SETTINGS);
+        activity.startActivity(intent);
+    }
+
+    public static void goToWindow(Context context) {
+        try {
+            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                    Uri.parse("package:" + context.getPackageName()));
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            goToAppSetting(context);
+        }
+    }
+
+    //进入应用设置
+    public static void goToAppSetting(Context context) {
+        if (!(context instanceof Activity)) return;
+        Uri packageURI = Uri.parse("package:" + context.getPackageName());
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
+        ((Activity) context).startActivityForResult(intent, CHECK_PEERISSION_CODE);
+    }
+}

+ 114 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/NotificationSetUtil.java

@@ -0,0 +1,114 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.annotation.TargetApi;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.support.v4.app.NotificationManagerCompat;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Created by HaiyuKing
+ * Used 判断是否开启消息通知,没有开启的话跳转到手机系统设置界面
+ * 参考:https://blog.csdn.net/lidayingyy/article/details/81778894
+ * https://www.jianshu.com/p/205bc87cac29
+ * https://blog.csdn.net/yrmao9893/article/details/74607402/
+ * https://www.meiwen.com.cn/subject/qcklpftx.html
+ */
+public class NotificationSetUtil {
+
+    //判断是否需要打开设置界面
+    public static void OpenNotificationSetting(Context context, OnNextLitener mOnNextLitener) {
+        if (!isNotificationEnabled(context)) {
+            mOnNextLitener.onNext(false);
+        } else {
+            if (mOnNextLitener != null) {
+                mOnNextLitener.onNext(true);
+            }
+        }
+    }
+
+    //判断该app是否打开了通知
+    /**
+     * 可以通过NotificationManagerCompat 中的 areNotificationsEnabled()来判断是否开启通知权限。NotificationManagerCompat 在 android.support.v4.app包中,是API 22.1.0 中加入的。而 areNotificationsEnabled()则是在 API 24.1.0之后加入的。
+     * areNotificationsEnabled 只对 API 19 及以上版本有效,低于API 19 会一直返回true
+     * */
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    public static boolean isNotificationEnabled(Context context) {
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
+            NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
+            boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled();
+            return areNotificationsEnabled;
+        }
+
+        String CHECK_OP_NO_THROW = "checkOpNoThrow";
+        String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
+
+        AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        ApplicationInfo appInfo = context.getApplicationInfo();
+        String pkg = context.getApplicationContext().getPackageName();
+        int uid = appInfo.uid;
+
+        Class appOpsClass = null;
+        /* Context.APP_OPS_MANAGER */
+        try {
+            appOpsClass = Class.forName(AppOpsManager.class.getName());
+            Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
+                    String.class);
+            Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
+
+            int value = (Integer) opPostNotificationValue.get(Integer.class);
+            return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+
+    }
+
+    //打开手机设置页面
+    /**
+     * 假设没有开启通知权限,点击之后就需要跳转到 APP的通知设置界面,对应的Action是:Settings.ACTION_APP_NOTIFICATION_SETTINGS, 这个Action是 API 26 后增加的
+     * 如果在部分手机中无法精确的跳转到 APP对应的通知设置界面,那么我们就考虑直接跳转到 APP信息界面,对应的Action是:Settings.ACTION_APPLICATION_DETAILS_SETTINGS*/
+    public static void gotoSet(Context context) {
+
+        Intent intent = new Intent();
+        if (Build.VERSION.SDK_INT >= 26) {
+            // android 8.0引导
+            intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
+            intent.putExtra("android.provider.extra.APP_PACKAGE", context.getPackageName());
+        } else if (Build.VERSION.SDK_INT >= 21) {
+            // android 5.0-7.0
+            intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
+            intent.putExtra("app_package", context.getPackageName());
+            intent.putExtra("app_uid", context.getApplicationInfo().uid);
+        } else {
+            // 其他
+            intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
+            intent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        }
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    /*=====================添加Listener回调================================*/
+    public interface OnNextLitener {
+        /**
+         * 不需要设置通知的下一步
+         */
+        void onNext(Boolean isOpen);
+    }
+
+    private OnNextLitener mOnNextLitener;
+
+    public void setOnNextLitener(OnNextLitener mOnNextLitener) {
+        this.mOnNextLitener = mOnNextLitener;
+    }
+}
+

+ 203 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/RandomUtil.java

@@ -0,0 +1,203 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import java.util.Random;
+
+/**
+ * 随机工具类
+ */
+public class RandomUtil {
+    public static final String NUMBERS_AND_LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    public static final String NUMBERS = "0123456789";
+    public static final String LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    public static final String LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
+
+    private RandomUtil() {
+        throw new AssertionError();
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of uppercase, lowercase letters and numbers
+     *
+     * @param length
+     * @return
+     * @see RandomUtil#getRandom(String source, int length)
+     */
+    public static String getRandomNumbersAndLetters(int length) {
+        return getRandom(NUMBERS_AND_LETTERS, length);
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of numbers
+     *
+     * @param length
+     * @return
+     * @see RandomUtil#getRandom(String source, int length)
+     */
+    public static String getRandomNumbers(int length) {
+        return getRandom(NUMBERS, length);
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of uppercase and lowercase letters
+     *
+     * @param length
+     * @return
+     * @see RandomUtil#getRandom(String source, int length)
+     */
+    public static String getRandomLetters(int length) {
+        return getRandom(LETTERS, length);
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of uppercase letters
+     *
+     * @param length
+     * @return
+     * @see RandomUtil#getRandom(String source, int length)
+     */
+    public static String getRandomCapitalLetters(int length) {
+        return getRandom(CAPITAL_LETTERS, length);
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of lowercase letters
+     *
+     * @param length
+     * @return
+     * @see RandomUtil#getRandom(String source, int length)
+     */
+    public static String getRandomLowerCaseLetters(int length) {
+        return getRandom(LOWER_CASE_LETTERS, length);
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of chars in source
+     *
+     * @param source
+     * @param length
+     * @return <ul>
+     * <li>if source is null or empty, return null</li>
+     * <li>else see {@link RandomUtil#getRandom(char[] sourceChar, int length)}</li>
+     * </ul>
+     */
+    public static String getRandom(String source, int length) {
+        return source == null ? null : getRandom(source.toCharArray(), length);
+    }
+
+    /**
+     * get a fixed-length random string, its a mixture of chars in sourceChar
+     *
+     * @param sourceChar
+     * @param length
+     * @return <ul>
+     * <li>if sourceChar is null or empty, return null</li>
+     * <li>if length less than 0, return null</li>
+     * </ul>
+     */
+    public static String getRandom(char[] sourceChar, int length) {
+        if (sourceChar == null || sourceChar.length == 0 || length < 0) {
+            return null;
+        }
+
+        StringBuilder str = new StringBuilder(length);
+        Random random = new Random();
+        for (int i = 0; i < length; i++) {
+            str.append(sourceChar[random.nextInt(sourceChar.length)]);
+        }
+        return str.toString();
+    }
+
+    /**
+     * get random int between 0 and max
+     *
+     * @param max
+     * @return <ul>
+     * <li>if max <= 0, return 0</li>
+     * <li>else return random int between 0 and max</li>
+     * </ul>
+     */
+    public static int getRandom(int max) {
+        return getRandom(0, max);
+    }
+
+    /**
+     * get random int between min and max
+     *
+     * @param min
+     * @param max
+     * @return <ul>
+     * <li>if min > max, return 0</li>
+     * <li>if min == max, return min</li>
+     * <li>else return random int between min and max</li>
+     * </ul>
+     */
+    public static int getRandom(int min, int max) {
+        if (min > max) {
+            return 0;
+        }
+        if (min == max) {
+            return min;
+        }
+        return min + new Random().nextInt(max - min);
+    }
+
+    /**
+     * Shuffling algorithm, Randomly permutes the specified array using a default source of randomness
+     */
+    public static boolean shuffle(Object[] objArray) {
+        if (objArray == null) {
+            return false;
+        }
+        return shuffle(objArray, getRandom(objArray.length));
+    }
+
+    /**
+     * Shuffling algorithm, Randomly permutes the specified array
+     */
+    public static boolean shuffle(Object[] objArray, int shuffleCount) {
+        int length;
+        if (objArray == null || shuffleCount < 0 || (length = objArray.length) < shuffleCount) {
+            return false;
+        }
+
+        for (int i = 1; i <= shuffleCount; i++) {
+            int random = getRandom(length - i);
+            Object temp = objArray[length - i];
+            objArray[length - i] = objArray[random];
+            objArray[random] = temp;
+        }
+        return true;
+    }
+
+    /**
+     * Shuffling algorithm, Randomly permutes the specified int array using a default source of randomness
+     */
+    public static int[] shuffle(int[] intArray) {
+        if (intArray == null) {
+            return null;
+        }
+
+        return shuffle(intArray, getRandom(intArray.length));
+    }
+
+    /**
+     * Shuffling algorithm, Randomly permutes the specified int array
+     */
+    public static int[] shuffle(int[] intArray, int shuffleCount) {
+        int length;
+        if (intArray == null || shuffleCount < 0 || (length = intArray.length) < shuffleCount) {
+            return null;
+        }
+
+        int[] out = new int[shuffleCount];
+        for (int i = 1; i <= shuffleCount; i++) {
+            int random = getRandom(length - i);
+            out[i - 1] = intArray[random];
+            int temp = intArray[length - i];
+            intArray[length - i] = intArray[random];
+            intArray[random] = temp;
+        }
+        return out;
+    }
+}

+ 224 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/SPUtils.java

@@ -0,0 +1,224 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+
+import java.util.Map;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/8/2
+ *     desc  : SP相关工具类
+ * </pre>
+ */
+public class SPUtils {
+
+    private SharedPreferences sp;
+    private SharedPreferences.Editor editor;
+
+    /**
+     * SPUtils构造函数
+     * <p>在Application中初始化</p>
+     *
+     * @param spName  spName
+     */
+    public SPUtils(String spName) {
+        sp = DaemonEnv.app.getSharedPreferences(spName, Context.MODE_PRIVATE);
+        editor = sp.edit();
+        editor.apply();
+    }
+
+    /**
+     * SP中写入String类型value
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void putString(String key, String value) {
+        editor.putString(key, value).apply();
+    }
+
+    /**
+     * SP中读取String
+     *
+     * @param key 键
+     * @return 存在返回对应值,不存在返回默认值{@code null}
+     */
+    public String getString(String key) {
+        return getString(key, null);
+    }
+
+    /**
+     * SP中读取String
+     *
+     * @param key          键
+     * @param defaultValue 默认值
+     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
+     */
+    public String getString(String key, String defaultValue) {
+        return sp.getString(key, defaultValue);
+    }
+
+    /**
+     * SP中写入int类型value
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void putInt(String key, int value) {
+        editor.putInt(key, value).apply();
+    }
+
+    /**
+     * SP中读取int
+     *
+     * @param key 键
+     * @return 存在返回对应值,不存在返回默认值-1
+     */
+    public int getInt(String key) {
+        return getInt(key, -1);
+    }
+
+    /**
+     * SP中读取int
+     *
+     * @param key          键
+     * @param defaultValue 默认值
+     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
+     */
+    public int getInt(String key, int defaultValue) {
+        return sp.getInt(key, defaultValue);
+    }
+
+    /**
+     * SP中写入long类型value
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void putLong(String key, long value) {
+        editor.putLong(key, value).apply();
+    }
+
+    /**
+     * SP中读取long
+     *
+     * @param key 键
+     * @return 存在返回对应值,不存在返回默认值-1
+     */
+    public long getLong(String key) {
+        return getLong(key, -1L);
+    }
+
+    /**
+     * SP中读取long
+     *
+     * @param key          键
+     * @param defaultValue 默认值
+     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
+     */
+    public long getLong(String key, long defaultValue) {
+        return sp.getLong(key, defaultValue);
+    }
+
+    /**
+     * SP中写入float类型value
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void putFloat(String key, float value) {
+        editor.putFloat(key, value).apply();
+    }
+
+    /**
+     * SP中读取float
+     *
+     * @param key 键
+     * @return 存在返回对应值,不存在返回默认值-1
+     */
+    public float getFloat(String key) {
+        return getFloat(key, -1f);
+    }
+
+    /**
+     * SP中读取float
+     *
+     * @param key          键
+     * @param defaultValue 默认值
+     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
+     */
+    public float getFloat(String key, float defaultValue) {
+        return sp.getFloat(key, defaultValue);
+    }
+
+    /**
+     * SP中写入boolean类型value
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void putBoolean(String key, boolean value) {
+        editor.putBoolean(key, value).apply();
+    }
+
+    /**
+     * SP中读取boolean
+     *
+     * @param key 键
+     * @return 存在返回对应值,不存在返回默认值{@code false}
+     */
+    public boolean getBoolean(String key) {
+        return getBoolean(key, false);
+    }
+
+    /**
+     * SP中读取boolean
+     *
+     * @param key          键
+     * @param defaultValue 默认值
+     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        return sp.getBoolean(key, defaultValue);
+    }
+
+    /**
+     * SP中获取所有键值对
+     *
+     * @return Map对象
+     */
+    public Map<String, ?> getAll() {
+        return sp.getAll();
+    }
+
+    /**
+     * SP中移除该key
+     *
+     * @param key 键
+     */
+    public void remove(String key) {
+        editor.remove(key).apply();
+    }
+
+    /**
+     * SP中是否存在该key
+     *
+     * @param key 键
+     * @return {@code true}: 存在<br>{@code false}: 不存在
+     */
+    public boolean contains(String key) {
+        return sp.contains(key);
+    }
+
+    /**
+     * SP中清除所有数据
+     */
+    public void clear() {
+        editor.clear().apply();
+    }
+}

+ 66 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/SpManager.java

@@ -0,0 +1,66 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+
+/**
+ * 
+ * SharedPreferences 对象管理类
+ *
+ */
+public class SpManager extends SPUtils {
+
+	private static final String SPNAME = "tourguide_sp";
+
+	private static SpManager instance;
+
+	private SpManager(String spName) {
+		super(spName);
+	}
+
+	/**
+	 * 
+	 * 
+	 * @return
+	 */
+	public static SpManager getInstance(){
+		if(instance == null) {
+			instance = new SpManager(SPNAME);
+		}
+		return instance;
+	}
+
+	/**
+	 * 
+	 * 存储的SharedPreferences的键
+	 * 
+	 * @author Administrator
+	 *
+	 */
+	public static final class Keys {
+
+		//搜索记录
+		public static final String SEARCH_HISTORY = "search_history";
+
+		//搜索记录
+		public static final String SEARCH_CITYSCENIC_HISTORY = "search_cityscenic_history";
+
+		//是否已经签到
+		public static final String ISSIGN = "issign";
+
+		//签到时间
+		public static final String SIGNTIME = "signtime";
+
+		//用户编号
+		public static final String USERNUMBER = "userNumber";
+
+		//用户token
+		public static final String USERTOKEN = "userToken";
+
+		//是否已执行过白名单操作
+		public static final String  SP_IS_ACTION_WHITE_POWER = "isDoPowerWhite";
+		//是否开启过悬浮窗权限
+		public static final String  IS_HINT_SYSTEM_WINDOW = "isDoWindowWhite";
+		public static final String  WORK_SERVICE = "workService";
+
+	}
+
+}

+ 579 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/utils/TimeUtils.java

@@ -0,0 +1,579 @@
+package com.wdkl.ncs.keepbackground.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+
+public class TimeUtils {
+    /**
+     * 毫秒与毫秒的倍数
+     */
+    private static final int MSEC = 1;
+    /**
+     * 秒与毫秒的倍数
+     */
+    private static final int SEC  = 1000;
+    /**
+     * 分与毫秒的倍数
+     */
+     public static final int MIN  = 60000;
+    /**
+     * 时与毫秒的倍数
+     */
+    private static final int HOUR = 3600000;
+    /**
+     * 天与毫秒的倍数
+     */
+    public static final int DAY  = 86400000;
+
+    private TimeUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    public static final SimpleDateFormat DEFAULT_SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+    public static final SimpleDateFormat UTC_SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault());
+
+    public static final SimpleDateFormat DATE_SDF = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+
+    public static final SimpleDateFormat DATE_SDF_POINT = new SimpleDateFormat("yyyy.MM.dd", Locale.getDefault());
+
+    public static final SimpleDateFormat DATE_NO_YEAR_SDF = new SimpleDateFormat("MM-dd", Locale.getDefault());
+
+    public static final SimpleDateFormat HOUR_SDF = new SimpleDateFormat("HH:mm", Locale.getDefault());
+
+    public static final SimpleDateFormat HOURLY_FORECAST_SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
+
+    public static final SimpleDateFormat API_TIME = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
+
+    public static final SimpleDateFormat MONEY_DAY_HH_MM = new SimpleDateFormat("MM-dd HH:mm", Locale.getDefault());
+
+    /**
+     * 将时间戳转为时间字符串
+     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param milliseconds 毫秒时间戳
+     * @return 时间字符串
+     */
+    public static String milliseconds2String(long milliseconds) {
+        return milliseconds2String(milliseconds, DEFAULT_SDF);
+    }
+
+    /**
+     * 将时间戳转为时间字符串
+     * <p>格式为用户自定义</p>
+     *
+     * @param milliseconds 毫秒时间戳
+     * @param format       时间格式
+     * @return 时间字符串
+     */
+    public  synchronized static String milliseconds2String(long milliseconds, SimpleDateFormat format) {
+        return format.format(new Date(milliseconds));
+    }
+
+    /**
+     * 将时间字符串转为时间戳
+     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @return 毫秒时间戳
+     */
+    public static long string2Milliseconds(String time) {
+        return string2Milliseconds(time, DEFAULT_SDF);
+    }
+
+    /**
+     * 将时间字符串转为时间戳
+     * <p>格式为用户自定义</p>
+     *
+     * @param time   时间字符串
+     * @param format 时间格式
+     * @return 毫秒时间戳
+     */
+    public static long string2Milliseconds(String time, SimpleDateFormat format) {
+        try {
+            return format.parse(time).getTime();
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return -1;
+    }
+
+    /**
+     * 将时间字符串转为Date类型
+     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @return Date类型
+     */
+    public static Date string2Date(String time) {
+        return string2Date(time, DEFAULT_SDF);
+    }
+
+    /**
+     * 将时间字符串转为Date类型
+     * <p>格式为用户自定义</p>
+     *
+     * @param time   时间字符串
+     * @param format 时间格式
+     * @return Date类型
+     */
+    public static Date string2Date(String time, SimpleDateFormat format) {
+        return new Date(string2Milliseconds(time, format));
+    }
+
+    /**
+     * 将Date类型转为时间字符串
+     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time Date类型时间
+     * @return 时间字符串
+     */
+    public static String date2String(Date time) {
+        return date2String(time, DEFAULT_SDF);
+    }
+
+    /**
+     * 将Date类型转为时间字符串
+     * <p>格式为用户自定义</p>
+     *
+     * @param time   Date类型时间
+     * @param format 时间格式
+     * @return 时间字符串
+     */
+    public synchronized static String date2String(Date time, SimpleDateFormat format) {
+        return format.format(time);
+    }
+
+    /**
+     * 将Date类型转为时间戳
+     *
+     * @param time Date类型时间
+     * @return 毫秒时间戳
+     */
+    public static long date2Milliseconds(Date time) {
+        return time.getTime();
+    }
+
+    /**
+     * 将时间戳转为Date类型
+     *
+     * @param milliseconds 毫秒时间戳
+     * @return Date类型时间
+     */
+    public static Date milliseconds2Date(long milliseconds) {
+        return new Date(milliseconds);
+    }
+
+    /**
+     * 毫秒时间戳单位转换(单位:unit)
+     *
+     * @param milliseconds 毫秒时间戳
+     * @param unit
+     * @return unit时间戳
+     */
+    private static long milliseconds2Unit(long milliseconds, TimeUnit unit) {
+        switch (unit) {
+            case MSEC:
+                return milliseconds / MSEC;
+            case SEC:
+                return milliseconds / SEC;
+            case MIN:
+                return milliseconds / MIN;
+            case HOUR:
+                return milliseconds / HOUR;
+            case DAY:
+                return milliseconds / DAY;
+            default:
+        }
+        return -1;
+    }
+
+    /**
+     * 获取两个时间差(单位:unit)
+     * <p>time1和time2格式都为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time0 时间字符串1
+     * @param time1 时间字符串2
+     * @param unit
+     * @return unit时间戳
+     */
+    public static long getIntervalTime(String time0, String time1, TimeUnit unit) {
+        return getIntervalTime(time0, time1, unit, DEFAULT_SDF);
+    }
+
+    /**
+     * 获取两个时间差(单位:unit)
+     * <p>time1和time2格式都为format</p>
+     *
+     * @param time0  时间字符串1
+     * @param time1  时间字符串2
+     * @param unit
+     * @param format 时间格式
+     * @return unit时间戳
+     */
+    public static long getIntervalTime(String time0, String time1, TimeUnit unit, SimpleDateFormat format) {
+        return milliseconds2Unit(Math.abs(string2Milliseconds(time0, format)
+                - string2Milliseconds(time1, format)), unit);
+    }
+
+    /**
+     * 获取两个时间差(单位:unit)
+     * <p>time1和time2都为Date类型</p>
+     *
+     * @param time0 Date类型时间1
+     * @param time1 Date类型时间2
+     * @param unit
+     * @return unit时间戳
+     */
+    public static long getIntervalTime(Date time0, Date time1, TimeUnit unit) {
+        return milliseconds2Unit(Math.abs(date2Milliseconds(time1)
+                - date2Milliseconds(time0)), unit);
+    }
+
+    /**
+     * 获取当前时间
+     *
+     * @return 毫秒时间戳
+     */
+    public static long getCurTimeMills() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * 获取当前时间
+     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @return 时间字符串
+     */
+    public static String getCurTimeString() {
+        return date2String(new Date());
+    }
+
+    /**
+     * 获取当前时间
+     * <p>格式为用户自定义</p>
+     *
+     * @param format 时间格式
+     * @return 时间字符串
+     */
+    public static String getCurTimeString(SimpleDateFormat format) {
+        return date2String(new Date(), format);
+    }
+
+    /**
+     * 获取当前时间
+     * <p>Date类型</p>
+     *
+     * @return Date类型时间
+     */
+    public static Date getCurTimeDate() {
+        return new Date();
+    }
+
+    /**
+     * 获取与当前时间的差(单位:unit)
+     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @param unit
+     * @return unit时间戳
+     */
+    public static long getIntervalByNow(String time, TimeUnit unit) {
+        return getIntervalByNow(time, unit, DEFAULT_SDF);
+    }
+
+    /**
+     * 获取与当前时间的差(单位:unit)
+     * <p>time格式为format</p>
+     *
+     * @param time   时间字符串
+     * @param unit
+     * @param format 时间格式
+     * @return unit时间戳
+     */
+    public static long getIntervalByNow(String time, TimeUnit unit, SimpleDateFormat format) {
+        return getIntervalTime(getCurTimeString(), time, unit, format);
+    }
+
+    /**
+     * 获取与当前时间的差(单位:unit)
+     * <p>time为Date类型</p>
+     *
+     * @param time Date类型时间
+     * @param unit
+     * @return unit时间戳
+     */
+    public static long getIntervalByNow(Date time, TimeUnit unit) {
+        return getIntervalTime(getCurTimeDate(), time, unit);
+    }
+
+    /**
+     * 判断闰年
+     *
+     * @param year 年份
+     * @return {@code true}: 闰年<br>{@code false}: 平年
+     */
+    public static boolean isLeapYear(int year) {
+        return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
+    }
+
+    /**
+     * 获取星期
+     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @return 星期
+     */
+    public static String getWeek(String time) {
+        return new SimpleDateFormat("EEEE", Locale.getDefault()).format(string2Date(time));
+    }
+
+    /**
+     * 获取星期
+     *
+     * @param time   时间字符串
+     * @param format 时间格式
+     * @return 星期
+     */
+    public static String getWeek(String time, SimpleDateFormat format) {
+        return new SimpleDateFormat("EEEE", Locale.getDefault()).format(string2Date(time, format));
+    }
+
+    /**
+     * 获取星期
+     *
+     * @param time Date类型时间
+     * @return 星期
+     */
+    public static String getWeek(Date time) {
+        return new SimpleDateFormat("EEEE", Locale.getDefault()).format(time);
+    }
+
+    /**
+     * 获取星期
+     * <p>注意:周日的Index才是1,周六为7</p>
+     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @return 1...5
+     */
+    public static int getWeekIndex(String time) {
+        Date date = string2Date(time);
+        return getWeekIndex(date);
+    }
+
+    /**
+     * 获取星期
+     * <p>注意:周日的Index才是1,周六为7</p>
+     *
+     * @param time   时间字符串
+     * @param format 时间格式
+     * @return 1...7
+     */
+    public static int getWeekIndex(String time, SimpleDateFormat format) {
+        Date date = string2Date(time, format);
+        return getWeekIndex(date);
+    }
+
+    /**
+     * 获取星期
+     * <p>注意:周日的Index才是1,周六为7</p>
+     *
+     * @param time Date类型时间
+     * @return 1...7
+     */
+    public static int getWeekIndex(Date time) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(time);
+        return cal.get(Calendar.DAY_OF_WEEK);
+    }
+
+    /**
+     * 获取月份中的第几周
+     * <p>注意:国外周日才是新的一周的开始</p>
+     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @return 1...5
+     */
+    public static int getWeekOfMonth(String time) {
+        Date date = string2Date(time);
+        return getWeekOfMonth(date);
+    }
+
+    /**
+     * 获取月份中的第几周
+     * <p>注意:国外周日才是新的一周的开始</p>
+     *
+     * @param time   时间字符串
+     * @param format 时间格式
+     * @return 1...5
+     */
+    public static int getWeekOfMonth(String time, SimpleDateFormat format) {
+        Date date = string2Date(time, format);
+        return getWeekOfMonth(date);
+    }
+
+    /**
+     * 获取月份中的第几周
+     * <p>注意:国外周日才是新的一周的开始</p>
+     *
+     * @param time Date类型时间
+     * @return 1...5
+     */
+    public static int getWeekOfMonth(Date time) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(time);
+        return cal.get(Calendar.WEEK_OF_MONTH);
+    }
+
+    /**
+     * 获取年份中的第几周
+     * <p>注意:国外周日才是新的一周的开始</p>
+     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
+     *
+     * @param time 时间字符串
+     * @return 1...54
+     */
+    public static int getWeekOfYear(String time) {
+        Date date = string2Date(time);
+        return getWeekOfYear(date);
+    }
+
+    /**
+     * 获取年份中的第几周
+     * <p>注意:国外周日才是新的一周的开始</p>
+     *
+     * @param time   时间字符串
+     * @param format 时间格式
+     * @return 1...54
+     */
+    public static int getWeekOfYear(String time, SimpleDateFormat format) {
+        Date date = string2Date(time, format);
+        return getWeekOfYear(date);
+    }
+
+    /**
+     * 获取年份中的第几周
+     * <p>注意:国外周日才是新的一周的开始</p>
+     *
+     * @param time Date类型时间
+     * @return 1...54
+     */
+    public static int getWeekOfYear(Date time) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(time);
+        return cal.get(Calendar.WEEK_OF_YEAR);
+    }
+
+    public static String string2String(String time, SimpleDateFormat fromFormat, SimpleDateFormat toFormat) {
+        return TimeUtils.date2String(TimeUtils.string2Date(time, fromFormat), toFormat);
+    }
+
+    public static boolean isBefore(Date d1, Date d2) {
+        return d1.before(d2);
+    }
+
+    public static boolean isBefore(String d1, String d2, SimpleDateFormat sdf) {
+        try {
+            return sdf.parse(d1).before(sdf.parse(d2));
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取某个日期的前几天的日期
+     *
+     * @param dateString 某日期
+     * @param dayNumber  前面第几天
+     * @return
+     */
+    public static String getPreviousDay(String dateString, int dayNumber) {
+        Calendar c = Calendar.getInstance();
+        Date date = null;
+        try {
+            date = new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        c.setTime(date);
+        int day = c.get(Calendar.DATE);
+        c.set(Calendar.DATE, day - dayNumber);
+
+        String previousDay = new SimpleDateFormat("yyyy-MM-dd").format(c.getTime());
+        return previousDay;
+    }
+
+    public static String getSystemTime() {
+        return String.valueOf(System.currentTimeMillis());
+    }
+
+    /**
+     * 判断当前时间是否在两个时间段之内
+     *
+     * @param beginTime 开始时间
+     * @param endTime   结束时间
+     * @return
+     */
+    public static boolean isNowBetween(String beginTime, String endTime) {
+        SimpleDateFormat df = new SimpleDateFormat("HH:mm");
+        Date now = null;
+        Date begin = null;
+        Date end = null;
+        try {
+            now = df.parse(df.format(new Date()));
+            begin = df.parse(beginTime);
+            end = df.parse(endTime);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        Calendar nowCal = Calendar.getInstance();
+        nowCal.setTime(now);
+
+        Calendar beginCal = Calendar.getInstance();
+        beginCal.setTime(begin);
+
+        Calendar endCal = Calendar.getInstance();
+        endCal.setTime(end);
+
+        if (nowCal.after(beginCal) && nowCal.before(endCal)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取当前时间在某段时间内的百分比位置
+     *
+     * @param beginTime 开始时间
+     * @param endTime   结束时间
+     * @return
+     */
+    public static float getTimeDiffPercent(String beginTime, String endTime) {
+        SimpleDateFormat df = new SimpleDateFormat("HH:mm");
+        Date now = null;
+        Date begin = null;
+        Date end = null;
+        try {
+            now = df.parse(df.format(new Date()));
+            begin = df.parse(beginTime);
+            end = df.parse(endTime);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (float) (now.getTime() - begin.getTime()) / (float) (end.getTime() - begin.getTime());
+    }
+    public enum TimeUnit {
+        MSEC,
+        SEC,
+        MIN,
+        HOUR,
+        DAY
+    }
+}

+ 30 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/AbsServiceConnection.java

@@ -0,0 +1,30 @@
+package com.wdkl.ncs.keepbackground.watch;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+public abstract class AbsServiceConnection implements ServiceConnection {
+
+    // 当前绑定的状态
+    public boolean mConnectedState = false;
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mConnectedState = true;
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        if (mConnectedState) {
+            mConnectedState = false;
+            onDisconnected(name);
+        }
+    }
+
+    @Override
+    public void onBindingDied(ComponentName name) {
+        onServiceDisconnected(name);
+    }
+
+    public abstract void onDisconnected(ComponentName name);
+}

+ 32 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/JobSchedulerService.java

@@ -0,0 +1,32 @@
+package com.wdkl.ncs.keepbackground.watch;
+
+import android.annotation.TargetApi;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.Build;
+import android.util.Log;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+
+/**
+ * Android 5.0+ 使用的 JobScheduler.
+ * 运行在 :watch 子进程中.
+ *
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class JobSchedulerService extends JobService {
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        Log.d("sj_keep", "JobSchedulerService  onStartJob 启动。。。。");
+        DaemonEnv.startServiceSafely(JobSchedulerService.this,
+                WatchDogService.class);
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        Log.d("sj_keep", "JobSchedulerService  onStopJob 停止。。。。");
+        return false;
+    }
+}

+ 109 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/PlayMusicService.java

@@ -0,0 +1,109 @@
+package com.wdkl.ncs.keepbackground.watch;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaPlayer;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+import com.wdkl.ncs.keepbackground.utils.ForegroundNotificationUtils;
+import com.wdkl.ncs.keepbackground.R;
+
+/**
+ * 后台播放无声音乐
+ */
+public class PlayMusicService extends Service {
+    private boolean mNeedStop = false; //控制是否播放音频
+    private MediaPlayer mMediaPlayer;
+    private StopBroadcastReceiver stopBroadcastReceiver;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        ForegroundNotificationUtils.startForegroundNotification(this);
+        startRegisterReceiver();
+        mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.no_notice);
+        mMediaPlayer.setLooping(true);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        startPlayMusic();
+        return START_STICKY;
+    }
+
+    private void startPlayMusic(){
+        if (mMediaPlayer!=null && !mMediaPlayer.isPlaying() && !mNeedStop) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    Log.d("sj_keep", "开始后台播放音乐");
+                    mMediaPlayer.start();
+                }
+            }).start();
+        }
+    }
+
+    private void stopPlayMusic() {
+        if (mMediaPlayer != null) {
+            Log.d("sj_keep", "关闭后台播放音乐");
+            mMediaPlayer.stop();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stopPlayMusic();
+        Log.d("sj_keep",  "----> stopPlayMusic ,停止服务");
+        // 重启自己
+        if (!mNeedStop) {
+            Log.d("sj_keep",  "----> PlayMusic ,重启服务");
+            Intent intent = new Intent(getApplicationContext(), PlayMusicService.class);
+            startService(intent);
+        }
+    }
+
+    private void startRegisterReceiver(){
+        if (stopBroadcastReceiver == null){
+            stopBroadcastReceiver = new StopBroadcastReceiver();
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(DaemonEnv.ACTION_CANCEL_JOB_ALARM_SUB);
+            registerReceiver(stopBroadcastReceiver,intentFilter);
+        }
+    }
+
+    private void startUnRegisterReceiver(){
+        if (stopBroadcastReceiver != null){
+            unregisterReceiver(stopBroadcastReceiver);
+            stopBroadcastReceiver = null;
+        }
+    }
+
+    /**
+     * 停止自己
+     */
+    private void stopService(){
+        mNeedStop = true;
+        stopPlayMusic();
+        startUnRegisterReceiver();
+        stopSelf();
+    }
+
+    class StopBroadcastReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            stopService();
+        }
+    }
+}

+ 44 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/WakeUpReceiver.java

@@ -0,0 +1,44 @@
+package com.wdkl.ncs.keepbackground.watch;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+
+
+public class WakeUpReceiver extends BroadcastReceiver {
+
+    /**
+     * 监听 8 种系统广播 :
+     * CONNECTIVITY\_CHANGE, USER\_PRESENT, ACTION\_POWER\_CONNECTED, ACTION\_POWER\_DISCONNECTED,
+     * BOOT\_COMPLETED, MEDIA\_MOUNTED, PACKAGE\_ADDED, PACKAGE\_REMOVED.
+     * 在网络连接改变, 用户屏幕解锁, 电源连接 / 断开, 系统启动完成, 挂载 SD 卡, 安装 / 卸载软件包时拉起 Service.
+     * Service 内部做了判断,若 Service 已在运行,不会重复启动.
+     * 运行在:watch子进程中.
+     */
+    @SuppressLint("UnsafeProtectedBroadcastReceiver")
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        DaemonEnv.startServiceSafely(context, WatchDogService.class);
+    }
+
+    public static class WakeUpAutoStartReceiver extends BroadcastReceiver {
+
+        @SuppressLint("UnsafeProtectedBroadcastReceiver")
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            DaemonEnv.startServiceSafely(context,WatchDogService.class);
+        }
+    }
+
+    public static class StartWatchReceiver extends BroadcastReceiver {
+
+        @SuppressLint("UnsafeProtectedBroadcastReceiver")
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            WatchProcessPrefHelper.setIsStartSDaemon(context,true);
+        }
+    }
+}

+ 220 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/WatchDogService.java

@@ -0,0 +1,220 @@
+package com.wdkl.ncs.keepbackground.watch;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+import com.wdkl.ncs.keepbackground.utils.ForegroundNotificationUtils;
+
+
+
+
+public class WatchDogService extends Service {
+    protected static final int HASH_CODE = 11222;
+//    protected static Disposable mDisposable;
+    protected static PendingIntent mPendingIntent;
+    private StopBroadcastReceiver stopBroadcastReceiver;
+    private boolean isCanStartWatchDog;
+
+    /**
+     * 服务绑定相关的操作
+     */
+    private AbsServiceConnection mConnection = new AbsServiceConnection() {
+
+        @Override
+        public void onDisconnected(ComponentName name) {
+            startBindWorkServices();
+        }
+    };
+
+    private void startBindWorkServices(){
+        if (WatchProcessPrefHelper.getWorkService()!=null && isCanStartWatchDog) {
+            DaemonEnv.startServiceMayBind(WatchDogService.this, WatchProcessPrefHelper.mWorkServiceClass, mConnection);
+            DaemonEnv.startServiceSafely(WatchDogService.this, PlayMusicService.class);
+        }
+    }
+
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        isCanStartWatchDog = WatchProcessPrefHelper.getIsStartDaemon(this);
+        if (!isCanStartWatchDog){
+            stopSelf();
+        }
+        startRegisterReceiver();
+        ForegroundNotificationUtils.startForegroundNotification(this);
+    }
+
+    @Override
+    public final int onStartCommand(Intent intent, int flags, int startId) {
+        onStart();
+        return START_STICKY;
+    }
+
+    /**
+     * 守护服务,运行在:watch子进程中
+     */
+    protected final void onStart() {
+//        if (mDisposable == null || mDisposable.isDisposed()) {
+        if (mPendingIntent == null ) {
+            //定时检查 AbsWorkService 是否在运行,如果不在运行就把它拉起来   Android 5.0+ 使用 JobScheduler,效果比 AlarmManager 好
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                JobInfo.Builder builder = new JobInfo.Builder(HASH_CODE,
+                        new ComponentName(WatchDogService.this, JobSchedulerService.class));
+                builder.setPeriodic(DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL));
+                //Android 7.0+ 增加了一项针对 JobScheduler 的新限制,最小间隔只能是下面设定的数字
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                    builder.setPeriodic(JobInfo.getMinPeriodMillis(), JobInfo.getMinFlexMillis());
+                }
+                builder.setPersisted(true);
+                JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
+                scheduler.schedule(builder.build());
+            } else {
+                //Android 4.4- 使用 AlarmManager
+                if(WatchProcessPrefHelper.getWorkService()!=null) {
+                    AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
+                    Intent i = new Intent(WatchDogService.this, WatchProcessPrefHelper.mWorkServiceClass);
+                    mPendingIntent = PendingIntent.getService(WatchDogService.this, HASH_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT);
+                    am.setRepeating(AlarmManager.RTC_WAKEUP,
+                            System.currentTimeMillis() + DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL),
+                            DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL), mPendingIntent);
+                }
+            }
+            //使用定时 Observable,避免 Android 定制系统 JobScheduler / AlarmManager 唤醒间隔不稳定的情况
+           /* mDisposable = Observable
+                    .interval(DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL), TimeUnit.MILLISECONDS)
+                    .subscribe(new Consumer<Long>() {
+                        @Override
+                        public void accept(Long aLong) throws Exception {
+                            startBindWorkServices();
+                        }
+                    }, new Consumer<Throwable>() {
+                        @Override
+                        public void accept(Throwable throwable) throws Exception {
+                            throwable.printStackTrace();
+                        }
+                    });*/
+            startBindWorkServices();
+            //守护 Service 组件的启用状态, 使其不被 MAT 等工具禁用
+            if(WatchProcessPrefHelper.getWorkService()!=null) {
+                getPackageManager().setComponentEnabledSetting(new ComponentName(getPackageName(), WatchProcessPrefHelper.mWorkServiceClass.getName()),
+                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+            }
+        }
+    }
+
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return new Messenger(new Handler()).getBinder();
+    }
+
+    private void onEnd() {
+        Log.d("sj_keep", "onEnd ---- + IsShouldStopSelf  :" + isCanStartWatchDog);
+        if (isCanStartWatchDog){
+            DaemonEnv.startServiceSafely(WatchDogService.this,WatchProcessPrefHelper.getWorkService());
+            DaemonEnv.startServiceSafely(WatchDogService.this,WatchDogService.class);
+        }
+    }
+
+    /**
+     * 最近任务列表中划掉卡片时回调
+     */
+    @Override
+    public void onTaskRemoved(Intent rootIntent) {
+        onEnd();
+    }
+
+    /**
+     * 设置-正在运行中停止服务时回调
+     */
+    @Override
+    public void onDestroy() {
+        onEnd();
+        stopRegisterReceiver();
+    }
+
+
+    /**
+     * 停止运行本服务,本进程
+     */
+    private void stopService(){
+        isCanStartWatchDog = false;
+        WatchProcessPrefHelper.setIsStartSDaemon(this,false);
+        cancelJobAlarmSub();
+        if (mConnection.mConnectedState) {
+            unbindService(mConnection);
+        }
+        exit();
+    }
+
+    private void exit(){
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                stopSelf();
+            }
+        },2000);
+    }
+
+
+    private void startRegisterReceiver(){
+        if (stopBroadcastReceiver == null){
+            stopBroadcastReceiver = new StopBroadcastReceiver();
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(DaemonEnv.ACTION_CANCEL_JOB_ALARM_SUB);
+            registerReceiver(stopBroadcastReceiver,intentFilter);
+        }
+    }
+
+    private void stopRegisterReceiver(){
+        if (stopBroadcastReceiver != null){
+            unregisterReceiver(stopBroadcastReceiver);
+            stopBroadcastReceiver = null;
+        }
+    }
+
+    /**
+     * 用于在不需要服务运行的时候取消 Job / Alarm / Subscription.
+     *
+     * 因 WatchDogService 运行在 :watch 子进程, 请勿在主进程中直接调用此方法.
+     * 而是向 WakeUpReceiver 发送一个 Action 为 WakeUpReceiver.ACTION_CANCEL_JOB_ALARM_SUB 的广播.
+     */
+    public void cancelJobAlarmSub() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            JobScheduler scheduler = (JobScheduler) WatchDogService.this.getSystemService(JOB_SCHEDULER_SERVICE);
+            scheduler.cancel(HASH_CODE);
+        } else {
+            AlarmManager am = (AlarmManager) WatchDogService.this.getSystemService(ALARM_SERVICE);
+            if (mPendingIntent != null) {
+                am.cancel(mPendingIntent);
+            }
+        }
+      /*  if (mDisposable !=null && !mDisposable.isDisposed()){
+            mDisposable.dispose();
+        }*/
+    }
+
+    class StopBroadcastReceiver extends BroadcastReceiver{
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            stopService();
+        }
+    }
+}

+ 59 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/watch/WatchProcessPrefHelper.java

@@ -0,0 +1,59 @@
+package com.wdkl.ncs.keepbackground.watch;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.wdkl.ncs.keepbackground.utils.FileUtils;
+import com.wdkl.ncs.keepbackground.utils.SpManager;
+import com.wdkl.ncs.keepbackground.work.AbsWorkService;
+
+import static com.wdkl.ncs.keepbackground.utils.SpManager.Keys.WORK_SERVICE;
+
+
+/**
+ * 用于多进程通讯的 SharedPreferences
+ *
+ * 此处存在着风险  github --> PreferencesProvider 项目
+ */
+
+public class WatchProcessPrefHelper {
+
+    private static final String SHARED_UTILS = "watch_process";
+
+    private static final String KEY_IS_START_DAEMON = "is_start_sport"; // 是否开始了一次保活(做为保活的判断依据)
+
+    // 多进程时,尽量少用静态、单例 此处不得已
+    public static Class<? extends AbsWorkService> mWorkServiceClass;
+
+    public static Class<? extends AbsWorkService> getWorkService(){
+        if(mWorkServiceClass==null){
+            try {
+                String localC= "";
+                try{
+                    localC=SpManager.getInstance().getString(WORK_SERVICE);
+                }catch (Exception e){
+                    e.printStackTrace();
+                }
+                if(TextUtils.isEmpty(localC)){
+                    localC=FileUtils.readTxtFile(FileUtils.FILE_PKG_PATH);
+                }
+                Log.v("mWorkServiceClass","保活目标服务:"+localC);
+                mWorkServiceClass= (Class<? extends AbsWorkService>) Class.forName(localC);
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        return mWorkServiceClass;
+    }
+
+    public static void setIsStartSDaemon(Context context,boolean mapType){
+        context.getSharedPreferences(SHARED_UTILS, Context.MODE_MULTI_PROCESS).edit().putBoolean(KEY_IS_START_DAEMON, mapType).apply();
+    }
+
+    public static boolean getIsStartDaemon(Context context){
+        return context.getSharedPreferences(SHARED_UTILS, Context.MODE_MULTI_PROCESS).getBoolean(KEY_IS_START_DAEMON, false);
+    }
+
+
+}

+ 215 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/work/AbsWorkService.java

@@ -0,0 +1,215 @@
+package com.wdkl.ncs.keepbackground.work;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.util.Log;
+
+
+import com.wdkl.ncs.keepbackground.utils.FileUtils;
+import com.wdkl.ncs.keepbackground.utils.SpManager;
+import com.wdkl.ncs.keepbackground.watch.AbsServiceConnection;
+import com.wdkl.ncs.keepbackground.utils.ForegroundNotificationUtils;
+import com.wdkl.ncs.keepbackground.singlepixel.ScreenReceiverUtil;
+import com.wdkl.ncs.keepbackground.watch.WatchDogService;
+import com.wdkl.ncs.keepbackground.watch.WatchProcessPrefHelper;
+
+import static com.wdkl.ncs.keepbackground.utils.SpManager.Keys.WORK_SERVICE;
+
+
+/**
+ * 主要Service 用户继承该类用来处理自己业务逻辑
+ *
+ * 该类已经实现如何启动结束及保活的功能,用户无需关心。
+ */
+public abstract class AbsWorkService extends Service {
+
+    private StopBroadcastReceiver stopBroadcastReceiver;
+
+    private AbsServiceConnection mConnection = new AbsServiceConnection() {
+
+        @Override
+        public void onDisconnected(ComponentName name) {
+            if (needStartWorkService()) {
+                DaemonEnv.startServiceMayBind(AbsWorkService.this, WatchDogService.class, mConnection);
+            }
+        }
+
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if(DaemonEnv.app==null)return;
+        Log.d("sj_keep", this.getClass()+"  onCreate 启动。。。。");
+        WatchProcessPrefHelper.mWorkServiceClass=this.getClass();
+        SpManager.getInstance().putString(WORK_SERVICE,this.getClass().getName());
+        FileUtils.writeTxtToFile(this.getClass().getName(),FileUtils.FILE_PKG_DIRRECT,FileUtils.FILE_SERVICE_NAME);
+        if (!needStartWorkService()) {
+            stopSelf();
+        }else {
+            Log.d("sj_keep", "AbsWorkService  onCreate 启动。。。。");
+            startRegisterReceiver();
+            createScreenListener();
+            ForegroundNotificationUtils.startForegroundNotification(this);
+            getPackageManager().setComponentEnabledSetting(new ComponentName(getPackageName(), WatchDogService.class.getName()),
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return onStart();
+    }
+
+    /**
+     * 1.防止重复启动,可以任意调用 DaemonEnv.startServiceMayBind(Class serviceClass);
+     * 2.利用漏洞启动前台服务而不显示通知;
+     * 3.在子线程中运行定时任务,处理了运行前检查和销毁时保存的问题;
+     * 4.启动守护服务;
+     * 5.守护 Service 组件的启用状态, 使其不被 MAT 等工具禁用.
+     */
+    protected int onStart() {
+        //启动守护服务,运行在:watch子进程中
+        //业务逻辑: 实际使用时,根据需求,将这里更改为自定义的条件,判定服务应当启动还是停止 (任务是否需要运行)
+        // 此处不比重复关闭服务。否则mConnection.mConnectedState的状态没有来得及改变,
+        //  再次unbindService(conn)服务会导致 Service not registered 异常抛出。 服务启动和关闭都需要耗时,段时间内不宜频繁开启和关闭。
+        //若还没有取消订阅,说明任务仍在运行,为防止重复启动,直接 return
+        DaemonEnv.startServiceMayBind(AbsWorkService.this, WatchDogService.class, mConnection);
+        Boolean workRunning = isWorkRunning();
+        if (!workRunning){
+            //业务逻辑
+            startWork();
+        }
+        return START_STICKY;
+    }
+
+
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return onBindService(intent, null);
+    }
+
+    /**
+     * 最近任务列表中划掉卡片时回调
+     */
+    @Override
+    public void onTaskRemoved(Intent rootIntent) {
+        onEnd();
+    }
+
+    /**
+     * 设置-正在运行中停止服务时回调
+     */
+    @Override
+    public void onDestroy() {
+        ForegroundNotificationUtils.deleteForegroundNotification(this);
+        stopRegisterReceiver();
+        stopScreenListener();
+        onEnd();
+    }
+
+    protected void onEnd() {
+        onServiceKilled();
+        // // 不同的进程,所有的静态和单例都会失效
+        if (needStartWorkService()){
+            DaemonEnv.startServiceSafely(AbsWorkService.this,WatchDogService.class);
+        }
+
+    }
+
+    /**
+     * 是否 任务完成, 不再需要服务运行?
+     * @return true 应当启动服务; false 应当停止服务; null 无法判断, 什么也不做.
+     */
+    public abstract Boolean needStartWorkService();
+
+    /**
+     * 开启具体业务,实际调用与isWorkRunning方法返回值有关,当isWorkRunning返回false才会执行该方法
+     */
+    public abstract void startWork();
+
+    /**
+     * 服务停止需要执行的操作
+     */
+    public abstract void stopWork();
+    /**
+     * 任务是否正在运行? 由实现者处理
+     * @return 任务正在运行, true; 任务当前不在运行, false; 无法判断, 什么也不做, null.
+     */
+    public abstract Boolean isWorkRunning();
+
+
+    /**
+     * 绑定远程service 可根据实际业务情况是否绑定自定义binder或者直接返回默认binder
+     * @param intent
+     * @param alwaysNull
+     * @return
+     */
+    public abstract IBinder onBindService(Intent intent, Void alwaysNull);
+    public abstract void onServiceKilled();
+
+
+    /**
+     * 任务完成,停止服务并取消定时唤醒
+     *
+     * 停止服务使用取消订阅的方式实现,而不是调用 Context.stopService(Intent name)。因为:
+     * 1.stopService 会调用 Service.onDestroy(),而 AbsWorkService 做了保活处理,会把 Service 再拉起来;
+     * 2.我们希望 AbsWorkService 起到一个类似于控制台的角色,即 AbsWorkService 始终运行 (无论任务是否需要运行),
+     * 而是通过 onStart() 里自定义的条件,来决定服务是否应当启动或停止。
+     */
+    private void stopService() {
+        // 给实现者处理业务逻辑
+        DaemonEnv.safelyUnbindService(this,mConnection);
+        stopWork();
+        stopSelf();
+    }
+
+
+    private ScreenReceiverUtil mScreenUtils;
+
+    private void createScreenListener(){
+        //   注册锁屏广播监听器
+        mScreenUtils = new ScreenReceiverUtil(this);
+        mScreenUtils.startScreenReceiverListener();
+    }
+
+    private void stopScreenListener(){
+        // 取消注册
+        if (mScreenUtils != null){
+            mScreenUtils.stopScreenReceiverListener();
+            mScreenUtils = null;
+        }
+    }
+
+    private void startRegisterReceiver(){
+        if (stopBroadcastReceiver == null){
+            stopBroadcastReceiver = new StopBroadcastReceiver();
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(DaemonEnv.ACTION_CANCEL_JOB_ALARM_SUB);
+            registerReceiver(stopBroadcastReceiver,intentFilter);
+        }
+    }
+
+    private void stopRegisterReceiver(){
+        if (stopBroadcastReceiver != null){
+            unregisterReceiver(stopBroadcastReceiver);
+            stopBroadcastReceiver = null;
+        }
+    }
+
+    class StopBroadcastReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // 停止业务
+            stopService();
+        }
+    }
+}

+ 227 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/work/DaemonEnv.java

@@ -0,0 +1,227 @@
+package com.wdkl.ncs.keepbackground.work;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Service;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+import android.support.annotation.RequiresApi;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+
+
+import com.wdkl.ncs.keepbackground.utils.JumpWindowPemiManagement;
+import com.wdkl.ncs.keepbackground.utils.NotificationSetUtil;
+import com.wdkl.ncs.keepbackground.utils.SpManager;
+import com.wdkl.ncs.keepbackground.watch.AbsServiceConnection;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.wdkl.ncs.keepbackground.work.IntentWrapper.getApplicationName;
+
+
+/**
+ *环境配置 每个进程独享一份 不同的进程,所有的静态和单例都会失效
+ *
+ * 这里将Daemon类 做成工具类
+ *
+ * 每一个进程都会有一个Daemon类
+ *
+ */
+public final class DaemonEnv {
+
+    /**
+     * 向 WakeUpReceiver 发送带有此 Action 的广播, 即可在不需要服务运行的时候取消 Job / Alarm / Subscription.
+     */
+    public static final String ACTION_START_JOB_ALARM_SUB = "com.sdk.START_JOB_ALARM_SUB";
+    public static final String ACTION_CANCEL_JOB_ALARM_SUB = "com.sdk.CANCEL_JOB_ALARM_SUB";
+
+    public static final int DEFAULT_WAKE_UP_INTERVAL = 2 * 60 * 1000; // 默认JobScheduler 唤醒时间为 2 分钟
+    public static final int MINIMAL_WAKE_UP_INTERVAL = 60 * 1000; // 最小时间为 1 分钟
+
+    public static Context app;
+    public static void init(Context context){
+        //开启保护
+        app=context.getApplicationContext();
+        DaemonEnv.sendStartWorkBroadcast(context);
+    }
+    public static void startServiceMayBind( final Context context,
+                                     final Class<? extends Service> serviceClass,
+                                     AbsServiceConnection connection) {
+
+        // 判断当前绑定的状态
+        if (!connection.mConnectedState) {
+            Log.d("sj_keep", "启动并绑定服务 :"+serviceClass.getSimpleName());
+            final Intent intent = new Intent(context, serviceClass);
+            startServiceSafely(context, serviceClass);
+            context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+        }
+    }
+
+    public static void startServiceSafely(Context context, Class<? extends Service> i) {
+        Log.d("sj_keep", "安全启动服务。。: "+i.getSimpleName());
+        try {
+            if (Build.VERSION.SDK_INT >= 26){
+                context.startForegroundService(new Intent(context,i));
+            }else {
+                context.startService(new Intent(context,i));
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    public static void startServiceSafelyWithData(Context context, Class<? extends Service> i){
+        try {
+            if (Build.VERSION.SDK_INT >= 26){
+                context.startForegroundService(new Intent(context,i));
+            }else {
+                context.startService(new Intent(context,i));
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static int getWakeUpInterval(int sWakeUpInterval) {
+        return Math.max(sWakeUpInterval, MINIMAL_WAKE_UP_INTERVAL);
+    }
+
+
+    public static void safelyUnbindService(Service service, AbsServiceConnection mConnection){
+        try{
+            if (mConnection.mConnectedState) {
+                service.unbindService(mConnection);
+            }
+        }catch(Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 当前哪个进程使用的时候 就用其上下文发送广播
+     *
+     * 如果是同一进程,可以自定义启动方式 不使用广播的模式
+     */
+    public static void sendStartWorkBroadcast(Context context) {
+        Log.d("sj_keep", "发送开始广播。。。。");
+        // 以广播的形式通知所有进程终止
+        context.sendBroadcast(new Intent(ACTION_START_JOB_ALARM_SUB));
+    }
+
+
+    /**
+     * 当前哪个进程使用的时候 就用其上下文发送广播
+     */
+    public static void sendStopWorkBroadcast(Context context) {
+        Log.d("sj_keep", "发送停止广播。。。。");
+        // 以广播的形式通知所有进程终止
+        context.sendBroadcast(new Intent(ACTION_CANCEL_JOB_ALARM_SUB));
+    }
+
+    /**
+     * 后台允许白名单
+     * @param a
+     * @param reason
+     */
+    public static void whiteListMatters(final Activity a, String reason){
+        try{
+            if (ContextCompat.checkSelfPermission(a, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {//判断是否已经赋予权限
+                ActivityCompat.requestPermissions(a,
+                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                                Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
+            }
+            IntentWrapper.whiteListMatters(a, reason);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+    /**
+     * 后台允许白名单
+     * @param a
+     * @param reason
+     * @param doAll  将所有影响保活开关都打开
+     */
+    public static void whiteListMatters(final Activity a, String reason,boolean doAll){
+        try{
+            IntentWrapper.whiteListMatters(a, reason);
+            if(doAll){
+                openPushSwitch(a,reason);
+                checkWindowPerission(a,reason);
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 打开通知权限
+     * @param context
+     * @param reason
+     */
+    public static void openPushSwitch(final Context context, String reason){
+        new AlertDialog.Builder(context)
+                .setCancelable(false)
+                .setTitle("需要开启 " + getApplicationName(context) + " 的通知开关")
+                .setMessage(reason + "需要 " + getApplicationName(context) + " 开启通知管理开关。\n\n" +
+                        "请点击『确定』,在弹出的『通知管理』页面打开允许通知选项。")
+                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface d, int w) {
+                        NotificationSetUtil.OpenNotificationSetting(context, null);
+                    }
+                })
+                .show();
+    }
+
+    /**
+     * 检测悬浮窗权限
+     * 兼容6.0及以下
+     */
+    public static void checkWindowPerission(final Context context, String reason) {
+        if(!SpManager.getInstance().getBoolean(SpManager.Keys.IS_HINT_SYSTEM_WINDOW)){
+            new AlertDialog.Builder(context)
+                    .setCancelable(false)
+                    .setTitle("需要开启 " + getApplicationName(context) + " 的悬浮窗权限")
+                    .setMessage(reason + "需要 " + getApplicationName(context) + " 开启悬浮窗开关。\n\n" +
+                            "请点击『确定』,在弹出的『悬浮窗』页面打开允许在其他应用上层显示开关。")
+                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface d, int w) {
+                            SpManager.getInstance().putBoolean(SpManager.Keys.IS_HINT_SYSTEM_WINDOW,true);
+                            JumpWindowPemiManagement.goToWindow(context);
+                        }
+                    })
+                    .show();
+        }else {
+            windowPermissionPassWithCheck(context,reason);
+        }
+    }
+
+    private static void windowPermissionPassWithCheck(final Context context, String reason) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !JumpWindowPemiManagement.hasWindowPei(context)) {
+            new AlertDialog.Builder(context)
+                    .setCancelable(false)
+                    .setTitle("需要开启 " + getApplicationName(context) + " 的悬浮窗权限")
+                    .setMessage(reason + "需要 " + getApplicationName(context) + " 开启悬浮窗开关。\n\n" +
+                            "请点击『确定』,在弹出的『悬浮窗』页面打开允许在其他应用上层显示开关。")
+                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                        @RequiresApi(api = Build.VERSION_CODES.M)
+                        @Override
+                        public void onClick(DialogInterface d, int w) {
+                            SpManager.getInstance().putBoolean(SpManager.Keys.IS_HINT_SYSTEM_WINDOW,true);
+                            JumpWindowPemiManagement.goToWindow(context);
+                            Intent intent =new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
+                            context.startActivity(intent);
+                        }
+                    })
+                    .show();
+            }
+    }
+}

+ 447 - 0
keepalive/src/main/java/com/wdkl/ncs/keepbackground/work/IntentWrapper.java

@@ -0,0 +1,447 @@
+package com.wdkl.ncs.keepbackground.work;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.PowerManager;
+import android.provider.Settings;
+
+
+import com.wdkl.ncs.keepbackground.utils.SpManager;
+
+import java.util.ArrayList;
+import java.util.List;
+;
+
+
+public class IntentWrapper {
+
+    //Android 7.0+ Doze 模式
+    protected static final int DOZE = 98;
+    //华为 自启管理
+    protected static final int HUAWEI = 99;
+    //华为 锁屏清理
+    protected static final int HUAWEI_GOD = 100;
+    //小米 自启动管理
+    protected static final int XIAOMI = 101;
+    //小米 神隐模式
+    protected static final int XIAOMI_GOD = 102;
+    //三星 5.0/5.1 自启动应用程序管理
+    protected static final int SAMSUNG_L = 103;
+    //魅族 自启动管理
+    protected static final int MEIZU = 104;
+    //魅族 待机耗电管理
+    protected static final int MEIZU_GOD = 105;
+    //Oppo 自启动管理
+    protected static final int OPPO = 106;
+    //三星 6.0+ 未监视的应用程序管理
+    protected static final int SAMSUNG_M = 107;
+    //Oppo 自启动管理(旧版本系统)
+    protected static final int OPPO_OLD = 108;
+    //Vivo 后台高耗电
+    protected static final int VIVO_GOD = 109;
+    //金立 应用自启
+    protected static final int GIONEE = 110;
+    //乐视 自启动管理
+    protected static final int LETV = 111;
+    //乐视 应用保护
+    protected static final int LETV_GOD = 112;
+    //酷派 自启动管理
+    protected static final int COOLPAD = 113;
+    //联想 后台管理
+    protected static final int LENOVO = 114;
+    //联想 后台耗电优化
+    protected static final int LENOVO_GOD = 115;
+    //中兴 自启管理
+    protected static final int ZTE = 116;
+    //中兴 锁屏加速受保护应用
+    protected static final int ZTE_GOD = 117;
+    
+//    protected static List<IntentWrapper> sIntentWrapperList;
+
+    public static List<IntentWrapper> getIntentWrapperList(Context context) {
+//        if (sIntentWrapperList == null) {
+
+            List<IntentWrapper> sIntentWrapperList = new ArrayList<>();
+//            sIntentWrapperList = new ArrayList<>();
+            
+            //Android 7.0+ Doze 模式
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+                boolean ignoringBatteryOptimizations = pm.isIgnoringBatteryOptimizations(context.getPackageName());
+                if (!ignoringBatteryOptimizations) {
+                    Intent dozeIntent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+                    dozeIntent.setData(Uri.parse("package:" + context.getPackageName()));
+                    sIntentWrapperList.add(new IntentWrapper(dozeIntent, DOZE));
+                }
+            }
+
+            //华为 自启管理
+            Intent huaweiIntent = new Intent();
+            huaweiIntent.setAction("huawei.intent.action.HSM_BOOTAPP_MANAGER");
+            sIntentWrapperList.add(new IntentWrapper(huaweiIntent, HUAWEI));
+
+            //华为 锁屏清理
+            Intent huaweiGodIntent = new Intent();
+            huaweiGodIntent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
+            sIntentWrapperList.add(new IntentWrapper(huaweiGodIntent, HUAWEI_GOD));
+
+            //小米 自启动管理
+            Intent xiaomiIntent = new Intent();
+            xiaomiIntent.setAction("miui.intent.action.OP_AUTO_START");
+            xiaomiIntent.addCategory(Intent.CATEGORY_DEFAULT);
+            sIntentWrapperList.add(new IntentWrapper(xiaomiIntent, XIAOMI));
+
+            //小米 神隐模式
+            Intent xiaomiGodIntent = new Intent();
+            xiaomiGodIntent.setComponent(new ComponentName("com.miui.powerkeeper", "com.miui.powerkeeper.ui.HiddenAppsConfigActivity"));
+            xiaomiGodIntent.putExtra("package_name", context.getPackageName());
+            xiaomiGodIntent.putExtra("package_label", getApplicationName(context));
+            sIntentWrapperList.add(new IntentWrapper(xiaomiGodIntent, XIAOMI_GOD));
+
+            //三星 5.0/5.1 自启动应用程序管理
+            Intent samsungLIntent = context.getPackageManager().getLaunchIntentForPackage("com.samsung.android.sm");
+            if (samsungLIntent != null) sIntentWrapperList.add(new IntentWrapper(samsungLIntent, SAMSUNG_L));
+
+            //三星 6.0+ 未监视的应用程序管理
+            Intent samsungMIntent = new Intent();
+            samsungMIntent.setComponent(new ComponentName("com.samsung.android.sm_cn", "com.samsung.android.sm.ui.battery.BatteryActivity"));
+            sIntentWrapperList.add(new IntentWrapper(samsungMIntent, SAMSUNG_M));
+
+            //魅族 自启动管理
+            Intent meizuIntent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
+            meizuIntent.addCategory(Intent.CATEGORY_DEFAULT);
+            meizuIntent.putExtra("packageName", context.getPackageName());
+            sIntentWrapperList.add(new IntentWrapper(meizuIntent, MEIZU));
+
+            //魅族 待机耗电管理
+            Intent meizuGodIntent = new Intent();
+            meizuGodIntent.setComponent(new ComponentName("com.meizu.safe", "com.meizu.safe.powerui.PowerAppPermissionActivity"));
+            sIntentWrapperList.add(new IntentWrapper(meizuGodIntent, MEIZU_GOD));
+
+            //Oppo 自启动管理
+            Intent oppoIntent = new Intent();
+            oppoIntent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
+            sIntentWrapperList.add(new IntentWrapper(oppoIntent, OPPO));
+
+            //Oppo 自启动管理(旧版本系统)
+            Intent oppoOldIntent = new Intent();
+            oppoOldIntent.setComponent(new ComponentName("com.color.safecenter", "com.color.safecenter.permission.startup.StartupAppListActivity"));
+            sIntentWrapperList.add(new IntentWrapper(oppoOldIntent, OPPO_OLD));
+
+            //Vivo 后台高耗电
+            Intent vivoGodIntent = new Intent();
+            vivoGodIntent.setComponent(new ComponentName("com.vivo.abe", "com.vivo.applicationbehaviorengine.ui.ExcessivePowerManagerActivity"));
+            sIntentWrapperList.add(new IntentWrapper(vivoGodIntent, VIVO_GOD));
+
+            //金立 应用自启
+            Intent gioneeIntent = new Intent();
+            gioneeIntent.setComponent(new ComponentName("com.gionee.softmanager", "com.gionee.softmanager.MainActivity"));
+            sIntentWrapperList.add(new IntentWrapper(gioneeIntent, GIONEE));
+
+            //乐视 自启动管理
+            Intent letvIntent = new Intent();
+            letvIntent.setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
+            sIntentWrapperList.add(new IntentWrapper(letvIntent, LETV));
+
+            //乐视 应用保护
+            Intent letvGodIntent = new Intent();
+            letvGodIntent.setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.BackgroundAppManageActivity"));
+            sIntentWrapperList.add(new IntentWrapper(letvGodIntent, LETV_GOD));
+
+            //酷派 自启动管理
+            Intent coolpadIntent = new Intent();
+            coolpadIntent.setComponent(new ComponentName("com.yulong.android.security", "com.yulong.android.seccenter.tabbarmain"));
+            sIntentWrapperList.add(new IntentWrapper(coolpadIntent, COOLPAD));
+
+            //联想 后台管理
+            Intent lenovoIntent = new Intent();
+            lenovoIntent.setComponent(new ComponentName("com.lenovo.security", "com.lenovo.security.purebackground.PureBackgroundActivity"));
+            sIntentWrapperList.add(new IntentWrapper(lenovoIntent, LENOVO));
+
+            //联想 后台耗电优化
+            Intent lenovoGodIntent = new Intent();
+            lenovoGodIntent.setComponent(new ComponentName("com.lenovo.powersetting", "com.lenovo.powersetting.ui.Settings$HighPowerApplicationsActivity"));
+            sIntentWrapperList.add(new IntentWrapper(lenovoGodIntent, LENOVO_GOD));
+
+            //中兴 自启管理
+            Intent zteIntent = new Intent();
+            zteIntent.setComponent(new ComponentName("com.zte.heartyservice", "com.zte.heartyservice.autorun.AppAutoRunManager"));
+            sIntentWrapperList.add(new IntentWrapper(zteIntent, ZTE));
+
+            //中兴 锁屏加速受保护应用
+            Intent zteGodIntent = new Intent();
+            zteGodIntent.setComponent(new ComponentName("com.zte.heartyservice", "com.zte.heartyservice.setting.ClearAppSettingsActivity"));
+            sIntentWrapperList.add(new IntentWrapper(zteGodIntent, ZTE_GOD));
+//        }
+        return sIntentWrapperList;
+    }
+
+
+    public static String getApplicationName(Context context) {
+        String sApplicationName = "";
+        PackageManager pm;
+        ApplicationInfo ai;
+        try {
+            pm = context.getPackageManager();
+            ai = pm.getApplicationInfo(context.getPackageName(), 0);
+            sApplicationName = pm.getApplicationLabel(ai).toString();
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            sApplicationName = context.getPackageName();
+        }
+        return sApplicationName;
+    }
+
+    /**
+     * 处理白名单.
+     * @return 弹过框的 IntentWrapper.
+     */
+
+    public static List<IntentWrapper> whiteListMatters(final Activity a, String reason) {
+        if(SpManager.getInstance().getBoolean(SpManager.Keys.SP_IS_ACTION_WHITE_POWER)){
+            return null;
+        }
+        SpManager.getInstance().putBoolean(SpManager.Keys.SP_IS_ACTION_WHITE_POWER,true);
+        List<IntentWrapper> showed = new ArrayList<>();
+        if (reason == null) reason = "核心服务的持续运行";
+        List<IntentWrapper> intentWrapperList = getIntentWrapperList(a);
+        for (final IntentWrapper iw : intentWrapperList) {
+            //如果本机上没有能处理这个Intent的Activity,说明不是对应的机型,直接忽略进入下一次循环。
+            if (!iw.doesActivityExists(a)) continue;
+            switch (iw.type) {
+                case DOZE:
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                        PowerManager pm = (PowerManager) a.getSystemService(Context.POWER_SERVICE);
+                        if (pm.isIgnoringBatteryOptimizations(a.getPackageName())) break;
+                        new AlertDialog.Builder(a)
+                                .setCancelable(false)
+                                .setTitle("需要忽略 " + getApplicationName(a) + " 的电池优化")
+                                .setMessage(reason + "需要 " + getApplicationName(a) + " 加入到电池优化的忽略名单。\n\n" +
+                                        "请点击『确定』,在弹出的『忽略电池优化』对话框中,选择『是』。")
+                                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                    public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                                })
+                                .show();
+                        showed.add(iw);
+                    }
+                    break;
+                case HUAWEI:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 自动启动")
+                            .setMessage(reason + "需要允许 " + getApplicationName(a) + " 的自动启动。\n\n" +
+                                    "请点击『确定』,在弹出的『自启管理』中,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case ZTE_GOD:
+                case HUAWEI_GOD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle(getApplicationName(a) + " 需要加入锁屏清理白名单")
+                            .setMessage(reason + "需要 " + getApplicationName(a) + " 加入到锁屏清理白名单。\n\n" +
+                                    "请点击『确定』,在弹出的『锁屏清理』列表中,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case XIAOMI_GOD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要关闭 " + getApplicationName(a) + " 的神隐模式")
+                            .setMessage(reason + "需要关闭 " + getApplicationName(a) + " 的神隐模式。\n\n" +
+                                    "请点击『确定』,在弹出的 " + getApplicationName(a) + " 神隐模式设置中,选择『无限制』,然后选择『允许定位』。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case SAMSUNG_L:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
+                            .setMessage(reason + "需要 " + getApplicationName(a) + " 在屏幕关闭时继续运行。\n\n" +
+                                    "请点击『确定』,在弹出的『智能管理器』中,点击『内存』,选择『自启动应用程序』选项卡,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case SAMSUNG_M:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
+                            .setMessage(reason + "需要 " + getApplicationName(a) + " 在屏幕关闭时继续运行。\n\n" +
+                                    "请点击『确定』,在弹出的『电池』页面中,点击『未监视的应用程序』->『添加应用程序』,勾选 " + getApplicationName(a) + ",然后点击『完成』。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case MEIZU:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 保持后台运行")
+                            .setMessage(reason + "需要允许 " + getApplicationName(a) + " 保持后台运行。\n\n" +
+                                    "请点击『确定』,在弹出的应用信息界面中,将『后台管理』选项更改为『保持后台运行』。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case MEIZU_GOD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle(getApplicationName(a) + " 需要在待机时保持运行")
+                            .setMessage(reason + "需要 " + getApplicationName(a) + " 在待机时保持运行。\n\n" +
+                                    "请点击『确定』,在弹出的『待机耗电管理』中,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case ZTE:
+                case LETV:
+                case XIAOMI:
+                case OPPO:
+                case OPPO_OLD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
+                            .setMessage(reason + "需要 " + getApplicationName(a) + " 加入到自启动白名单。\n\n" +
+                                    "请点击『确定』,在弹出的『自启动管理』中,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case COOLPAD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
+                            .setMessage(reason + "需要允许 " + getApplicationName(a) + " 的自启动。\n\n" +
+                                    "请点击『确定』,在弹出的『酷管家』中,找到『软件管理』->『自启动管理』,取消勾选 " + getApplicationName(a) + ",将 " + getApplicationName(a) + " 的状态改为『已允许』。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case VIVO_GOD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 的后台运行")
+                            .setMessage(reason + "需要允许 " + getApplicationName(a) + " 在后台高耗电时运行。\n\n" +
+                                    "请点击『确定』,在弹出的『后台高耗电』中,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case GIONEE:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle(getApplicationName(a) + " 需要加入应用自启和绿色后台白名单")
+                            .setMessage(reason + "需要允许 " + getApplicationName(a) + " 的自启动和后台运行。\n\n" +
+                                    "请点击『确定』,在弹出的『系统管家』中,分别找到『应用管理』->『应用自启』和『绿色后台』->『清理白名单』,将 " + getApplicationName(a) + " 添加到白名单。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case LETV_GOD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要禁止 " + getApplicationName(a) + " 被自动清理")
+                            .setMessage(reason + "需要禁止 " + getApplicationName(a) + " 被自动清理。\n\n" +
+                                    "请点击『确定』,在弹出的『应用保护』中,将 " + getApplicationName(a) + " 对应的开关关闭。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case LENOVO:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要允许 " + getApplicationName(a) + " 的后台运行")
+                            .setMessage(reason + "需要允许 " + getApplicationName(a) + " 的后台自启、后台 GPS 和后台运行。\n\n" +
+                                    "请点击『确定』,在弹出的『后台管理』中,分别找到『后台自启』、『后台 GPS』和『后台运行』,将 " + getApplicationName(a) + " 对应的开关打开。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+                case LENOVO_GOD:
+                    new AlertDialog.Builder(a)
+                            .setCancelable(false)
+                            .setTitle("需要关闭 " + getApplicationName(a) + " 的后台耗电优化")
+                            .setMessage(reason + "需要关闭 " + getApplicationName(a) + " 的后台耗电优化。\n\n" +
+                                    "请点击『确定』,在弹出的『后台耗电优化』中,将 " + getApplicationName(a) + " 对应的开关关闭。")
+                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
+                            })
+                            .show();
+                    showed.add(iw);
+                    break;
+            }
+        }
+        return showed;
+    }
+
+    /**
+     * 防止华为机型未加入白名单时按返回键回到桌面再锁屏后几秒钟进程被杀
+     */
+    public static void onBackPressed(Activity a) {
+        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
+        launcherIntent.addCategory(Intent.CATEGORY_HOME);
+        a.startActivity(launcherIntent);
+    }
+
+    protected Intent intent;
+    protected int type;
+
+    protected IntentWrapper(Intent intent, int type) {
+        this.intent = intent;
+        this.type = type;
+    }
+
+    /**
+     * 判断本机上是否有能处理当前Intent的Activity
+     */
+    protected boolean doesActivityExists(Context context) {
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        return list != null && list.size() > 0;
+    }
+
+    /**
+     * 安全地启动一个Activity
+     */
+    protected void startActivitySafely(Activity activityContext) {
+        try { activityContext.startActivity(intent); } catch (Exception e) { e.printStackTrace(); }
+    }
+}

BIN
keepalive/src/main/res/drawable/icon1.webp


BIN
keepalive/src/main/res/raw/no_notice.mp3


+ 3 - 0
keepalive/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">sss</string>
+</resources>

+ 16 - 0
keepalive/src/main/res/values/styles.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="SingleActivityStyle" parent="android:Theme.Holo.Light.NoActionBar">
+
+    <item name="android:windowBackground">@android:color/transparent</item>
+    <item name="android:windowFrame">@null</item>
+    <item name="android:windowNoTitle">true</item>
+    <item name="android:windowIsFloating">true</item>
+    <item name="android:windowContentOverlay">@null</item>
+    <item name="android:backgroundDimEnabled">false</item>
+    <item name="android:windowAnimationStyle">@null</item>
+    <item name="android:windowDisablePreview">true</item>
+    <item name="android:windowNoDisplay">false</item>
+
+    </style>
+</resources>

+ 4 - 0
keepalive/src/main/res/xml/net_config.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+    <base-config cleartextTrafficPermitted="true" />
+</network-security-config>

+ 17 - 0
keepalive/src/test/java/com/wdkl/ncs/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.wdkl.ncs;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 1 - 1
settings.gradle

@@ -1 +1 @@
-include ':app', ':common', ':home', ':resource', ':middleware', 'webrtc', 'rtc-chat', 'libwebrtc'
+include ':app', ':common', ':home', ':resource', ':middleware', 'webrtc', 'rtc-chat', 'libwebrtc', 'keepalive'