Pq преди 4 години
родител
ревизия
10dc42ff2f
променени са 100 файла, в които са добавени 42067 реда и са изтрити 520 реда
  1. 6 15
      BaseLibrary/build.gradle
  2. 12 1
      BaseLibrary/proguard-rules.pro
  3. 0 13
      BaseLibrary/src/main/java/com/cooleshow/base/common/BaseApplication.kt
  4. 16 0
      BaseLibrary/src/main/java/com/cooleshow/base/constanst/CacheConstants.java
  5. 27 0
      BaseLibrary/src/main/java/com/cooleshow/base/constanst/MemoryConstants.java
  6. 108 0
      BaseLibrary/src/main/java/com/cooleshow/base/constanst/PermissionConstants.java
  7. 131 0
      BaseLibrary/src/main/java/com/cooleshow/base/constanst/RegexConstants.java
  8. 28 0
      BaseLibrary/src/main/java/com/cooleshow/base/constanst/TimeConstants.java
  9. 40 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/ApiException.java
  10. 65 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/BaseResponse.java
  11. 0 12
      BaseLibrary/src/main/java/com/cooleshow/base/injection/ActivityScope.kt
  12. 0 12
      BaseLibrary/src/main/java/com/cooleshow/base/injection/PerComponentScope.kt
  13. 0 21
      BaseLibrary/src/main/java/com/cooleshow/base/injection/component/ActivityComponent.kt
  14. 0 16
      BaseLibrary/src/main/java/com/cooleshow/base/injection/component/AppComponent.kt
  15. 0 22
      BaseLibrary/src/main/java/com/cooleshow/base/injection/module/ActivityModule.kt
  16. 0 20
      BaseLibrary/src/main/java/com/cooleshow/base/injection/module/AppModule.kt
  17. 0 17
      BaseLibrary/src/main/java/com/cooleshow/base/injection/module/LifecycleProviderModule.kt
  18. 108 0
      BaseLibrary/src/main/java/com/cooleshow/base/presenter/BasePresenter.java
  19. 0 34
      BaseLibrary/src/main/java/com/cooleshow/base/presenter/BasePresenter.kt
  20. 29 0
      BaseLibrary/src/main/java/com/cooleshow/base/presenter/IPresenter.java
  21. 72 0
      BaseLibrary/src/main/java/com/cooleshow/base/rx/BaseObserver.java
  22. 30 11
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.kt
  23. 29 0
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseMVPActivity.java
  24. 0 82
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseMvpActivity.kt
  25. 0 159
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseTakePhotoActivity.kt
  26. 106 0
      BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseFragment.java
  27. 0 9
      BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseFragment.kt
  28. 64 0
      BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseMVPFragment.java
  29. 0 76
      BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseMvpFragment.kt
  30. 2140 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ActivityUtils.java
  31. 165 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/AdaptScreenUtils.java
  32. 115 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ApiUtils.java
  33. 935 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/AppUtils.java
  34. 2143 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ArrayUtils.java
  35. 718 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/BarUtils.java
  36. 115 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/BrightnessUtils.java
  37. 459 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/BusUtils.java
  38. 958 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDiskStaticUtils.java
  39. 875 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDiskUtils.java
  40. 969 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDoubleStaticUtils.java
  41. 556 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDoubleUtils.java
  42. 188 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheMemoryStaticUtils.java
  43. 168 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheMemoryUtils.java
  44. 104 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CleanUtils.java
  45. 621 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ClickUtils.java
  46. 109 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ClipboardUtils.java
  47. 35 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CloneUtils.java
  48. 54 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CloseUtils.java
  49. 949 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CollectionUtils.java
  50. 217 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ColorUtils.java
  51. 762 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ConvertUtils.java
  52. 150 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/CrashUtils.java
  53. 86 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/DebouncingUtils.java
  54. 524 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/DeviceUtils.java
  55. 130 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/DialogUtils.java
  56. 217 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/EncodeUtils.java
  57. 1184 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/EncryptUtils.java
  58. 951 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/FileIOUtils.java
  59. 1456 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/FileUtils.java
  60. 105 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/FlashlightUtils.java
  61. 1881 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/FragmentUtils.java
  62. 283 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/GsonUtils.java
  63. 2374 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ImageUtils.java
  64. 538 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/IntentUtils.java
  65. 255 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/JsonUtils.java
  66. 405 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/KeyboardUtils.java
  67. 318 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/LanguageUtils.java
  68. 1231 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/LogUtils.java
  69. 191 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/MapUtils.java
  70. 355 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/MessengerUtils.java
  71. 144 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/MetaDataUtils.java
  72. 1063 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/NetworkUtils.java
  73. 338 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/NotificationUtils.java
  74. 198 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/NumberUtils.java
  75. 279 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ObjectUtils.java
  76. 471 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/PathUtils.java
  77. 599 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/PermissionUtils.java
  78. 321 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/PhoneUtils.java
  79. 273 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ProcessUtils.java
  80. 556 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ReflectUtils.java
  81. 364 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/RegexUtils.java
  82. 303 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ResourceUtils.java
  83. 456 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/RomUtils.java
  84. 212 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/SDCardUtils.java
  85. 729 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/SPStaticUtils.java
  86. 446 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/SPUtils.java
  87. 362 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ScreenUtils.java
  88. 238 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ServiceUtils.java
  89. 691 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ShadowUtils.java
  90. 386 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ShellUtils.java
  91. 175 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/SizeUtils.java
  92. 1465 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/SpanUtils.java
  93. 262 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/StringUtils.java
  94. 1391 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ThreadUtils.java
  95. 91 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ThrowableUtils.java
  96. 1636 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/TimeUtils.java
  97. 912 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ToastUtils.java
  98. 215 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/TouchUtils.java
  99. 275 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/UiMessageUtils.java
  100. 356 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/UriUtils.java

+ 6 - 15
BaseLibrary/build.gradle

@@ -4,6 +4,7 @@ plugins {
 //    id 'kotlin-parcelize'
     id 'kotlin-android'
 //    id 'kotlin-android-extensions'
+    id 'com.jakewharton.butterknife'
 }
 apply plugin: 'kotlin-kapt'
 apply plugin: 'kotlin-android-extensions'
@@ -20,7 +21,9 @@ android {
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
 
-
+    buildFeatures {
+        viewBinding = true
+    }
 
     buildTypes {
         release {
@@ -81,11 +84,6 @@ dependencies {
 
     // BottomNavigation
     api 'com.ashokvarma.android:bottom-navigation-bar:2.2.0'
-
-    // Add Dagger dependencies
-    api "com.google.dagger:dagger:$dagger_version"
-    kapt "com.google.dagger:dagger-compiler:$dagger_version"
-
 //    implementation("io.reactivex.rxjava3:rxkotlin:3.x.y")
 //    // RxKotlin and RxAndroid
 ////    implementation "io.reactivex:rxkotlin:$rx_kotlin_version"
@@ -99,8 +97,6 @@ dependencies {
     // explicitly depend on RxJava's latest version for bug fixes and new features.
     // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
     implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
-
-
     //Retrofit
     api("com.squareup.okhttp3:okhttp:${ok_http_version}")
     api "com.squareup.okhttp3:logging-interceptor:${ok_http_version}"
@@ -108,16 +104,11 @@ dependencies {
     api "com.squareup.retrofit2:retrofit:${retrofit_version}"
     api "com.squareup.retrofit2:converter-gson:${retrofit_version}"
     api "com.squareup.retrofit2:adapter-rxjava:${retrofit_version}"
-
     //MulitStateView
     api "com.github.Kennyc1012:MultiStateView:$multi_state_view_version"
-
-    //ARouter
-//    implementation "com.alibaba:arouter-api:$arouter_api_version"
-
-
     api "com.alibaba:arouter-api:$arouter_api_version"
     kapt "com.alibaba:arouter-compiler:$arouter_api_version"
-
+    api "com.jakewharton:butterknife:10.1.0"
+    annotationProcessor "com.jakewharton:butterknife-compiler:10.1.0"
 
 }

+ 12 - 1
BaseLibrary/proguard-rules.pro

@@ -18,4 +18,15 @@
 
 # If you keep the line number information, uncomment this to
 # hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile
+-keep class butterknife.** { *; }
+-dontwarn butterknife.internal.**
+-keep class **$$ViewBinder { *; }
+
+-keepclasseswithmembernames class * {
+    @butterknife.* <fields>;
+}
+
+-keepclasseswithmembernames class * {
+    @butterknife.* <methods>;
+}

+ 0 - 13
BaseLibrary/src/main/java/com/cooleshow/base/common/BaseApplication.kt

@@ -3,9 +3,6 @@ package com.cooleshow.base.common
 import android.app.Application
 import android.content.Context
 import com.alibaba.android.arouter.launcher.ARouter
-import com.cooleshow.base.injection.component.AppComponent
-import com.cooleshow.base.injection.component.DaggerAppComponent
-import com.cooleshow.base.injection.module.AppModule
 
 
 /*
@@ -13,12 +10,10 @@ import com.cooleshow.base.injection.module.AppModule
  */
 open class BaseApplication : Application() {
 
-    lateinit var appComponent: AppComponent
 
     override fun onCreate() {
         super.onCreate()
 
-        initAppInjection()
         context = this
 
         //ARouter初始化
@@ -31,14 +26,6 @@ open class BaseApplication : Application() {
 
 
 
-
-    /*
-        Application Component初始化
-     */
-    private fun initAppInjection() {
-        appComponent = DaggerAppComponent.builder().appModule(AppModule(this)).build()
-    }
-
     /*
         全局伴生对象
      */

+ 16 - 0
BaseLibrary/src/main/java/com/cooleshow/base/constanst/CacheConstants.java

@@ -0,0 +1,16 @@
+package com.cooleshow.base.constanst;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/06/13
+ *     desc  : constants of cache
+ * </pre>
+ */
+public interface CacheConstants {
+    int SEC  = 1;
+    int MIN  = 60;
+    int HOUR = 3600;
+    int DAY  = 86400;
+}

+ 27 - 0
BaseLibrary/src/main/java/com/cooleshow/base/constanst/MemoryConstants.java

@@ -0,0 +1,27 @@
+package com.cooleshow.base.constanst;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/03/13
+ *     desc  : constants of memory
+ * </pre>
+ */
+public final class MemoryConstants {
+
+    public static final int BYTE = 1;
+    public static final int KB   = 1024;
+    public static final int MB   = 1048576;
+    public static final int GB   = 1073741824;
+
+    @IntDef({BYTE, KB, MB, GB})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Unit {
+    }
+}

+ 108 - 0
BaseLibrary/src/main/java/com/cooleshow/base/constanst/PermissionConstants.java

@@ -0,0 +1,108 @@
+package com.cooleshow.base.constanst;
+
+import android.Manifest.permission;
+import android.annotation.SuppressLint;
+import android.os.Build;
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/12/29
+ *     desc  : constants of permission
+ * </pre>
+ */
+@SuppressLint("InlinedApi")
+public final class PermissionConstants {
+
+    public static final String CALENDAR             = "CALENDAR";
+    public static final String CAMERA               = "CAMERA";
+    public static final String CONTACTS             = "CONTACTS";
+    public static final String LOCATION             = "LOCATION";
+    public static final String MICROPHONE           = "MICROPHONE";
+    public static final String PHONE                = "PHONE";
+    public static final String SENSORS              = "SENSORS";
+    public static final String SMS                  = "SMS";
+    public static final String STORAGE              = "STORAGE";
+    public static final String ACTIVITY_RECOGNITION = "ACTIVITY_RECOGNITION";
+
+    private static final String[] GROUP_CALENDAR             = {
+            permission.READ_CALENDAR, permission.WRITE_CALENDAR
+    };
+    private static final String[] GROUP_CAMERA               = {
+            permission.CAMERA
+    };
+    private static final String[] GROUP_CONTACTS             = {
+            permission.READ_CONTACTS, permission.WRITE_CONTACTS, permission.GET_ACCOUNTS
+    };
+    private static final String[] GROUP_LOCATION             = {
+            permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION, permission.ACCESS_BACKGROUND_LOCATION
+    };
+    private static final String[] GROUP_MICROPHONE           = {
+            permission.RECORD_AUDIO
+    };
+    private static final String[] GROUP_PHONE                = {
+            permission.READ_PHONE_STATE, permission.READ_PHONE_NUMBERS, permission.CALL_PHONE,
+            permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.ADD_VOICEMAIL,
+            permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS, permission.ANSWER_PHONE_CALLS
+    };
+    private static final String[] GROUP_PHONE_BELOW_O        = {
+            permission.READ_PHONE_STATE, permission.READ_PHONE_NUMBERS, permission.CALL_PHONE,
+            permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.ADD_VOICEMAIL,
+            permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS
+    };
+    private static final String[] GROUP_SENSORS              = {
+            permission.BODY_SENSORS
+    };
+    private static final String[] GROUP_SMS                  = {
+            permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS,
+            permission.RECEIVE_WAP_PUSH, permission.RECEIVE_MMS,
+    };
+    private static final String[] GROUP_STORAGE              = {
+            permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE,
+    };
+    private static final String[] GROUP_ACTIVITY_RECOGNITION = {
+            permission.ACTIVITY_RECOGNITION,
+    };
+
+    @StringDef({CALENDAR, CAMERA, CONTACTS, LOCATION, MICROPHONE, PHONE, SENSORS, SMS, STORAGE,})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PermissionGroup {
+    }
+
+    public static String[] getPermissions(@PermissionGroup final String permission) {
+        if (permission == null) return new String[0];
+        switch (permission) {
+            case CALENDAR:
+                return GROUP_CALENDAR;
+            case CAMERA:
+                return GROUP_CAMERA;
+            case CONTACTS:
+                return GROUP_CONTACTS;
+            case LOCATION:
+                return GROUP_LOCATION;
+            case MICROPHONE:
+                return GROUP_MICROPHONE;
+            case PHONE:
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+                    return GROUP_PHONE_BELOW_O;
+                } else {
+                    return GROUP_PHONE;
+                }
+            case SENSORS:
+                return GROUP_SENSORS;
+            case SMS:
+                return GROUP_SMS;
+            case STORAGE:
+                return GROUP_STORAGE;
+            case ACTIVITY_RECOGNITION:
+                return GROUP_ACTIVITY_RECOGNITION;
+        }
+        return new String[]{permission};
+    }
+}

+ 131 - 0
BaseLibrary/src/main/java/com/cooleshow/base/constanst/RegexConstants.java

@@ -0,0 +1,131 @@
+package com.cooleshow.base.constanst;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/03/13
+ *     desc  : constants of regex
+ * </pre>
+ */
+public final class RegexConstants {
+
+    /**
+     * Regex of simple mobile.
+     */
+    public static final String REGEX_MOBILE_SIMPLE = "^[1]\\d{10}$";
+    /**
+     * Regex of exact mobile.
+     * <p>china mobile: 134(0-8), 135, 136, 137, 138, 139, 147, 150, 151, 152, 157, 158, 159, 165, 172, 178, 182, 183, 184, 187, 188, 195, 197, 198</p>
+     * <p>china unicom: 130, 131, 132, 145, 155, 156, 166, 167, 175, 176, 185, 186, 196</p>
+     * <p>china telecom: 133, 149, 153, 162, 173, 177, 180, 181, 189, 190, 191, 199</p>
+     * <p>china broadcasting: 192</p>
+     * <p>global star: 1349</p>
+     * <p>virtual operator: 170, 171</p>
+     */
+    public static final String REGEX_MOBILE_EXACT  = "^((13[0-9])|(14[579])|(15[0-35-9])|(16[2567])|(17[0-35-8])|(18[0-9])|(19[0-35-9]))\\d{8}$";
+    /**
+     * Regex of telephone number.
+     */
+    public static final String REGEX_TEL           = "^0\\d{2,3}[- ]?\\d{7,8}$";
+    /**
+     * Regex of id card number which length is 15.
+     */
+    public static final String REGEX_ID_CARD15     = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$";
+    /**
+     * Regex of id card number which length is 18.
+     */
+    public static final String REGEX_ID_CARD18     = "^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9Xx])$";
+    /**
+     * Regex of email.
+     */
+    public static final String REGEX_EMAIL         = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
+    /**
+     * Regex of url.
+     */
+    public static final String REGEX_URL           = "[a-zA-z]+://[^\\s]*";
+    /**
+     * Regex of Chinese character.
+     */
+    public static final String REGEX_ZH            = "^[\\u4e00-\\u9fa5]+$";
+    /**
+     * Regex of username.
+     * <p>scope for "a-z", "A-Z", "0-9", "_", "Chinese character"</p>
+     * <p>can't end with "_"</p>
+     * <p>length is between 6 to 20</p>
+     */
+    public static final String REGEX_USERNAME      = "^[\\w\\u4e00-\\u9fa5]{6,20}(?<!_)$";
+    /**
+     * Regex of date which pattern is "yyyy-MM-dd".
+     */
+    public static final String REGEX_DATE          = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$";
+    /**
+     * Regex of ip address.
+     */
+    public static final String REGEX_IP            = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)";
+
+    ///////////////////////////////////////////////////////////////////////////
+    // The following come from http://tool.oschina.net/regex
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Regex of double-byte characters.
+     */
+    public static final String REGEX_DOUBLE_BYTE_CHAR     = "[^\\x00-\\xff]";
+    /**
+     * Regex of blank line.
+     */
+    public static final String REGEX_BLANK_LINE           = "\\n\\s*\\r";
+    /**
+     * Regex of QQ number.
+     */
+    public static final String REGEX_QQ_NUM               = "[1-9][0-9]{4,}";
+    /**
+     * Regex of postal code in China.
+     */
+    public static final String REGEX_CHINA_POSTAL_CODE    = "[1-9]\\d{5}(?!\\d)";
+    /**
+     * Regex of integer.
+     */
+    public static final String REGEX_INTEGER              = "^(-?[1-9]\\d*)|0$";
+    /**
+     * Regex of positive integer.
+     */
+    public static final String REGEX_POSITIVE_INTEGER     = "^[1-9]\\d*$";
+    /**
+     * Regex of negative integer.
+     */
+    public static final String REGEX_NEGATIVE_INTEGER     = "^-[1-9]\\d*$";
+    /**
+     * Regex of non-negative integer.
+     */
+    public static final String REGEX_NOT_NEGATIVE_INTEGER = "^[1-9]\\d*|0$";
+    /**
+     * Regex of non-positive integer.
+     */
+    public static final String REGEX_NOT_POSITIVE_INTEGER = "^-[1-9]\\d*|0$";
+    /**
+     * Regex of positive float.
+     */
+    public static final String REGEX_FLOAT                = "^-?([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*|0?\\.0+|0)$";
+    /**
+     * Regex of positive float.
+     */
+    public static final String REGEX_POSITIVE_FLOAT       = "^[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*$";
+    /**
+     * Regex of negative float.
+     */
+    public static final String REGEX_NEGATIVE_FLOAT       = "^-[1-9]\\d*\\.\\d*|-0\\.\\d*[1-9]\\d*$";
+    /**
+     * Regex of positive float.
+     */
+    public static final String REGEX_NOT_NEGATIVE_FLOAT   = "^[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*|0?\\.0+|0$";
+    /**
+     * Regex of negative float.
+     */
+    public static final String REGEX_NOT_POSITIVE_FLOAT   = "^(-([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*))|0?\\.0+|0$";
+
+    ///////////////////////////////////////////////////////////////////////////
+    // If u want more please visit http://toutiao.com/i6231678548520731137
+    ///////////////////////////////////////////////////////////////////////////
+}

+ 28 - 0
BaseLibrary/src/main/java/com/cooleshow/base/constanst/TimeConstants.java

@@ -0,0 +1,28 @@
+package com.cooleshow.base.constanst;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/03/13
+ *     desc  : constants of time
+ * </pre>
+ */
+public final class TimeConstants {
+
+    public static final int MSEC = 1;
+    public static final int SEC  = 1000;
+    public static final int MIN  = 60000;
+    public static final int HOUR = 3600000;
+    public static final int DAY  = 86400000;
+
+    @IntDef({MSEC, SEC, MIN, HOUR, DAY})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Unit {
+    }
+}

+ 40 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/ApiException.java

@@ -0,0 +1,40 @@
+package com.cooleshow.base.data.net;
+
+public class ApiException extends RuntimeException {
+
+    private int errcode;
+    private String errmsg;
+
+    public ApiException() {
+
+    }
+
+    public ApiException(int code, String msg) {
+        this.errcode = code;
+        this.errmsg = msg;
+    }
+
+    public int getErrcode() {
+        return errcode;
+    }
+
+    public void setErrcode(int errcode) {
+        this.errcode = errcode;
+    }
+
+    public String getErrmsg() {
+        return errmsg;
+    }
+
+    public void setErrmsg(String errmsg) {
+        this.errmsg = errmsg;
+    }
+
+    @Override
+    public String toString() {
+        return "ApiException{" +
+                "errcode=" + errcode +
+                ", errmsg='" + errmsg + '\'' +
+                '}';
+    }
+}

+ 65 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/BaseResponse.java

@@ -0,0 +1,65 @@
+package com.cooleshow.base.data.net;
+
+/**
+ * 网络请求返回的数据,按格式统一包装成 BaseResponse 类
+ * Created by Administrator on 2018/9/15.
+ */
+
+public class BaseResponse<T> {
+
+    private int code = -1;
+    private String msg;
+    private T data;
+
+    private boolean status;
+    private boolean error = true;
+
+    public int getErrorCode() {
+        return code;
+    }
+
+    public void setErrorCode(int code) {
+        this.code = code;
+    }
+
+    public String getErrorMsg() {
+        return msg;
+    }
+
+    public void setErrorMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    public boolean isError() {
+        return error;
+    }
+
+    public boolean getStatus() {
+        return status;
+    }
+
+    public void setStatus(boolean results) {
+        this.status = status;
+    }
+
+    public void setError(boolean error) {
+        this.error = error;
+    }
+
+    @Override
+    public String toString() {
+        return "BaseResponse{" +
+                "errorCode=" + code +
+                ", errorMsg='" + msg + '\'' +
+                ", data=" + data +
+                '}';
+    }
+}

+ 0 - 12
BaseLibrary/src/main/java/com/cooleshow/base/injection/ActivityScope.kt

@@ -1,12 +0,0 @@
-package com.cooleshow.base.injection
-
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import javax.inject.Scope
-
-/*
-    Activity级别 作用域
- */
-@Scope
-@Retention(RetentionPolicy.RUNTIME)
-annotation class ActivityScope

+ 0 - 12
BaseLibrary/src/main/java/com/cooleshow/base/injection/PerComponentScope.kt

@@ -1,12 +0,0 @@
-package com.cooleshow.base.injection
-
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import javax.inject.Scope
-
-/*
-    组件级别 作用域
- */
-@Scope
-@Retention(RetentionPolicy.RUNTIME)
-annotation class PerComponentScope

+ 0 - 21
BaseLibrary/src/main/java/com/cooleshow/base/injection/component/ActivityComponent.kt

@@ -1,21 +0,0 @@
-package com.cooleshow.base.injection.component
-
-import android.app.Activity
-import android.content.Context
-import com.cooleshow.base.injection.ActivityScope
-import com.cooleshow.base.injection.module.ActivityModule
-import com.cooleshow.base.injection.module.LifecycleProviderModule
-import com.trello.rxlifecycle4.LifecycleProvider
-import dagger.Component
-
-/*
-    Activity级别Component
- */
-@ActivityScope
-@Component(dependencies = arrayOf(AppComponent::class),
-    modules = arrayOf(ActivityModule::class,LifecycleProviderModule::class))
-interface ActivityComponent {
-    fun activity():Activity
-    fun context(): Context
-    fun lifecycleProvider(): LifecycleProvider<*>
-}

+ 0 - 16
BaseLibrary/src/main/java/com/cooleshow/base/injection/component/AppComponent.kt

@@ -1,16 +0,0 @@
-package com.cooleshow.base.injection.component
-
-import android.content.Context
-import com.cooleshow.base.injection.module.AppModule
-import dagger.Component
-import javax.inject.Singleton
-
-/*
-    Application级别Component
- */
-@Singleton
-@Component(modules = arrayOf(AppModule::class))
-interface AppComponent {
-
-    fun context():Context
-}

+ 0 - 22
BaseLibrary/src/main/java/com/cooleshow/base/injection/module/ActivityModule.kt

@@ -1,22 +0,0 @@
-package com.cooleshow.base.injection.module
-
-import android.app.Activity
-import android.content.Context
-import com.cooleshow.base.common.BaseApplication
-import com.cooleshow.base.injection.ActivityScope
-import dagger.Module
-import dagger.Provides
-import javax.inject.Singleton
-
-/*
-    Activity级别Module
- */
-@Module
-class ActivityModule(private val activity: Activity) {
-
-    @ActivityScope
-    @Provides
-    fun provideActivity(): Activity {
-        return this.activity
-    }
-}

+ 0 - 20
BaseLibrary/src/main/java/com/cooleshow/base/injection/module/AppModule.kt

@@ -1,20 +0,0 @@
-package com.cooleshow.base.injection.module
-
-import android.content.Context
-import com.cooleshow.base.common.BaseApplication
-import dagger.Module
-import dagger.Provides
-import javax.inject.Singleton
-
-/*
-    Application级别Module
- */
-@Module
-class AppModule(private val context:BaseApplication) {
-
-    @Singleton
-    @Provides
-    fun provideContext():Context{
-        return this.context
-    }
-}

+ 0 - 17
BaseLibrary/src/main/java/com/cooleshow/base/injection/module/LifecycleProviderModule.kt

@@ -1,17 +0,0 @@
-package com.cooleshow.base.injection.module
-
-import com.trello.rxlifecycle4.LifecycleProvider
-import dagger.Module
-import dagger.Provides
-
-/*
-    Rx生命周期管理能用Module
- */
-@Module
-class LifecycleProviderModule(private val lifecycleProvider: LifecycleProvider<*>) {
-
-    @Provides
-    fun provideLifecycleProvider(): LifecycleProvider<*> {
-        return this.lifecycleProvider
-    }
-}

+ 108 - 0
BaseLibrary/src/main/java/com/cooleshow/base/presenter/BasePresenter.java

@@ -0,0 +1,108 @@
+package com.cooleshow.base.presenter;
+
+import com.cooleshow.base.data.net.RetrofitFactory;
+import com.cooleshow.base.presenter.view.BaseView;
+import com.cooleshow.base.rx.BaseObserver;
+
+import java.lang.ref.WeakReference;
+
+import androidx.appcompat.app.AppCompatActivity;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.observers.DisposableObserver;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+public class BasePresenter<V extends BaseView> implements IPresenter<V> {
+
+    //    protected V view;
+    protected WeakReference<V> viewRef;
+    // 管理订阅关系,用于取消订阅
+    protected CompositeDisposable compositeDisposable;
+
+    protected AppCompatActivity mContext;
+
+    /**
+     * 绑定 View ,一般在初始化时调用
+     *
+     * @param view
+     */
+    public void attachView(V view) {
+        viewRef = new WeakReference<>(view);
+        V v = viewRef.get();
+        //        this.view = view;
+    }
+
+    /**
+     * 解除绑定 View,一般在 onDestroy 中调用
+     */
+    public void detachView() {
+        this.viewRef = null;
+        unsubscribe();
+    }
+
+    /**
+     * 是否绑定了View,一般在发起网络请求之前调用
+     *
+     * @return
+     */
+    public boolean isViewAttached() {
+        if (viewRef == null) {
+            return false;
+        }
+        return viewRef.get() != null;
+    }
+
+    public V getView() {
+        return viewRef.get();
+    }
+
+
+    /**
+     * 添加订阅
+     */
+    public void addSubscribe(Observable<?> observable, BaseObserver observer) {
+        if (compositeDisposable == null) {
+            compositeDisposable = new CompositeDisposable();
+        }
+        BaseObserver baseObserver = observable.subscribeOn(Schedulers.newThread())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribeWith(observer);
+        compositeDisposable.add(baseObserver);
+    }
+
+    /**
+     * 添加订阅,无loading
+     */
+    public void addSubscribe(Observable<?> observable, DisposableObserver observer) {
+        if (compositeDisposable == null) {
+            compositeDisposable = new CompositeDisposable();
+        }
+        DisposableObserver baseObserver = observable.subscribeOn(Schedulers.newThread())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribeWith(observer);
+        compositeDisposable.add(baseObserver);
+    }
+
+    /**
+     * 取消订阅
+     */
+    public void unsubscribe() {
+        if (compositeDisposable != null) {
+            compositeDisposable.dispose();
+        }
+        mContext = null;
+    }
+
+    protected <T> T create(Class<T> clazz) {
+        return RetrofitFactory.Companion.getInstance().create(clazz);
+    }
+
+//    protected <T> T createNoToken(Class<T> clazz) {
+//        return RetrofitClientNoToken.getInstance().getRetrofit().create(clazz);
+//    }
+//
+//    protected <T> T createUpFile(Class<T> clazz) {
+//        return RetrofitClientUpFile.getInstance().getRetrofit().create(clazz);
+//    }
+}

+ 0 - 34
BaseLibrary/src/main/java/com/cooleshow/base/presenter/BasePresenter.kt

@@ -1,34 +0,0 @@
-package com.cooleshow.base.presenter
-
-import android.content.Context
-import com.cooleshow.base.presenter.view.BaseView
-import com.cooleshow.base.utils.NetWorkUtils
-import com.trello.rxlifecycle4.LifecycleProvider
-import javax.inject.Inject
-
-/*
-    MVP中P层 基类
- */
-open class BasePresenter<T:BaseView>{
-
-    lateinit var mView:T
-
-    //Dagger注入,Rx生命周期管理
-    @Inject
-    lateinit var lifecycleProvider: LifecycleProvider<*>
-
-
-    @Inject
-    lateinit var context:Context
-
-    /*
-        检查网络是否可用
-     */
-    fun checkNetWork():Boolean{
-        if(NetWorkUtils.isNetWorkAvailable(context)){
-            return true
-        }
-        mView.onError("网络不可用")
-        return false
-    }
-}

+ 29 - 0
BaseLibrary/src/main/java/com/cooleshow/base/presenter/IPresenter.java

@@ -0,0 +1,29 @@
+package com.cooleshow.base.presenter;
+
+import com.cooleshow.base.presenter.view.BaseView;
+
+public interface IPresenter<V extends BaseView> {
+
+    /**
+     * 绑定 View ,一般在初始化时调用
+     *
+     * @param view
+     */
+    void attachView(V view);
+
+    /**
+     * 解除绑定 View,一般在 onDestroy 中调用
+     */
+    void detachView();
+
+    /**
+     * 是否绑定了View,一般在发起网络请求之前调用
+     *
+     * @return
+     */
+    boolean isViewAttached();
+
+    V getView();
+
+
+}

+ 72 - 0
BaseLibrary/src/main/java/com/cooleshow/base/rx/BaseObserver.java

@@ -0,0 +1,72 @@
+package com.cooleshow.base.rx;
+
+import com.cooleshow.base.data.net.ApiException;
+import com.cooleshow.base.data.net.BaseResponse;
+import com.cooleshow.base.presenter.view.BaseView;
+
+import io.reactivex.rxjava3.observers.DisposableObserver;
+
+
+public abstract class BaseObserver<T> extends DisposableObserver<BaseResponse<T>> {
+    private BaseView baseView;
+
+    public BaseObserver() {
+
+    }
+
+
+    public BaseObserver(BaseView baseView) {
+        this.baseView = baseView;
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    public void onNext(BaseResponse<T> baseResponse) {
+        int errcode = baseResponse.getErrorCode();
+        String errmsg = baseResponse.getErrorMsg();
+
+        if (errcode == 200) {
+            T data = baseResponse.getData();
+            // 将服务端获取到的正常数据传递给上层调用方
+            onSuccess(data);
+        } else {
+            onError(new ApiException(errcode, errmsg));
+        }
+    }
+
+    /**
+     * 回调正常数据
+     * @param data
+     */
+    protected abstract void onSuccess(T data);
+
+    /**
+     * 进度回调
+     * @param progress
+     */
+    public void onProgress(long total , long progress) {}
+
+    /**
+     * 异常处理,包括两方面数据:
+     * (1) 服务端没有没有返回数据,HttpException,如网络异常,连接超时;
+     * (2) 服务端返回了数据,但 errcode!=0,即服务端返回的data为空,如 密码错误,App登陆超时,token失效
+     */
+    @Override
+    public void onError(Throwable e) {
+        if (baseView != null) {
+            baseView.hideLoading();
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        if (baseView != null) {
+            baseView.hideLoading();
+        }
+
+    }
+}

+ 30 - 11
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.kt

@@ -3,38 +3,57 @@ package com.cooleshow.base.ui.activity
 import android.os.Bundle
 import android.view.View
 import android.widget.FrameLayout
+import butterknife.ButterKnife
+import butterknife.Unbinder
 import com.cooleshow.base.common.AppManager
+import com.cooleshow.base.presenter.view.BaseView
+import com.cooleshow.base.utils.ToastUtils
 import com.cooleshow.base.utils.helper.QMUIStatusBarHelper
+import com.cooleshow.base.widgets.ProgressLoading
 import com.trello.rxlifecycle4.components.support.RxAppCompatActivity
 
 /*
     Activity基类,业务无关
  */
-abstract class BaseActivity : RxAppCompatActivity() {
+abstract class BaseActivity : RxAppCompatActivity(), BaseView {
+    var mLoading: ProgressLoading? = null;
+    var unbinder: Unbinder? = null;
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(getLayoutId())
-        initView();
         AppManager.instance.addActivity(this)
         QMUIStatusBarHelper.translucent(this)
+        unbinder = ButterKnife.bind(this)
+        initView()
+        initData()
     }
 
     protected abstract fun getLayoutId(): Int
 
     protected abstract fun initView()
 
-    override fun onDestroy() {
-        super.onDestroy()
+    open fun initData() {
+        mLoading = ProgressLoading.create(this);
 
-        AppManager.instance.finishActivity(this)
     }
 
+    override fun showLoading() {
+        mLoading?.showLoading()
+    }
+
+    override fun hideLoading() {
+        mLoading?.hideLoading()
+    }
+
+    override fun onError(text: String) {
+        ToastUtils.showShort("onError:$text")
+    }
 
-    //获取Window中视图content
-    val contentView: View
-        get() {
-            val content = findViewById<FrameLayout>(android.R.id.content)
-            return content.getChildAt(0)
-        }
+    override fun onDestroy() {
+        super.onDestroy()
+        AppManager.instance.finishActivity(this)
+        mLoading?.stopAnim()
+        unbinder?.unbind()
+    }
 }

+ 29 - 0
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseMVPActivity.java

@@ -0,0 +1,29 @@
+package com.cooleshow.base.ui.activity;
+
+import com.cooleshow.base.presenter.BasePresenter;
+
+public abstract class BaseMVPActivity<P extends BasePresenter> extends BaseActivity {
+    protected P presenter;
+
+    @Override
+    public void initData() {
+        presenter = createPresenter();
+        // presenter 绑定 view
+        if (presenter != null) {
+            presenter.attachView(this);
+        }
+    }
+
+    protected abstract P createPresenter();
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // Activity 销毁时取消所有的订阅
+        if (presenter != null) {
+            presenter.detachView();
+            presenter = null;
+        }
+    }
+
+}

+ 0 - 82
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseMvpActivity.kt

@@ -1,82 +0,0 @@
-package com.cooleshow.base.ui.activity
-
-import android.content.Context
-import android.os.Bundle
-import android.util.AttributeSet
-import android.view.View
-import com.alibaba.android.arouter.launcher.ARouter
-import com.cooleshow.base.ui.activity.BaseActivity
-import com.cooleshow.base.common.BaseApplication
-import com.cooleshow.base.ext.showToast
-import com.cooleshow.base.injection.component.ActivityComponent
-import com.cooleshow.base.injection.component.DaggerActivityComponent
-import com.cooleshow.base.injection.module.ActivityModule
-import com.cooleshow.base.injection.module.LifecycleProviderModule
-import com.cooleshow.base.presenter.BasePresenter
-import com.cooleshow.base.presenter.view.BaseView
-import com.cooleshow.base.widgets.ProgressLoading
-import javax.inject.Inject
-
-/*
-    Activity基类,业务相关
- */
-abstract class BaseMvpActivity<T : BasePresenter<*>> : BaseActivity(), BaseView {
-
-    //Presenter泛型,Dagger注入
-    @Inject
-    lateinit var mPresenter: T
-
-    lateinit var mActivityComponent: ActivityComponent
-
-    private lateinit var mLoadingDialog: ProgressLoading
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        initActivityInjection()
-        injectComponent()
-
-        //初始加载框
-        mLoadingDialog = ProgressLoading.create(this)
-        //ARouter注册
-        ARouter.getInstance().inject(this)
-    }
-
-    /*
-        Dagger注册
-     */
-    protected abstract fun injectComponent()
-
-
-    /*
-        初始Activity Component
-     */
-    private fun initActivityInjection() {
-        mActivityComponent = DaggerActivityComponent.builder()
-            .appComponent((application as BaseApplication).appComponent)
-            .activityModule(ActivityModule(this))
-            .lifecycleProviderModule(LifecycleProviderModule(this))
-            .build()
-
-    }
-
-    /*
-        显示加载框,默认实现
-     */
-    override fun showLoading() {
-        mLoadingDialog.showLoading()
-    }
-
-    /*
-        隐藏加载框,默认实现
-     */
-    override fun hideLoading() {
-        mLoadingDialog.hideLoading()
-    }
-
-    /*
-        错误信息提示,默认实现
-     */
-    override fun onError(text: String) {
-        showToast(text)
-    }
-}

+ 0 - 159
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseTakePhotoActivity.kt

@@ -1,159 +0,0 @@
-//package com.cooleshow.base.ui.activity
-//
-//import android.content.Intent
-//import android.net.Uri
-//import android.os.Bundle
-//import android.os.Environment
-//import android.util.Log
-//import android.widget.Toast
-//import com.alibaba.android.arouter.launcher.ARouter
-//import com.bigkoo.alertview.AlertView
-//import com.bigkoo.alertview.OnItemClickListener
-//import com.cooleshow.base.common.AppManager
-//import com.cooleshow.base.ui.activity.BaseActivity
-//import com.jph.takephoto.app.TakePhoto
-//import com.jph.takephoto.app.TakePhotoImpl
-//import com.jph.takephoto.compress.CompressConfig
-//import com.jph.takephoto.model.TResult
-//import com.cooleshow.base.common.BaseApplication
-//import com.cooleshow.base.injection.component.ActivityComponent
-//import com.cooleshow.base.injection.component.DaggerActivityComponent
-//import com.cooleshow.base.injection.module.ActivityModule
-//import com.cooleshow.base.injection.module.LifecycleProviderModule
-//import com.cooleshow.base.presenter.BasePresenter
-//import com.cooleshow.base.presenter.view.BaseView
-//import com.cooleshow.base.utils.DateUtils
-//import com.cooleshow.base.widgets.ProgressLoading
-//import java.io.File
-//import javax.inject.Inject
-//
-///*
-//    存在选择图片的Activity基础封装
-// */
-//abstract open class BaseTakePhotoActivity<T : BasePresenter<*>> : BaseActivity(), BaseView, TakePhoto.TakeResultListener {
-//
-//    private lateinit var mTakePhoto:TakePhoto
-//
-//    private lateinit var mTempFile: File
-//
-//    @Inject
-//    lateinit var mPresenter: T
-//
-//    lateinit var mActivityComponent: ActivityComponent
-//
-//    private lateinit var mLoadingDialog: ProgressLoading
-//
-//    override fun onCreate(savedInstanceState: Bundle?) {
-//        super.onCreate(savedInstanceState)
-//        initActivityInjection()
-//        injectComponent()
-//
-//        mTakePhoto = TakePhotoImpl(this,this)
-//        mTakePhoto.onCreate(savedInstanceState)
-//
-//        mLoadingDialog = ProgressLoading.create(this)
-//        ARouter.getInstance().inject(this)
-//    }
-//
-//    /*
-//        Dagger注册
-//     */
-//    protected abstract fun injectComponent()
-//
-//    /*
-//        初始化Activity Component
-//     */
-//    private fun initActivityInjection() {
-//        mActivityComponent = DaggerActivityComponent.builder().appComponent((application as BaseApplication).appComponent)
-//                .activityModule(ActivityModule(this))
-//                .lifecycleProviderModule(LifecycleProviderModule(this))
-//                .build()
-//
-//    }
-//
-//    /*
-//        显示加载框,默认实现
-//     */
-//    override fun showLoading() {
-//        mLoadingDialog.showLoading()
-//    }
-//
-//    /*
-//        隐藏加载框,默认实现
-//     */
-//    override fun hideLoading() {
-//        mLoadingDialog.hideLoading()
-//    }
-//
-//    /*
-//        错误信息提示,默认实现
-//     */
-//    override fun onError(text:String) {
-//        toast(text)
-////        Toast.makeText(AppManager.instance, text, Toast.LENGTH_SHORT).show()
-//
-//    }
-//
-//    /*
-//        弹出选择框,默认实现
-//        可根据实际情况,自行修改
-//     */
-//    protected fun showAlertView() {
-//        AlertView("选择图片", "", "取消", null, arrayOf("拍照", "相册"), this,
-//                AlertView.Style.ActionSheet, OnItemClickListener { o, position ->
-//            mTakePhoto.onEnableCompress(CompressConfig.ofDefaultConfig(),false)
-//            when (position) {
-//                0 -> {
-//                    createTempFile()
-//                    mTakePhoto.onPickFromCapture(Uri.fromFile(mTempFile))
-//                }
-//                1 -> mTakePhoto.onPickFromGallery()
-//            }
-//        }
-//
-//        ).show()
-//
-//    }
-//
-//    /*
-//        获取图片,成功回调
-//     */
-//    override fun takeSuccess(result: TResult?) {
-//        Log.d("TakePhoto",result?.image?.originalPath)
-//        Log.d("TakePhoto",result?.image?.compressPath)
-//    }
-//
-//    /*
-//        获取图片,取消回调
-//     */
-//    override fun takeCancel() {
-//    }
-//
-//    /*
-//        获取图片,失败回调
-//     */
-//    override fun takeFail(result: TResult?, msg: String?) {
-//        Log.e("takePhoto",msg)
-//    }
-//
-//    /*
-//        TakePhoto默认实现
-//     */
-//    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-//        super.onActivityResult(requestCode, resultCode, data)
-//        mTakePhoto.onActivityResult(requestCode,resultCode,data)
-//    }
-//
-//    /*
-//        新建临时文件
-//     */
-//    fun createTempFile(){
-//        val tempFileName = "${DateUtils.curTime}.png"
-//        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
-//            this.mTempFile = File(Environment.getExternalStorageDirectory(),tempFileName)
-//            return
-//        }
-//
-//        this.mTempFile = File(filesDir,tempFileName)
-//    }
-//}

+ 106 - 0
BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseFragment.java

@@ -0,0 +1,106 @@
+package com.cooleshow.base.ui.fragment;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.RelativeLayout;
+
+import com.cooleshow.base.R;
+import com.cooleshow.base.presenter.view.BaseView;
+import com.cooleshow.base.utils.LogUtils;
+import com.cooleshow.base.utils.ToastUtils;
+import com.cooleshow.base.widgets.ProgressLoading;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import butterknife.ButterKnife;
+
+public abstract class BaseFragment extends Fragment implements BaseView {
+    protected Context mContext;
+    private View rootView;
+    private ViewStub mViewStubContent;
+    private ViewStub mViewStubNoData;
+    private ViewStub mViewStubError;
+    private ProgressLoading mLoading;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mContext = context;
+    }
+
+    @Override
+    public final View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                   Bundle savedInstanceState) {
+        rootView = inflater.inflate(R.layout.fragment_root, container, false);
+        initCommonView(rootView);
+        initView(rootView);
+        initData();
+        return rootView;
+    }
+
+    public void initCommonView(View view) {
+        mViewStubContent = view.findViewById(R.id.view_stub_content);
+        mViewStubNoData = view.findViewById(R.id.view_stub_nodata);
+        mViewStubError = view.findViewById(R.id.view_stub_error);
+        mViewStubContent.setLayoutResource(getLayoutResId());
+        mViewStubContent.inflate();
+        mLoading = ProgressLoading.Companion.create(requireContext());
+        ButterKnife.bind(this, rootView);
+    }
+
+    protected abstract int getLayoutResId();
+
+    protected abstract void initView(View rootView);
+
+    protected abstract void initData();
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mLoading != null) {
+            mLoading.stopAnim();
+        }
+    }
+
+    public void showLoading() {
+        if (mLoading != null) {
+            mLoading.showLoading();
+        }
+    }
+
+    public void hideLoading() {
+        if (mLoading != null) {
+            mLoading.hideLoading();
+        }
+    }
+
+    @Override
+    public void onError(@NonNull String text) {
+        LogUtils.e("onError:" + text);
+        ToastUtils.showShort("onError:" + text);
+    }
+}

+ 0 - 9
BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseFragment.kt

@@ -1,9 +0,0 @@
-package com.cooleshow.base.ui.fragment
-
-
-import com.trello.rxlifecycle4.components.support.RxFragment
-
-/*
-    Fragment基类,业务无关
- */
-open class BaseFragment : RxFragment()

+ 64 - 0
BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseMVPFragment.java

@@ -0,0 +1,64 @@
+package com.cooleshow.base.ui.fragment;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+import com.cooleshow.base.presenter.BasePresenter;
+
+public abstract class BaseMVPFragment<P extends BasePresenter> extends BaseFragment {
+
+    protected Context mContext;
+    protected P presenter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        presenter = createPresenter();
+        if (presenter != null) {
+            presenter.attachView(this);
+        }
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mContext = context;
+    }
+
+    public void refreshPresenter() {
+        presenter = createPresenter();
+        if (presenter != null) {
+            presenter.attachView(this);
+        }
+    }
+
+    protected abstract P createPresenter();
+
+    protected abstract int getLayoutResId();
+
+    protected abstract void initView(View rootView);
+
+    protected abstract void initData();
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (presenter != null) {
+            presenter.detachView();
+            presenter = null;
+        }
+    }
+}

+ 0 - 76
BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseMvpFragment.kt

@@ -1,76 +0,0 @@
-package com.cooleshow.base.ui.fragment
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import com.cooleshow.base.common.BaseApplication
-import com.cooleshow.base.injection.component.ActivityComponent
-import com.cooleshow.base.injection.module.ActivityModule
-import com.cooleshow.base.injection.module.LifecycleProviderModule
-import com.cooleshow.base.presenter.BasePresenter
-import com.cooleshow.base.presenter.view.BaseView
-import com.cooleshow.base.widgets.ProgressLoading
-
-
-import javax.inject.Inject
-
-/*
-    Fragment基类,业务相关
- */
-abstract open class BaseMvpFragment<T : BasePresenter<*>> : BaseFragment(), BaseView {
-
-    @Inject
-    lateinit var mPresenter: T
-
-    lateinit var mActivityComponent: ActivityComponent
-
-    private lateinit var mLoadingDialog: ProgressLoading
-
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
-        initActivityInjection()
-        injectComponent()
-
-        //初始加载框
-        mLoadingDialog = ProgressLoading.create(requireContext())
-        return super.onCreateView(inflater, container, savedInstanceState)
-    }
-
-    /*
-        Dagger注册
-     */
-    protected abstract fun injectComponent()
-
-    /*
-        初始化Activity级别Component
-     */
-    private fun initActivityInjection() {
-//        mActivityComponent = DaggerActivityComponent.builder().appComponent((activity?.application as BaseApplication).appComponent)
-//                .activityModule(ActivityModule(activity!!))
-//                .lifecycleProviderModule(LifecycleProviderModule(this))
-//                .build()
-
-    }
-
-    /*
-       显示加载框,默认实现
-    */
-    override fun showLoading() {
-        mLoadingDialog.showLoading()
-    }
-
-    /*
-        隐藏加载框,默认实现
-     */
-    override fun hideLoading() {
-        mLoadingDialog.hideLoading()
-    }
-
-    /*
-        错误信息提示,默认实现
-     */
-    override fun onError(text:String) {
-        Toast.makeText(BaseApplication.context, "text", Toast.LENGTH_SHORT).show()
-    }
-}

+ 2140 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ActivityUtils.java

@@ -0,0 +1,2140 @@
+package com.cooleshow.base.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.AnimRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.util.Pair;
+import androidx.fragment.app.Fragment;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/23
+ *     desc  : utils about activity
+ * </pre>
+ */
+public final class ActivityUtils {
+
+    private ActivityUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Add callbacks of activity lifecycle.
+     *
+     * @param callbacks The callbacks.
+     */
+    public static void addActivityLifecycleCallbacks(@Nullable final Utils.ActivityLifecycleCallbacks callbacks) {
+        UtilsBridge.addActivityLifecycleCallbacks(callbacks);
+    }
+
+    /**
+     * Add callbacks of activity lifecycle.
+     *
+     * @param activity  The activity.
+     * @param callbacks The callbacks.
+     */
+    public static void addActivityLifecycleCallbacks(@Nullable final Activity activity,
+                                                     @Nullable final Utils.ActivityLifecycleCallbacks callbacks) {
+        UtilsBridge.addActivityLifecycleCallbacks(activity, callbacks);
+    }
+
+    /**
+     * Remove callbacks of activity lifecycle.
+     *
+     * @param callbacks The callbacks.
+     */
+    public static void removeActivityLifecycleCallbacks(@Nullable final Utils.ActivityLifecycleCallbacks callbacks) {
+        UtilsBridge.removeActivityLifecycleCallbacks(callbacks);
+    }
+
+    /**
+     * Remove callbacks of activity lifecycle.
+     *
+     * @param activity The activity.
+     */
+    public static void removeActivityLifecycleCallbacks(@Nullable final Activity activity) {
+        UtilsBridge.removeActivityLifecycleCallbacks(activity);
+    }
+
+    /**
+     * Remove callbacks of activity lifecycle.
+     *
+     * @param activity  The activity.
+     * @param callbacks The callbacks.
+     */
+    public static void removeActivityLifecycleCallbacks(@Nullable final Activity activity,
+                                                        @Nullable final Utils.ActivityLifecycleCallbacks callbacks) {
+        UtilsBridge.removeActivityLifecycleCallbacks(activity, callbacks);
+    }
+
+    /**
+     * Return the activity by context.
+     *
+     * @param context The context.
+     * @return the activity by context.
+     */
+    @Nullable
+    public static Activity getActivityByContext(@Nullable Context context) {
+        if (context == null) return null;
+        Activity activity = getActivityByContextInner(context);
+        if (!isActivityAlive(activity)) return null;
+        return activity;
+    }
+
+    @Nullable
+    private static Activity getActivityByContextInner(@Nullable Context context) {
+        if (context == null) return null;
+        List<Context> list = new ArrayList<>();
+        while (context instanceof ContextWrapper) {
+            if (context instanceof Activity) {
+                return (Activity) context;
+            }
+            Activity activity = getActivityFromDecorContext(context);
+            if (activity != null) return activity;
+            list.add(context);
+            context = ((ContextWrapper) context).getBaseContext();
+            if (context == null) {
+                return null;
+            }
+            if (list.contains(context)) {
+                // loop context
+                return null;
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    private static Activity getActivityFromDecorContext(@Nullable Context context) {
+        if (context == null) return null;
+        if (context.getClass().getName().equals("com.android.internal.policy.DecorContext")) {
+            try {
+                Field mActivityContextField = context.getClass().getDeclaredField("mActivityContext");
+                mActivityContextField.setAccessible(true);
+                //noinspection ConstantConditions,unchecked
+                return ((WeakReference<Activity>) mActivityContextField.get(context)).get();
+            } catch (Exception ignore) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return whether the activity exists.
+     *
+     * @param pkg The name of the package.
+     * @param cls The name of the class.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isActivityExists(@NonNull final String pkg,
+                                           @NonNull final String cls) {
+        Intent intent = new Intent();
+        intent.setClassName(pkg, cls);
+        PackageManager pm = Utils.getApp().getPackageManager();
+        return !(pm.resolveActivity(intent, 0) == null ||
+                intent.resolveActivity(pm) == null ||
+                pm.queryIntentActivities(intent, 0).size() == 0);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param clz The activity class.
+     */
+    public static void startActivity(@NonNull final Class<? extends Activity> clz) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, null, context.getPackageName(), clz.getName(), null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param clz     The activity class.
+     * @param options Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Class<? extends Activity> clz,
+                                     @Nullable final Bundle options) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, null, context.getPackageName(), clz.getName(), options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param clz       The activity class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Class<? extends Activity> clz,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, null, context.getPackageName(), clz.getName(),
+                getOptionsBundle(context, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {
+            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param clz      The activity class.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz) {
+        startActivity(activity, null, activity.getPackageName(), clz.getName(), null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param clz      The activity class.
+     * @param options  Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     @Nullable final Bundle options) {
+        startActivity(activity, null, activity.getPackageName(), clz.getName(), options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity       The activity.
+     * @param clz            The activity class.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     final View... sharedElements) {
+        startActivity(activity, null, activity.getPackageName(), clz.getName(),
+                getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity  The activity.
+     * @param clz       The activity class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        startActivity(activity, null, activity.getPackageName(), clz.getName(),
+                getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras The Bundle of extras to add to this intent.
+     * @param clz    The activity class.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Class<? extends Activity> clz) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, extras, context.getPackageName(), clz.getName(), null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras  The Bundle of extras to add to this intent.
+     * @param clz     The activity class.
+     * @param options Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     @Nullable final Bundle options) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, extras, context.getPackageName(), clz.getName(), options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras    The Bundle of extras to add to this intent.
+     * @param clz       The activity class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, extras, context.getPackageName(), clz.getName(),
+                getOptionsBundle(context, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {
+            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras   The Bundle of extras to add to this intent.
+     * @param activity The activity.
+     * @param clz      The activity class.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz) {
+        startActivity(activity, extras, activity.getPackageName(), clz.getName(), null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras   The Bundle of extras to add to this intent.
+     * @param activity The activity.
+     * @param clz      The activity class.
+     * @param options  Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     @Nullable final Bundle options) {
+        startActivity(activity, extras, activity.getPackageName(), clz.getName(), options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras         The Bundle of extras to add to this intent.
+     * @param activity       The activity.
+     * @param clz            The activity class.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     final View... sharedElements) {
+        startActivity(activity, extras, activity.getPackageName(), clz.getName(),
+                getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras    The Bundle of extras to add to this intent.
+     * @param activity  The activity.
+     * @param clz       The activity class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final Class<? extends Activity> clz,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        startActivity(activity, extras, activity.getPackageName(), clz.getName(),
+                getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param pkg The name of the package.
+     * @param cls The name of the class.
+     */
+    public static void startActivity(@NonNull final String pkg,
+                                     @NonNull final String cls) {
+        startActivity(getTopActivityOrApp(), null, pkg, cls, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param pkg     The name of the package.
+     * @param cls     The name of the class.
+     * @param options Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @Nullable final Bundle options) {
+        startActivity(getTopActivityOrApp(), null, pkg, cls, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param pkg       The name of the package.
+     * @param cls       The name of the class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, null, pkg, cls, getOptionsBundle(context, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {
+            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param pkg      The name of the package.
+     * @param cls      The name of the class.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls) {
+        startActivity(activity, null, pkg, cls, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param pkg      The name of the package.
+     * @param cls      The name of the class.
+     * @param options  Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @Nullable final Bundle options) {
+        startActivity(activity, null, pkg, cls, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity       The activity.
+     * @param pkg            The name of the package.
+     * @param cls            The name of the class.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     final View... sharedElements) {
+        startActivity(activity, null, pkg, cls, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity  The activity.
+     * @param pkg       The name of the package.
+     * @param cls       The name of the class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        startActivity(activity, null, pkg, cls, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras The Bundle of extras to add to this intent.
+     * @param pkg    The name of the package.
+     * @param cls    The name of the class.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls) {
+        startActivity(getTopActivityOrApp(), extras, pkg, cls, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras  The Bundle of extras to add to this intent.
+     * @param pkg     The name of the package.
+     * @param cls     The name of the class.
+     * @param options Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @Nullable final Bundle options) {
+        startActivity(getTopActivityOrApp(), extras, pkg, cls, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras    The Bundle of extras to add to this intent.
+     * @param pkg       The name of the package.
+     * @param cls       The name of the class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        Context context = getTopActivityOrApp();
+        startActivity(context, extras, pkg, cls, getOptionsBundle(context, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {
+            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param extras   The Bundle of extras to add to this intent.
+     * @param pkg      The name of the package.
+     * @param cls      The name of the class.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls) {
+        startActivity(activity, extras, pkg, cls, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras   The Bundle of extras to add to this intent.
+     * @param activity The activity.
+     * @param pkg      The name of the package.
+     * @param cls      The name of the class.
+     * @param options  Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @Nullable final Bundle options) {
+        startActivity(activity, extras, pkg, cls, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras         The Bundle of extras to add to this intent.
+     * @param activity       The activity.
+     * @param pkg            The name of the package.
+     * @param cls            The name of the class.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     final View... sharedElements) {
+        startActivity(activity, extras, pkg, cls, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras    The Bundle of extras to add to this intent.
+     * @param pkg       The name of the package.
+     * @param cls       The name of the class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Bundle extras,
+                                     @NonNull final Activity activity,
+                                     @NonNull final String pkg,
+                                     @NonNull final String cls,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        startActivity(activity, extras, pkg, cls, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param intent The description of the activity to start.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean startActivity(@NonNull final Intent intent) {
+        return startActivity(intent, getTopActivityOrApp(), null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param intent  The description of the activity to start.
+     * @param options Additional options for how the Activity should be started.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean startActivity(@NonNull final Intent intent,
+                                        @Nullable final Bundle options) {
+        return startActivity(intent, getTopActivityOrApp(), options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param intent    The description of the activity to start.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean startActivity(@NonNull final Intent intent,
+                                        @AnimRes final int enterAnim,
+                                        @AnimRes final int exitAnim) {
+        Context context = getTopActivityOrApp();
+        boolean isSuccess = startActivity(intent, context, getOptionsBundle(context, enterAnim, exitAnim));
+        if (isSuccess) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {
+                ((Activity) context).overridePendingTransition(enterAnim, exitAnim);
+            }
+        }
+        return isSuccess;
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param intent   The description of the activity to start.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Intent intent) {
+        startActivity(intent, activity, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity The activity.
+     * @param intent   The description of the activity to start.
+     * @param options  Additional options for how the Activity should be started.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Intent intent,
+                                     @Nullable final Bundle options) {
+        startActivity(intent, activity, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity       The activity.
+     * @param intent         The description of the activity to start.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Intent intent,
+                                     final View... sharedElements) {
+        startActivity(intent, activity, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity  The activity.
+     * @param intent    The description of the activity to start.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivity(@NonNull final Activity activity,
+                                     @NonNull final Intent intent,
+                                     @AnimRes final int enterAnim,
+                                     @AnimRes final int exitAnim) {
+        startActivity(intent, activity, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity    The activity.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode) {
+        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),
+                requestCode, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity    The activity.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),
+                requestCode, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity       The activity.
+     * @param clz            The activity class.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param activity    The activity.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param activity    The activity.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode) {
+        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),
+                requestCode, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param activity    The activity.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),
+                requestCode, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras         The Bundle of extras to add to this intent.
+     * @param activity       The activity.
+     * @param clz            The activity class.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param activity    The activity.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param activity    The activity.
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param pkg         The name of the package.
+     * @param cls         The name of the class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode) {
+        startActivityForResult(activity, extras, pkg, cls, requestCode, null);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param activity    The activity.
+     * @param pkg         The name of the package.
+     * @param cls         The name of the class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(activity, extras, pkg, cls, requestCode, options);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param extras         The Bundle of extras to add to this intent.
+     * @param activity       The activity.
+     * @param pkg            The name of the package.
+     * @param cls            The name of the class.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(activity, extras, pkg, cls,
+                requestCode, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param pkg         The name of the package.
+     * @param cls         The name of the class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Activity activity,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(activity, extras, pkg, cls,
+                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param activity    The activity.
+     * @param intent      The description of the activity to start.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Intent intent,
+                                              final int requestCode) {
+        startActivityForResult(intent, activity, requestCode, null);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param activity    The activity.
+     * @param intent      The description of the activity to start.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Intent intent,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(intent, activity, requestCode, options);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param activity       The activity.
+     * @param intent         The description of the activity to start.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Intent intent,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(intent, activity,
+                requestCode, getOptionsBundle(activity, sharedElements));
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param activity    The activity.
+     * @param intent      The description of the activity to start.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Activity activity,
+                                              @NonNull final Intent intent,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(intent, activity,
+                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param fragment    The fragment.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode) {
+        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param fragment    The fragment.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param fragment       The fragment.
+     * @param clz            The activity class.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(fragment, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param fragment    The fragment.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param fragment    The fragment.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode) {
+        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, null);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param fragment    The fragment.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, options);
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras         The Bundle of extras to add to this intent.
+     * @param fragment       The fragment.
+     * @param clz            The activity class.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(fragment, sharedElements));
+    }
+
+    /**
+     * Start the activity.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param fragment    The fragment.
+     * @param clz         The activity class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final Class<? extends Activity> clz,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),
+                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param fragment    The fragment.
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param pkg         The name of the package.
+     * @param cls         The name of the class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode) {
+        startActivityForResult(fragment, extras, pkg, cls, requestCode, null);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param fragment    The fragment.
+     * @param pkg         The name of the package.
+     * @param cls         The name of the class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(fragment, extras, pkg, cls, requestCode, options);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param extras         The Bundle of extras to add to this intent.
+     * @param fragment       The fragment.
+     * @param pkg            The name of the package.
+     * @param cls            The name of the class.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(fragment, extras, pkg, cls,
+                requestCode, getOptionsBundle(fragment, sharedElements));
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param extras      The Bundle of extras to add to this intent.
+     * @param pkg         The name of the package.
+     * @param cls         The name of the class.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Bundle extras,
+                                              @NonNull final Fragment fragment,
+                                              @NonNull final String pkg,
+                                              @NonNull final String cls,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(fragment, extras, pkg, cls,
+                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param fragment    The fragment.
+     * @param intent      The description of the activity to start.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Intent intent,
+                                              final int requestCode) {
+        startActivityForResult(intent, fragment, requestCode, null);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param fragment    The fragment.
+     * @param intent      The description of the activity to start.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Intent intent,
+                                              final int requestCode,
+                                              @Nullable final Bundle options) {
+        startActivityForResult(intent, fragment, requestCode, options);
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param fragment       The fragment.
+     * @param intent         The description of the activity to start.
+     * @param requestCode    if &gt;= 0, this code will be returned in
+     *                       onActivityResult() when the activity exits.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Intent intent,
+                                              final int requestCode,
+                                              final View... sharedElements) {
+        startActivityForResult(intent, fragment,
+                requestCode, getOptionsBundle(fragment, sharedElements));
+    }
+
+    /**
+     * Start the activity for result.
+     *
+     * @param fragment    The fragment.
+     * @param intent      The description of the activity to start.
+     * @param requestCode if &gt;= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param enterAnim   A resource ID of the animation resource to use for the
+     *                    incoming activity.
+     * @param exitAnim    A resource ID of the animation resource to use for the
+     *                    outgoing activity.
+     */
+    public static void startActivityForResult(@NonNull final Fragment fragment,
+                                              @NonNull final Intent intent,
+                                              final int requestCode,
+                                              @AnimRes final int enterAnim,
+                                              @AnimRes final int exitAnim) {
+        startActivityForResult(intent, fragment,
+                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));
+    }
+
+    /**
+     * Start activities.
+     *
+     * @param intents The descriptions of the activities to start.
+     */
+    public static void startActivities(@NonNull final Intent[] intents) {
+        startActivities(intents, getTopActivityOrApp(), null);
+    }
+
+    /**
+     * Start activities.
+     *
+     * @param intents The descriptions of the activities to start.
+     * @param options Additional options for how the Activity should be started.
+     */
+    public static void startActivities(@NonNull final Intent[] intents,
+                                       @Nullable final Bundle options) {
+        startActivities(intents, getTopActivityOrApp(), options);
+    }
+
+    /**
+     * Start activities.
+     *
+     * @param intents   The descriptions of the activities to start.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivities(@NonNull final Intent[] intents,
+                                       @AnimRes final int enterAnim,
+                                       @AnimRes final int exitAnim) {
+        Context context = getTopActivityOrApp();
+        startActivities(intents, context, getOptionsBundle(context, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {
+            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start activities.
+     *
+     * @param activity The activity.
+     * @param intents  The descriptions of the activities to start.
+     */
+    public static void startActivities(@NonNull final Activity activity,
+                                       @NonNull final Intent[] intents) {
+        startActivities(intents, activity, null);
+    }
+
+    /**
+     * Start activities.
+     *
+     * @param activity The activity.
+     * @param intents  The descriptions of the activities to start.
+     * @param options  Additional options for how the Activity should be started.
+     */
+    public static void startActivities(@NonNull final Activity activity,
+                                       @NonNull final Intent[] intents,
+                                       @Nullable final Bundle options) {
+        startActivities(intents, activity, options);
+    }
+
+    /**
+     * Start activities.
+     *
+     * @param activity  The activity.
+     * @param intents   The descriptions of the activities to start.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void startActivities(@NonNull final Activity activity,
+                                       @NonNull final Intent[] intents,
+                                       @AnimRes final int enterAnim,
+                                       @AnimRes final int exitAnim) {
+        startActivities(intents, activity, getOptionsBundle(activity, enterAnim, exitAnim));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            activity.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Start home activity.
+     */
+    public static void startHomeActivity() {
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(homeIntent);
+    }
+
+    /**
+     * Start the launcher activity.
+     */
+    public static void startLauncherActivity() {
+        startLauncherActivity(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Start the launcher activity.
+     *
+     * @param pkg The name of the package.
+     */
+    public static void startLauncherActivity(@NonNull final String pkg) {
+        String launcherActivity = getLauncherActivity(pkg);
+        if (TextUtils.isEmpty(launcherActivity)) return;
+        startActivity(pkg, launcherActivity);
+    }
+
+    /**
+     * Return the list of activity.
+     *
+     * @return the list of activity
+     */
+    public static List<Activity> getActivityList() {
+        return UtilsBridge.getActivityList();
+    }
+
+    /**
+     * Return the name of launcher activity.
+     *
+     * @return the name of launcher activity
+     */
+    public static String getLauncherActivity() {
+        return getLauncherActivity(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the name of launcher activity.
+     *
+     * @param pkg The name of the package.
+     * @return the name of launcher activity
+     */
+    public static String getLauncherActivity(@NonNull final String pkg) {
+        if (UtilsBridge.isSpace(pkg)) return "";
+        Intent intent = new Intent(Intent.ACTION_MAIN, null);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setPackage(pkg);
+        PackageManager pm = Utils.getApp().getPackageManager();
+        List<ResolveInfo> info = pm.queryIntentActivities(intent, 0);
+        if (info == null || info.size() == 0) {
+            return "";
+        }
+        return info.get(0).activityInfo.name;
+    }
+
+    /**
+     * Return the list of main activities.
+     *
+     * @return the list of main activities
+     */
+    public static List<String> getMainActivities() {
+        return getMainActivities(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the list of main activities.
+     *
+     * @param pkg The name of the package.
+     * @return the list of main activities
+     */
+    public static List<String> getMainActivities(@NonNull final String pkg) {
+        List<String> ret = new ArrayList<>();
+        Intent intent = new Intent(Intent.ACTION_MAIN, null);
+        intent.setPackage(pkg);
+        PackageManager pm = Utils.getApp().getPackageManager();
+        List<ResolveInfo> info = pm.queryIntentActivities(intent, 0);
+        int size = info.size();
+        if (size == 0) return ret;
+        for (int i = 0; i < size; i++) {
+            ResolveInfo ri = info.get(i);
+            if (ri.activityInfo.processName.equals(pkg)) {
+                ret.add(ri.activityInfo.name);
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Return the top activity in activity's stack.
+     *
+     * @return the top activity in activity's stack
+     */
+    public static Activity getTopActivity() {
+        return UtilsBridge.getTopActivity();
+    }
+
+    /**
+     * Return whether the activity is alive.
+     *
+     * @param context The context.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isActivityAlive(final Context context) {
+        return isActivityAlive(getActivityByContext(context));
+    }
+
+    /**
+     * Return whether the activity is alive.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isActivityAlive(final Activity activity) {
+        return activity != null && !activity.isFinishing()
+                && (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed());
+    }
+
+    /**
+     * Return whether the activity exists in activity's stack.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isActivityExistsInStack(@NonNull final Activity activity) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity aActivity : activities) {
+            if (aActivity.equals(activity)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return whether the activity exists in activity's stack.
+     *
+     * @param clz The activity class.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isActivityExistsInStack(@NonNull final Class<? extends Activity> clz) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity aActivity : activities) {
+            if (aActivity.getClass().equals(clz)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Finish the activity.
+     *
+     * @param activity The activity.
+     */
+    public static void finishActivity(@NonNull final Activity activity) {
+        finishActivity(activity, false);
+    }
+
+    /**
+     * Finish the activity.
+     *
+     * @param activity   The activity.
+     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+     */
+    public static void finishActivity(@NonNull final Activity activity, final boolean isLoadAnim) {
+        activity.finish();
+        if (!isLoadAnim) {
+            activity.overridePendingTransition(0, 0);
+        }
+    }
+
+    /**
+     * Finish the activity.
+     *
+     * @param activity  The activity.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void finishActivity(@NonNull final Activity activity,
+                                      @AnimRes final int enterAnim,
+                                      @AnimRes final int exitAnim) {
+        activity.finish();
+        activity.overridePendingTransition(enterAnim, exitAnim);
+    }
+
+    /**
+     * Finish the activity.
+     *
+     * @param clz The activity class.
+     */
+    public static void finishActivity(@NonNull final Class<? extends Activity> clz) {
+        finishActivity(clz, false);
+    }
+
+    /**
+     * Finish the activity.
+     *
+     * @param clz        The activity class.
+     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+     */
+    public static void finishActivity(@NonNull final Class<? extends Activity> clz,
+                                      final boolean isLoadAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity activity : activities) {
+            if (activity.getClass().equals(clz)) {
+                activity.finish();
+                if (!isLoadAnim) {
+                    activity.overridePendingTransition(0, 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * Finish the activity.
+     *
+     * @param clz       The activity class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void finishActivity(@NonNull final Class<? extends Activity> clz,
+                                      @AnimRes final int enterAnim,
+                                      @AnimRes final int exitAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity activity : activities) {
+            if (activity.getClass().equals(clz)) {
+                activity.finish();
+                activity.overridePendingTransition(enterAnim, exitAnim);
+            }
+        }
+    }
+
+    /**
+     * Finish to the activity.
+     *
+     * @param activity      The activity.
+     * @param isIncludeSelf True to include the activity, false otherwise.
+     */
+    public static boolean finishToActivity(@NonNull final Activity activity,
+                                           final boolean isIncludeSelf) {
+        return finishToActivity(activity, isIncludeSelf, false);
+    }
+
+    /**
+     * Finish to the activity.
+     *
+     * @param activity      The activity.
+     * @param isIncludeSelf True to include the activity, false otherwise.
+     * @param isLoadAnim    True to use animation for the outgoing activity, false otherwise.
+     */
+    public static boolean finishToActivity(@NonNull final Activity activity,
+                                           final boolean isIncludeSelf,
+                                           final boolean isLoadAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity act : activities) {
+            if (act.equals(activity)) {
+                if (isIncludeSelf) {
+                    finishActivity(act, isLoadAnim);
+                }
+                return true;
+            }
+            finishActivity(act, isLoadAnim);
+        }
+        return false;
+    }
+
+    /**
+     * Finish to the activity.
+     *
+     * @param activity      The activity.
+     * @param isIncludeSelf True to include the activity, false otherwise.
+     * @param enterAnim     A resource ID of the animation resource to use for the
+     *                      incoming activity.
+     * @param exitAnim      A resource ID of the animation resource to use for the
+     *                      outgoing activity.
+     */
+    public static boolean finishToActivity(@NonNull final Activity activity,
+                                           final boolean isIncludeSelf,
+                                           @AnimRes final int enterAnim,
+                                           @AnimRes final int exitAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity act : activities) {
+            if (act.equals(activity)) {
+                if (isIncludeSelf) {
+                    finishActivity(act, enterAnim, exitAnim);
+                }
+                return true;
+            }
+            finishActivity(act, enterAnim, exitAnim);
+        }
+        return false;
+    }
+
+    /**
+     * Finish to the activity.
+     *
+     * @param clz           The activity class.
+     * @param isIncludeSelf True to include the activity, false otherwise.
+     */
+    public static boolean finishToActivity(@NonNull final Class<? extends Activity> clz,
+                                           final boolean isIncludeSelf) {
+        return finishToActivity(clz, isIncludeSelf, false);
+    }
+
+    /**
+     * Finish to the activity.
+     *
+     * @param clz           The activity class.
+     * @param isIncludeSelf True to include the activity, false otherwise.
+     * @param isLoadAnim    True to use animation for the outgoing activity, false otherwise.
+     */
+    public static boolean finishToActivity(@NonNull final Class<? extends Activity> clz,
+                                           final boolean isIncludeSelf,
+                                           final boolean isLoadAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity act : activities) {
+            if (act.getClass().equals(clz)) {
+                if (isIncludeSelf) {
+                    finishActivity(act, isLoadAnim);
+                }
+                return true;
+            }
+            finishActivity(act, isLoadAnim);
+        }
+        return false;
+    }
+
+    /**
+     * Finish to the activity.
+     *
+     * @param clz           The activity class.
+     * @param isIncludeSelf True to include the activity, false otherwise.
+     * @param enterAnim     A resource ID of the animation resource to use for the
+     *                      incoming activity.
+     * @param exitAnim      A resource ID of the animation resource to use for the
+     *                      outgoing activity.
+     */
+    public static boolean finishToActivity(@NonNull final Class<? extends Activity> clz,
+                                           final boolean isIncludeSelf,
+                                           @AnimRes final int enterAnim,
+                                           @AnimRes final int exitAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity act : activities) {
+            if (act.getClass().equals(clz)) {
+                if (isIncludeSelf) {
+                    finishActivity(act, enterAnim, exitAnim);
+                }
+                return true;
+            }
+            finishActivity(act, enterAnim, exitAnim);
+        }
+        return false;
+    }
+
+    /**
+     * Finish the activities whose type not equals the activity class.
+     *
+     * @param clz The activity class.
+     */
+    public static void finishOtherActivities(@NonNull final Class<? extends Activity> clz) {
+        finishOtherActivities(clz, false);
+    }
+
+
+    /**
+     * Finish the activities whose type not equals the activity class.
+     *
+     * @param clz        The activity class.
+     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+     */
+    public static void finishOtherActivities(@NonNull final Class<? extends Activity> clz,
+                                             final boolean isLoadAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity act : activities) {
+            if (!act.getClass().equals(clz)) {
+                finishActivity(act, isLoadAnim);
+            }
+        }
+    }
+
+    /**
+     * Finish the activities whose type not equals the activity class.
+     *
+     * @param clz       The activity class.
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void finishOtherActivities(@NonNull final Class<? extends Activity> clz,
+                                             @AnimRes final int enterAnim,
+                                             @AnimRes final int exitAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (Activity act : activities) {
+            if (!act.getClass().equals(clz)) {
+                finishActivity(act, enterAnim, exitAnim);
+            }
+        }
+    }
+
+    /**
+     * Finish all of activities.
+     */
+    public static void finishAllActivities() {
+        finishAllActivities(false);
+    }
+
+    /**
+     * Finish all of activities.
+     *
+     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+     */
+    public static void finishAllActivities(final boolean isLoadAnim) {
+        List<Activity> activityList = UtilsBridge.getActivityList();
+        for (Activity act : activityList) {
+            // sActivityList remove the index activity at onActivityDestroyed
+            act.finish();
+            if (!isLoadAnim) {
+                act.overridePendingTransition(0, 0);
+            }
+        }
+    }
+
+    /**
+     * Finish all of activities.
+     *
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void finishAllActivities(@AnimRes final int enterAnim,
+                                           @AnimRes final int exitAnim) {
+        List<Activity> activityList = UtilsBridge.getActivityList();
+        for (Activity act : activityList) {
+            // sActivityList remove the index activity at onActivityDestroyed
+            act.finish();
+            act.overridePendingTransition(enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Finish all of activities except the newest activity.
+     */
+    public static void finishAllActivitiesExceptNewest() {
+        finishAllActivitiesExceptNewest(false);
+    }
+
+    /**
+     * Finish all of activities except the newest activity.
+     *
+     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+     */
+    public static void finishAllActivitiesExceptNewest(final boolean isLoadAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (int i = 1; i < activities.size(); i++) {
+            finishActivity(activities.get(i), isLoadAnim);
+        }
+    }
+
+    /**
+     * Finish all of activities except the newest activity.
+     *
+     * @param enterAnim A resource ID of the animation resource to use for the
+     *                  incoming activity.
+     * @param exitAnim  A resource ID of the animation resource to use for the
+     *                  outgoing activity.
+     */
+    public static void finishAllActivitiesExceptNewest(@AnimRes final int enterAnim,
+                                                       @AnimRes final int exitAnim) {
+        List<Activity> activities = UtilsBridge.getActivityList();
+        for (int i = 1; i < activities.size(); i++) {
+            finishActivity(activities.get(i), enterAnim, exitAnim);
+        }
+    }
+
+    /**
+     * Return the icon of activity.
+     *
+     * @param activity The activity.
+     * @return the icon of activity
+     */
+    @Nullable
+    public static Drawable getActivityIcon(@NonNull final Activity activity) {
+        return getActivityIcon(activity.getComponentName());
+    }
+
+    /**
+     * Return the icon of activity.
+     *
+     * @param clz The activity class.
+     * @return the icon of activity
+     */
+    @Nullable
+    public static Drawable getActivityIcon(@NonNull final Class<? extends Activity> clz) {
+        return getActivityIcon(new ComponentName(Utils.getApp(), clz));
+    }
+
+    /**
+     * Return the icon of activity.
+     *
+     * @param activityName The name of activity.
+     * @return the icon of activity
+     */
+    @Nullable
+    public static Drawable getActivityIcon(@NonNull final ComponentName activityName) {
+        PackageManager pm = Utils.getApp().getPackageManager();
+        try {
+            return pm.getActivityIcon(activityName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Return the logo of activity.
+     *
+     * @param activity The activity.
+     * @return the logo of activity
+     */
+    @Nullable
+    public static Drawable getActivityLogo(@NonNull final Activity activity) {
+        return getActivityLogo(activity.getComponentName());
+    }
+
+    /**
+     * Return the logo of activity.
+     *
+     * @param clz The activity class.
+     * @return the logo of activity
+     */
+    @Nullable
+    public static Drawable getActivityLogo(@NonNull final Class<? extends Activity> clz) {
+        return getActivityLogo(new ComponentName(Utils.getApp(), clz));
+    }
+
+    /**
+     * Return the logo of activity.
+     *
+     * @param activityName The name of activity.
+     * @return the logo of activity
+     */
+    @Nullable
+    public static Drawable getActivityLogo(@NonNull final ComponentName activityName) {
+        PackageManager pm = Utils.getApp().getPackageManager();
+        try {
+            return pm.getActivityLogo(activityName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static void startActivity(final Context context,
+                                      final Bundle extras,
+                                      final String pkg,
+                                      final String cls,
+                                      @Nullable final Bundle options) {
+        Intent intent = new Intent();
+        if (extras != null) intent.putExtras(extras);
+        intent.setComponent(new ComponentName(pkg, cls));
+        startActivity(intent, context, options);
+    }
+
+    private static boolean startActivity(final Intent intent,
+                                         final Context context,
+                                         final Bundle options) {
+        if (!isIntentAvailable(intent)) {
+            Log.e("ActivityUtils", "intent is unavailable");
+            return false;
+        }
+        if (!(context instanceof Activity)) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            context.startActivity(intent, options);
+        } else {
+            context.startActivity(intent);
+        }
+        return true;
+    }
+
+    private static boolean isIntentAvailable(final Intent intent) {
+        return Utils.getApp()
+                .getPackageManager()
+                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                .size() > 0;
+    }
+
+    private static boolean startActivityForResult(final Activity activity,
+                                                  final Bundle extras,
+                                                  final String pkg,
+                                                  final String cls,
+                                                  final int requestCode,
+                                                  @Nullable final Bundle options) {
+        Intent intent = new Intent();
+        if (extras != null) intent.putExtras(extras);
+        intent.setComponent(new ComponentName(pkg, cls));
+        return startActivityForResult(intent, activity, requestCode, options);
+    }
+
+    private static boolean startActivityForResult(final Intent intent,
+                                                  final Activity activity,
+                                                  final int requestCode,
+                                                  @Nullable final Bundle options) {
+        if (!isIntentAvailable(intent)) {
+            Log.e("ActivityUtils", "intent is unavailable");
+            return false;
+        }
+        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            activity.startActivityForResult(intent, requestCode, options);
+        } else {
+            activity.startActivityForResult(intent, requestCode);
+        }
+        return true;
+    }
+
+    private static void startActivities(final Intent[] intents,
+                                        final Context context,
+                                        @Nullable final Bundle options) {
+        if (!(context instanceof Activity)) {
+            for (Intent intent : intents) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            }
+        }
+        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            context.startActivities(intents, options);
+        } else {
+            context.startActivities(intents);
+        }
+    }
+
+    private static boolean startActivityForResult(final Fragment fragment,
+                                                  final Bundle extras,
+                                                  final String pkg,
+                                                  final String cls,
+                                                  final int requestCode,
+                                                  @Nullable final Bundle options) {
+        Intent intent = new Intent();
+        if (extras != null) intent.putExtras(extras);
+        intent.setComponent(new ComponentName(pkg, cls));
+        return startActivityForResult(intent, fragment, requestCode, options);
+    }
+
+    private static boolean startActivityForResult(final Intent intent,
+                                                  final Fragment fragment,
+                                                  final int requestCode,
+                                                  @Nullable final Bundle options) {
+        if (!isIntentAvailable(intent)) {
+            Log.e("ActivityUtils", "intent is unavailable");
+            return false;
+        }
+        if (fragment.getActivity() == null) {
+            Log.e("ActivityUtils", "Fragment " + fragment + " not attached to Activity");
+            return false;
+        }
+        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            fragment.startActivityForResult(intent, requestCode, options);
+        } else {
+            fragment.startActivityForResult(intent, requestCode);
+        }
+        return true;
+    }
+
+    private static Bundle getOptionsBundle(final Fragment fragment,
+                                           final int enterAnim,
+                                           final int exitAnim) {
+        Activity activity = fragment.getActivity();
+        if (activity == null) return null;
+        return ActivityOptionsCompat.makeCustomAnimation(activity, enterAnim, exitAnim).toBundle();
+    }
+
+    private static Bundle getOptionsBundle(final Context context,
+                                           final int enterAnim,
+                                           final int exitAnim) {
+        return ActivityOptionsCompat.makeCustomAnimation(context, enterAnim, exitAnim).toBundle();
+    }
+
+    private static Bundle getOptionsBundle(final Fragment fragment,
+                                           final View[] sharedElements) {
+        Activity activity = fragment.getActivity();
+        if (activity == null) return null;
+        return getOptionsBundle(activity, sharedElements);
+    }
+
+    private static Bundle getOptionsBundle(final Activity activity,
+                                           final View[] sharedElements) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
+        if (sharedElements == null) return null;
+        int len = sharedElements.length;
+        if (len <= 0) return null;
+        @SuppressWarnings("unchecked")
+        Pair<View, String>[] pairs = new Pair[len];
+        for (int i = 0; i < len; i++) {
+            pairs[i] = Pair.create(sharedElements[i], sharedElements[i].getTransitionName());
+        }
+        return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs).toBundle();
+    }
+
+    private static Context getTopActivityOrApp() {
+        if (UtilsBridge.isAppForeground()) {
+            Activity topActivity = getTopActivity();
+            return topActivity == null ? Utils.getApp() : topActivity;
+        } else {
+            return Utils.getApp();
+        }
+    }
+}

+ 165 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/AdaptScreenUtils.java

@@ -0,0 +1,165 @@
+package com.cooleshow.base.utils;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/11/15
+ *     desc  : utils about adapt screen
+ * </pre>
+ */
+public final class AdaptScreenUtils {
+
+    private static List<Field> sMetricsFields;
+
+    private AdaptScreenUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Adapt for the horizontal screen, and call it in {@link android.app.Activity#getResources()}.
+     */
+    @NonNull
+    public static Resources adaptWidth(@NonNull final Resources resources, final int designWidth) {
+        float newXdpi = (resources.getDisplayMetrics().widthPixels * 72f) / designWidth;
+        applyDisplayMetrics(resources, newXdpi);
+        return resources;
+    }
+
+    /**
+     * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.
+     */
+    @NonNull
+    public static Resources adaptHeight(@NonNull final Resources resources, final int designHeight) {
+        return adaptHeight(resources, designHeight, false);
+    }
+
+    /**
+     * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.
+     */
+    @NonNull
+    public static Resources adaptHeight(@NonNull final Resources resources, final int designHeight, final boolean includeNavBar) {
+        float screenHeight = (resources.getDisplayMetrics().heightPixels
+                + (includeNavBar ? getNavBarHeight(resources) : 0)) * 72f;
+        float newXdpi = screenHeight / designHeight;
+        applyDisplayMetrics(resources, newXdpi);
+        return resources;
+    }
+
+    private static int getNavBarHeight(@NonNull final Resources resources) {
+        int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
+        if (resourceId != 0) {
+            return resources.getDimensionPixelSize(resourceId);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * @param resources The resources.
+     * @return the resource
+     */
+    @NonNull
+    public static Resources closeAdapt(@NonNull final Resources resources) {
+        float newXdpi = Resources.getSystem().getDisplayMetrics().density * 72f;
+        applyDisplayMetrics(resources, newXdpi);
+        return resources;
+    }
+
+    /**
+     * Value of pt to value of px.
+     *
+     * @param ptValue The value of pt.
+     * @return value of px
+     */
+    public static int pt2Px(final float ptValue) {
+        DisplayMetrics metrics = Utils.getApp().getResources().getDisplayMetrics();
+        return (int) (ptValue * metrics.xdpi / 72f + 0.5);
+    }
+
+    /**
+     * Value of px to value of pt.
+     *
+     * @param pxValue The value of px.
+     * @return value of pt
+     */
+    public static int px2Pt(final float pxValue) {
+        DisplayMetrics metrics = Utils.getApp().getResources().getDisplayMetrics();
+        return (int) (pxValue * 72 / metrics.xdpi + 0.5);
+    }
+
+    private static void applyDisplayMetrics(@NonNull final Resources resources, final float newXdpi) {
+        resources.getDisplayMetrics().xdpi = newXdpi;
+        Utils.getApp().getResources().getDisplayMetrics().xdpi = newXdpi;
+        applyOtherDisplayMetrics(resources, newXdpi);
+    }
+
+    static Runnable getPreLoadRunnable() {
+        return new Runnable() {
+            @Override
+            public void run() {
+                preLoad();
+            }
+        };
+    }
+
+    private static void preLoad() {
+        applyDisplayMetrics(Resources.getSystem(), Resources.getSystem().getDisplayMetrics().xdpi);
+    }
+
+    private static void applyOtherDisplayMetrics(final Resources resources, final float newXdpi) {
+        if (sMetricsFields == null) {
+            sMetricsFields = new ArrayList<>();
+            Class resCls = resources.getClass();
+            Field[] declaredFields = resCls.getDeclaredFields();
+            while (declaredFields != null && declaredFields.length > 0) {
+                for (Field field : declaredFields) {
+                    if (field.getType().isAssignableFrom(DisplayMetrics.class)) {
+                        field.setAccessible(true);
+                        DisplayMetrics tmpDm = getMetricsFromField(resources, field);
+                        if (tmpDm != null) {
+                            sMetricsFields.add(field);
+                            tmpDm.xdpi = newXdpi;
+                        }
+                    }
+                }
+                resCls = resCls.getSuperclass();
+                if (resCls != null) {
+                    declaredFields = resCls.getDeclaredFields();
+                } else {
+                    break;
+                }
+            }
+        } else {
+            applyMetricsFields(resources, newXdpi);
+        }
+    }
+
+    private static void applyMetricsFields(final Resources resources, final float newXdpi) {
+        for (Field metricsField : sMetricsFields) {
+            try {
+                DisplayMetrics dm = (DisplayMetrics) metricsField.get(resources);
+                if (dm != null) dm.xdpi = newXdpi;
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static DisplayMetrics getMetricsFromField(final Resources resources, final Field field) {
+        try {
+            return (DisplayMetrics) field.get(resources);
+        } catch (Exception ignore) {
+            return null;
+        }
+    }
+}

+ 115 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ApiUtils.java

@@ -0,0 +1,115 @@
+package com.cooleshow.base.utils;
+
+import android.util.Log;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/07/09
+ *     desc  : utils about api
+ * </pre>
+ */
+public final class ApiUtils {
+
+    private static final String TAG = "ApiUtils";
+
+    private Map<Class, BaseApi> mApiMap           = new ConcurrentHashMap<>();
+    private Map<Class, Class>   mInjectApiImplMap = new HashMap<>();
+
+    private ApiUtils() {
+        init();
+    }
+
+    /**
+     * It'll be injected the implClasses who have {@link ApiUtils.Api} annotation
+     * by function of {@link ApiUtils#registerImpl} when execute transform task.
+     */
+    private void init() {/*inject*/}
+
+    private void registerImpl(Class implClass) {
+        mInjectApiImplMap.put(implClass.getSuperclass(), implClass);
+    }
+
+    /**
+     * Get api.
+     *
+     * @param apiClass The class of api.
+     * @param <T>      The type.
+     * @return the api
+     */
+    @Nullable
+    public static <T extends BaseApi> T getApi(@NonNull final Class<T> apiClass) {
+        return getInstance().getApiInner(apiClass);
+    }
+
+    public static void register(@Nullable Class<? extends BaseApi> implClass) {
+        if (implClass == null) return;
+        getInstance().registerImpl(implClass);
+    }
+
+    @NonNull
+    public static String toString_() {
+        return getInstance().toString();
+    }
+
+    @Override
+    public String toString() {
+        return "ApiUtils: " + mInjectApiImplMap;
+    }
+
+    private static ApiUtils getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    private <Result> Result getApiInner(Class apiClass) {
+        BaseApi api = mApiMap.get(apiClass);
+        if (api != null) {
+            return (Result) api;
+        }
+        synchronized (apiClass) {
+            api = mApiMap.get(apiClass);
+            if (api != null) {
+                return (Result) api;
+            }
+            Class implClass = mInjectApiImplMap.get(apiClass);
+            if (implClass != null) {
+                try {
+                    api = (BaseApi) implClass.newInstance();
+                    mApiMap.put(apiClass, api);
+                    return (Result) api;
+                } catch (Exception ignore) {
+                    Log.e(TAG, "The <" + implClass + "> has no parameterless constructor.");
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "The <" + apiClass + "> doesn't implement.");
+                return null;
+            }
+        }
+    }
+
+    private static class LazyHolder {
+        private static final ApiUtils INSTANCE = new ApiUtils();
+    }
+
+    @Target({ElementType.TYPE})
+    @Retention(RetentionPolicy.CLASS)
+    public @interface Api {
+        boolean isMock() default false;
+    }
+
+    public static class BaseApi {
+    }
+}

+ 935 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/AppUtils.java

@@ -0,0 +1,935 @@
+package com.cooleshow.base.utils;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about app
+ * </pre>
+ */
+public final class AppUtils {
+
+    private AppUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Register the status of application changed listener.
+     *
+     * @param listener The status of application changed listener
+     */
+    public static void registerAppStatusChangedListener(@NonNull final Utils.OnAppStatusChangedListener listener) {
+        UtilsBridge.addOnAppStatusChangedListener(listener);
+    }
+
+    /**
+     * Unregister the status of application changed listener.
+     *
+     * @param listener The status of application changed listener
+     */
+    public static void unregisterAppStatusChangedListener(@NonNull final Utils.OnAppStatusChangedListener listener) {
+        UtilsBridge.removeOnAppStatusChangedListener(listener);
+    }
+
+    /**
+     * Install the app.
+     * <p>Target APIs greater than 25 must hold
+     * {@code <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />}</p>
+     *
+     * @param filePath The path of file.
+     */
+    public static void installApp(final String filePath) {
+        installApp(UtilsBridge.getFileByPath(filePath));
+    }
+
+    /**
+     * Install the app.
+     * <p>Target APIs greater than 25 must hold
+     * {@code <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />}</p>
+     *
+     * @param file The file.
+     */
+    public static void installApp(final File file) {
+        Intent installAppIntent = UtilsBridge.getInstallAppIntent(file);
+        if (installAppIntent == null) return;
+        Utils.getApp().startActivity(installAppIntent);
+    }
+
+    /**
+     * Install the app.
+     * <p>Target APIs greater than 25 must hold
+     * {@code <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />}</p>
+     *
+     * @param uri The uri.
+     */
+    public static void installApp(final Uri uri) {
+        Intent installAppIntent = UtilsBridge.getInstallAppIntent(uri);
+        if (installAppIntent == null) return;
+        Utils.getApp().startActivity(installAppIntent);
+    }
+
+    /**
+     * Uninstall the app.
+     * <p>Target APIs greater than 25 must hold
+     * Must hold {@code <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />}</p>
+     *
+     * @param packageName The name of the package.
+     */
+    public static void uninstallApp(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return;
+        Utils.getApp().startActivity(UtilsBridge.getUninstallAppIntent(packageName));
+    }
+
+    /**
+     * Return whether the app is installed.
+     *
+     * @param pkgName The name of the package.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppInstalled(final String pkgName) {
+        if (UtilsBridge.isSpace(pkgName)) return false;
+        PackageManager pm = Utils.getApp().getPackageManager();
+        try {
+            return pm.getApplicationInfo(pkgName, 0).enabled;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Return whether the application with root permission.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppRoot() {
+        ShellUtils.CommandResult result = UtilsBridge.execCmd("echo root", true);
+        return result.result == 0;
+    }
+
+    /**
+     * Return whether it is a debug application.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppDebug() {
+        return isAppDebug(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return whether it is a debug application.
+     *
+     * @param packageName The name of the package.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppDebug(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return false;
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+            return (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Return whether it is a system application.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppSystem() {
+        return isAppSystem(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return whether it is a system application.
+     *
+     * @param packageName The name of the package.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppSystem(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return false;
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+            return (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Return whether application is foreground.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppForeground() {
+        return UtilsBridge.isAppForeground();
+    }
+
+    /**
+     * Return whether application is foreground.
+     * <p>Target APIs greater than 21 must hold
+     * {@code <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />}</p>
+     *
+     * @param pkgName The name of the package.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppForeground(@NonNull final String pkgName) {
+        return !UtilsBridge.isSpace(pkgName) && pkgName.equals(UtilsBridge.getForegroundProcessName());
+    }
+
+    /**
+     * Return whether application is running.
+     *
+     * @param pkgName The name of the package.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppRunning(final String pkgName) {
+        if (UtilsBridge.isSpace(pkgName)) return false;
+        ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        if (am != null) {
+            List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(Integer.MAX_VALUE);
+            if (taskInfo != null && taskInfo.size() > 0) {
+                for (ActivityManager.RunningTaskInfo aInfo : taskInfo) {
+                    if (aInfo.baseActivity != null) {
+                        if (pkgName.equals(aInfo.baseActivity.getPackageName())) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            List<ActivityManager.RunningServiceInfo> serviceInfo = am.getRunningServices(Integer.MAX_VALUE);
+            if (serviceInfo != null && serviceInfo.size() > 0) {
+                for (ActivityManager.RunningServiceInfo aInfo : serviceInfo) {
+                    if (pkgName.equals(aInfo.service.getPackageName())) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Launch the application.
+     *
+     * @param packageName The name of the package.
+     */
+    public static void launchApp(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return;
+        Intent launchAppIntent = UtilsBridge.getLaunchAppIntent(packageName);
+        if (launchAppIntent == null) {
+            Log.e("AppUtils", "Didn't exist launcher activity.");
+            return;
+        }
+        Utils.getApp().startActivity(launchAppIntent);
+    }
+
+    /**
+     * Relaunch the application.
+     */
+    public static void relaunchApp() {
+        relaunchApp(false);
+    }
+
+    /**
+     * Relaunch the application.
+     *
+     * @param isKillProcess True to kill the process, false otherwise.
+     */
+    public static void relaunchApp(final boolean isKillProcess) {
+        Intent intent = UtilsBridge.getLaunchAppIntent(Utils.getApp().getPackageName());
+        if (intent == null) {
+            Log.e("AppUtils", "Didn't exist launcher activity.");
+            return;
+        }
+        intent.addFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
+        );
+        Utils.getApp().startActivity(intent);
+        if (!isKillProcess) return;
+        android.os.Process.killProcess(android.os.Process.myPid());
+        System.exit(0);
+    }
+
+    /**
+     * Launch the application's details settings.
+     */
+    public static void launchAppDetailsSettings() {
+        launchAppDetailsSettings(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Launch the application's details settings.
+     *
+     * @param pkgName The name of the package.
+     */
+    public static void launchAppDetailsSettings(final String pkgName) {
+        if (UtilsBridge.isSpace(pkgName)) return;
+        Intent intent = UtilsBridge.getLaunchAppDetailsSettingsIntent(pkgName, true);
+        if (!UtilsBridge.isIntentAvailable(intent)) return;
+        Utils.getApp().startActivity(intent);
+    }
+
+    /**
+     * Launch the application's details settings.
+     *
+     * @param activity    The activity.
+     * @param requestCode The requestCode.
+     */
+    public static void launchAppDetailsSettings(final Activity activity, final int requestCode) {
+        launchAppDetailsSettings(activity, requestCode, Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Launch the application's details settings.
+     *
+     * @param activity    The activity.
+     * @param requestCode The requestCode.
+     * @param pkgName     The name of the package.
+     */
+    public static void launchAppDetailsSettings(final Activity activity, final int requestCode, final String pkgName) {
+        if (activity == null || UtilsBridge.isSpace(pkgName)) return;
+        Intent intent = UtilsBridge.getLaunchAppDetailsSettingsIntent(pkgName, false);
+        if (!UtilsBridge.isIntentAvailable(intent)) return;
+        activity.startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Exit the application.
+     */
+    public static void exitApp() {
+        UtilsBridge.finishAllActivities();
+        System.exit(0);
+    }
+
+    /**
+     * Return the application's icon.
+     *
+     * @return the application's icon
+     */
+    @Nullable
+    public static Drawable getAppIcon() {
+        return getAppIcon(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's icon.
+     *
+     * @param packageName The name of the package.
+     * @return the application's icon
+     */
+    @Nullable
+    public static Drawable getAppIcon(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return null;
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return pi == null ? null : pi.applicationInfo.loadIcon(pm);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Return the application's icon resource identifier.
+     *
+     * @return the application's icon resource identifier
+     */
+    public static int getAppIconId() {
+        return getAppIconId(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's icon resource identifier.
+     *
+     * @param packageName The name of the package.
+     * @return the application's icon resource identifier
+     */
+    public static int getAppIconId(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return 0;
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return pi == null ? 0 : pi.applicationInfo.icon;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+
+    /**
+     * Return true if this is the first ever time that the application is installed on the device.
+     *
+     * @return true if this is the first ever time that the application is installed on the device.
+     */
+    public static boolean isFirstTimeInstall() {
+        try {
+            long firstInstallTime = Utils.getApp().getPackageManager().getPackageInfo(getAppPackageName(), 0).firstInstallTime;
+            long lastUpdateTime = Utils.getApp().getPackageManager().getPackageInfo(getAppPackageName(), 0).lastUpdateTime;
+            return firstInstallTime == lastUpdateTime;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * Return true if app was previously installed and this one is an update/upgrade to that one, returns false if this is a fresh installation and not an update/upgrade.
+     *
+     * @return true if app was previously installed and this one is an update/upgrade to that one, returns false if this is a fresh installation and not an update/upgrade.
+     */
+    public static boolean isAppUpgraded() {
+        try {
+            long firstInstallTime = Utils.getApp().getPackageManager().getPackageInfo(getAppPackageName(), 0).firstInstallTime;
+            long lastUpdateTime = Utils.getApp().getPackageManager().getPackageInfo(getAppPackageName(), 0).lastUpdateTime;
+            return firstInstallTime != lastUpdateTime;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+
+    /**
+     * Return the application's package name.
+     *
+     * @return the application's package name
+     */
+    @NonNull
+    public static String getAppPackageName() {
+        return Utils.getApp().getPackageName();
+    }
+
+    /**
+     * Return the application's name.
+     *
+     * @return the application's name
+     */
+    @NonNull
+    public static String getAppName() {
+        return getAppName(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's name.
+     *
+     * @param packageName The name of the package.
+     * @return the application's name
+     */
+    @NonNull
+    public static String getAppName(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return "";
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return pi == null ? "" : pi.applicationInfo.loadLabel(pm).toString();
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Return the application's path.
+     *
+     * @return the application's path
+     */
+    @NonNull
+    public static String getAppPath() {
+        return getAppPath(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's path.
+     *
+     * @param packageName The name of the package.
+     * @return the application's path
+     */
+    @NonNull
+    public static String getAppPath(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return "";
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return pi == null ? "" : pi.applicationInfo.sourceDir;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Return the application's version name.
+     *
+     * @return the application's version name
+     */
+    @NonNull
+    public static String getAppVersionName() {
+        return getAppVersionName(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's version name.
+     *
+     * @param packageName The name of the package.
+     * @return the application's version name
+     */
+    @NonNull
+    public static String getAppVersionName(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return "";
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return pi == null ? "" : pi.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Return the application's version code.
+     *
+     * @return the application's version code
+     */
+    public static int getAppVersionCode() {
+        return getAppVersionCode(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's version code.
+     *
+     * @param packageName The name of the package.
+     * @return the application's version code
+     */
+    public static int getAppVersionCode(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return -1;
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return pi == null ? -1 : pi.versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return -1;
+        }
+    }
+
+    /**
+     * Return the application's signature.
+     *
+     * @return the application's signature
+     */
+    @Nullable
+    public static Signature[] getAppSignatures() {
+        return getAppSignatures(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's signature.
+     *
+     * @param packageName The name of the package.
+     * @return the application's signature
+     */
+    @Nullable
+    public static Signature[] getAppSignatures(final String packageName) {
+        if (UtilsBridge.isSpace(packageName)) return null;
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES);
+                if (pi == null) return null;
+
+                SigningInfo signingInfo = pi.signingInfo;
+                if (signingInfo.hasMultipleSigners()) {
+                    return signingInfo.getApkContentsSigners();
+                } else {
+                    return signingInfo.getSigningCertificateHistory();
+                }
+            } else {
+                PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+                if (pi == null) return null;
+
+                return pi.signatures;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Return the application's signature.
+     *
+     * @param file The file.
+     * @return the application's signature
+     */
+    @Nullable
+    public static Signature[] getAppSignatures(final File file) {
+        if (file == null) return null;
+        PackageManager pm = Utils.getApp().getPackageManager();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            PackageInfo pi = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNING_CERTIFICATES);
+            if (pi == null) return null;
+
+            SigningInfo signingInfo = pi.signingInfo;
+            if (signingInfo.hasMultipleSigners()) {
+                return signingInfo.getApkContentsSigners();
+            } else {
+                return signingInfo.getSigningCertificateHistory();
+            }
+        } else {
+            PackageInfo pi = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNATURES);
+            if (pi == null) return null;
+
+            return pi.signatures;
+        }
+    }
+
+    /**
+     * Return the application's signature for SHA1 value.
+     *
+     * @return the application's signature for SHA1 value
+     */
+    @NonNull
+    public static List<String> getAppSignaturesSHA1() {
+        return getAppSignaturesSHA1(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's signature for SHA1 value.
+     *
+     * @param packageName The name of the package.
+     * @return the application's signature for SHA1 value
+     */
+    @NonNull
+    public static List<String> getAppSignaturesSHA1(final String packageName) {
+        return getAppSignaturesHash(packageName, "SHA1");
+    }
+
+    /**
+     * Return the application's signature for SHA256 value.
+     *
+     * @return the application's signature for SHA256 value
+     */
+    @NonNull
+    public static List<String> getAppSignaturesSHA256() {
+        return getAppSignaturesSHA256(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's signature for SHA256 value.
+     *
+     * @param packageName The name of the package.
+     * @return the application's signature for SHA256 value
+     */
+    @NonNull
+    public static List<String> getAppSignaturesSHA256(final String packageName) {
+        return getAppSignaturesHash(packageName, "SHA256");
+    }
+
+    /**
+     * Return the application's signature for MD5 value.
+     *
+     * @return the application's signature for MD5 value
+     */
+    @NonNull
+    public static List<String> getAppSignaturesMD5() {
+        return getAppSignaturesMD5(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's signature for MD5 value.
+     *
+     * @param packageName The name of the package.
+     * @return the application's signature for MD5 value
+     */
+    @NonNull
+    public static List<String> getAppSignaturesMD5(final String packageName) {
+        return getAppSignaturesHash(packageName, "MD5");
+    }
+
+    /**
+     * Return the application's user-ID.
+     *
+     * @return the application's signature for MD5 value
+     */
+    public static int getAppUid() {
+        return getAppUid(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's user-ID.
+     *
+     * @param pkgName The name of the package.
+     * @return the application's signature for MD5 value
+     */
+    public static int getAppUid(String pkgName) {
+        try {
+            return Utils.getApp().getPackageManager().getApplicationInfo(pkgName, 0).uid;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return -1;
+        }
+    }
+
+    private static List<String> getAppSignaturesHash(final String packageName, final String algorithm) {
+        ArrayList<String> result = new ArrayList<>();
+        if (UtilsBridge.isSpace(packageName)) return result;
+        Signature[] signatures = getAppSignatures(packageName);
+        if (signatures == null || signatures.length <= 0) return result;
+        for (Signature signature : signatures) {
+            String hash = UtilsBridge.bytes2HexString(UtilsBridge.hashTemplate(signature.toByteArray(), algorithm))
+                    .replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0");
+            result.add(hash);
+        }
+        return result;
+    }
+
+    /**
+     * Return the application's information.
+     * <ul>
+     * <li>name of package</li>
+     * <li>icon</li>
+     * <li>name</li>
+     * <li>path of package</li>
+     * <li>version name</li>
+     * <li>version code</li>
+     * <li>is system</li>
+     * </ul>
+     *
+     * @return the application's information
+     */
+    @Nullable
+    public static AppInfo getAppInfo() {
+        return getAppInfo(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the application's information.
+     * <ul>
+     * <li>name of package</li>
+     * <li>icon</li>
+     * <li>name</li>
+     * <li>path of package</li>
+     * <li>version name</li>
+     * <li>version code</li>
+     * <li>is system</li>
+     * </ul>
+     *
+     * @param packageName The name of the package.
+     * @return the application's information
+     */
+    @Nullable
+    public static AppInfo getAppInfo(final String packageName) {
+        try {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            if (pm == null) return null;
+            return getBean(pm, pm.getPackageInfo(packageName, 0));
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Return the applications' information.
+     *
+     * @return the applications' information
+     */
+    @NonNull
+    public static List<AppInfo> getAppsInfo() {
+        List<AppInfo> list = new ArrayList<>();
+        PackageManager pm = Utils.getApp().getPackageManager();
+        if (pm == null) return list;
+        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
+        for (PackageInfo pi : installedPackages) {
+            AppInfo ai = getBean(pm, pi);
+            if (ai == null) continue;
+            list.add(ai);
+        }
+        return list;
+    }
+
+    /**
+     * Return the application's package information.
+     *
+     * @return the application's package information
+     */
+    @Nullable
+    public static AppUtils.AppInfo getApkInfo(final File apkFile) {
+        if (apkFile == null || !apkFile.isFile() || !apkFile.exists()) return null;
+        return getApkInfo(apkFile.getAbsolutePath());
+    }
+
+    /**
+     * Return the application's package information.
+     *
+     * @return the application's package information
+     */
+    @Nullable
+    public static AppUtils.AppInfo getApkInfo(final String apkFilePath) {
+        if (UtilsBridge.isSpace(apkFilePath)) return null;
+        PackageManager pm = Utils.getApp().getPackageManager();
+        if (pm == null) return null;
+        PackageInfo pi = pm.getPackageArchiveInfo(apkFilePath, 0);
+        if (pi == null) return null;
+        ApplicationInfo appInfo = pi.applicationInfo;
+        appInfo.sourceDir = apkFilePath;
+        appInfo.publicSourceDir = apkFilePath;
+        return getBean(pm, pi);
+    }
+
+
+    /**
+     * Return whether the application was first installed.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFirstTimeInstalled() {
+        try {
+            PackageInfo pi = Utils.getApp().getPackageManager().getPackageInfo(Utils.getApp().getPackageName(), 0);
+            return pi.firstInstallTime == pi.lastUpdateTime;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return true;
+        }
+    }
+
+    private static AppInfo getBean(final PackageManager pm, final PackageInfo pi) {
+        if (pi == null) return null;
+        String versionName = pi.versionName;
+        int versionCode = pi.versionCode;
+        String packageName = pi.packageName;
+        ApplicationInfo ai = pi.applicationInfo;
+        if (ai == null) {
+            return new AppInfo(packageName, "", null, "", versionName, versionCode, false);
+        }
+        String name = ai.loadLabel(pm).toString();
+        Drawable icon = ai.loadIcon(pm);
+        String packagePath = ai.sourceDir;
+        boolean isSystem = (ApplicationInfo.FLAG_SYSTEM & ai.flags) != 0;
+        return new AppInfo(packageName, name, icon, packagePath, versionName, versionCode, isSystem);
+    }
+
+    /**
+     * The application's information.
+     */
+    public static class AppInfo {
+
+        private String   packageName;
+        private String   name;
+        private Drawable icon;
+        private String   packagePath;
+        private String   versionName;
+        private int      versionCode;
+        private boolean  isSystem;
+
+        public Drawable getIcon() {
+            return icon;
+        }
+
+        public void setIcon(final Drawable icon) {
+            this.icon = icon;
+        }
+
+        public boolean isSystem() {
+            return isSystem;
+        }
+
+        public void setSystem(final boolean isSystem) {
+            this.isSystem = isSystem;
+        }
+
+        public String getPackageName() {
+            return packageName;
+        }
+
+        public void setPackageName(final String packageName) {
+            this.packageName = packageName;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        public String getPackagePath() {
+            return packagePath;
+        }
+
+        public void setPackagePath(final String packagePath) {
+            this.packagePath = packagePath;
+        }
+
+        public int getVersionCode() {
+            return versionCode;
+        }
+
+        public void setVersionCode(final int versionCode) {
+            this.versionCode = versionCode;
+        }
+
+        public String getVersionName() {
+            return versionName;
+        }
+
+        public void setVersionName(final String versionName) {
+            this.versionName = versionName;
+        }
+
+        public AppInfo(String packageName, String name, Drawable icon, String packagePath,
+                       String versionName, int versionCode, boolean isSystem) {
+            this.setName(name);
+            this.setIcon(icon);
+            this.setPackageName(packageName);
+            this.setPackagePath(packagePath);
+            this.setVersionName(versionName);
+            this.setVersionCode(versionCode);
+            this.setSystem(isSystem);
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "{" +
+                    "\n    pkg name: " + getPackageName() +
+                    "\n    app icon: " + getIcon() +
+                    "\n    app name: " + getName() +
+                    "\n    app path: " + getPackagePath() +
+                    "\n    app v name: " + getVersionName() +
+                    "\n    app v code: " + getVersionCode() +
+                    "\n    is system: " + isSystem() +
+                    "\n}";
+        }
+    }
+}

+ 2143 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ArrayUtils.java

@@ -0,0 +1,2143 @@
+package com.cooleshow.base.utils;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * <pre>
+ *     author: blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/08/10
+ *     desc  : utils about array
+ * </pre>
+ */
+public class ArrayUtils {
+
+    public static final int INDEX_NOT_FOUND = -1;
+
+    private ArrayUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Returns a new array only of those given elements.
+     *
+     * @param array The array.
+     * @return a new array only of those given elements.
+     */
+    @NonNull
+    public static <T> T[] newArray(T... array) {
+        return array;
+    }
+
+    @NonNull
+    public static long[] newLongArray(long... array) {
+        return array;
+    }
+
+    @NonNull
+    public static int[] newIntArray(int... array) {
+        return array;
+    }
+
+    @NonNull
+    public static short[] newShortArray(short... array) {
+        return array;
+    }
+
+    @NonNull
+    public static char[] newCharArray(char... array) {
+        return array;
+    }
+
+    @NonNull
+    public static byte[] newByteArray(byte... array) {
+        return array;
+    }
+
+    @NonNull
+    public static double[] newDoubleArray(double... array) {
+        return array;
+    }
+
+    @NonNull
+    public static float[] newFloatArray(float... array) {
+        return array;
+    }
+
+    @NonNull
+    public static boolean[] newBooleanArray(boolean... array) {
+        return array;
+    }
+
+    /**
+     * Return the array is empty.
+     *
+     * @param array The array.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isEmpty(@Nullable Object array) {
+        return getLength(array) == 0;
+    }
+
+    /**
+     * Return the size of array.
+     *
+     * @param array The array.
+     * @return the size of array
+     */
+    public static int getLength(@Nullable Object array) {
+        if (array == null) return 0;
+        return Array.getLength(array);
+    }
+
+    public static boolean isSameLength(@Nullable Object array1, @Nullable Object array2) {
+        return getLength(array1) == getLength(array2);
+    }
+
+    /**
+     * Get the value of the specified index of the array.
+     *
+     * @param array The array.
+     * @param index The index into the array.
+     * @return the value of the specified index of the array
+     */
+    @Nullable
+    public static Object get(@Nullable Object array, int index) {
+        return get(array, index, null);
+    }
+
+    /**
+     * Get the value of the specified index of the array.
+     *
+     * @param array        The array.
+     * @param index        The index into the array.
+     * @param defaultValue The default value.
+     * @return the value of the specified index of the array
+     */
+    @Nullable
+    public static Object get(@Nullable Object array, int index, @Nullable Object defaultValue) {
+        if (array == null) return defaultValue;
+        try {
+            return Array.get(array, index);
+        } catch (Exception ignore) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Set the value of the specified index of the array.
+     *
+     * @param array The array.
+     * @param index The index into the array.
+     * @param value The new value of the indexed component.
+     */
+    public static void set(@Nullable Object array, int index, @Nullable Object value) {
+        if (array == null) return;
+        Array.set(array, index, value);
+    }
+
+    /**
+     * Return whether the two arrays are equals.
+     *
+     * @param a  One array.
+     * @param a2 The other array.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean equals(@Nullable Object[] a, @Nullable Object[] a2) {
+        return Arrays.deepEquals(a, a2);
+    }
+
+    public static boolean equals(boolean[] a, boolean[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    public static boolean equals(byte[] a, byte[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    public static boolean equals(char[] a, char[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    public static boolean equals(double[] a, double[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    public static boolean equals(float[] a, float[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    public static boolean equals(int[] a, int[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    public static boolean equals(short[] a, short[] a2) {
+        return Arrays.equals(a, a2);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // reverse
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>Reverses the order of the given array.</p>
+     *
+     * <p>There is no special handling for multi-dimensional arrays.</p>
+     *
+     * <p>This method does nothing for a <code>null</code> input array.</p>
+     *
+     * @param array the array to reverse, may be <code>null</code>
+     */
+    public static <T> void reverse(T[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        T tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(long[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        long tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(int[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        int tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(short[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        short tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(char[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        char tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(byte[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        byte tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(double[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        double tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(float[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        float tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    public static void reverse(boolean[] array) {
+        if (array == null) {
+            return;
+        }
+        int i = 0;
+        int j = array.length - 1;
+        boolean tmp;
+        while (j > i) {
+            tmp = array[j];
+            array[j] = array[i];
+            array[i] = tmp;
+            j--;
+            i++;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // copy
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>Copies the specified array and handling
+     * <code>null</code>.</p>
+     *
+     * <p>The objects in the array are not cloned, thus there is no special
+     * handling for multi-dimensional arrays.</p>
+     *
+     * <p>This method returns <code>null</code> for a <code>null</code> input array.</p>
+     *
+     * @param array the array to shallow clone, may be <code>null</code>
+     * @return the cloned array, <code>null</code> if <code>null</code> input
+     */
+    @Nullable
+    public static <T> T[] copy(@Nullable T[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static long[] copy(@Nullable long[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static int[] copy(@Nullable int[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static short[] copy(@Nullable short[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static char[] copy(@Nullable char[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static byte[] copy(@Nullable byte[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static double[] copy(@Nullable double[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static float[] copy(@Nullable float[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    public static boolean[] copy(@Nullable boolean[] array) {
+        if (array == null) return null;
+        return subArray(array, 0, array.length);
+    }
+
+    @Nullable
+    private static Object realCopy(@Nullable Object array) {
+        if (array == null) return null;
+        return realSubArray(array, 0, getLength(array));
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // subArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static <T> T[] subArray(@Nullable T[] array, int startIndexInclusive, int endIndexExclusive) {
+        //noinspection unchecked
+        return (T[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static long[] subArray(@Nullable long[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (long[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static int[] subArray(@Nullable int[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (int[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static short[] subArray(@Nullable short[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (short[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static char[] subArray(@Nullable char[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (char[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static byte[] subArray(@Nullable byte[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (byte[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static double[] subArray(@Nullable double[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (double[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static float[] subArray(@Nullable float[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (float[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    public static boolean[] subArray(@Nullable boolean[] array, int startIndexInclusive, int endIndexExclusive) {
+        return (boolean[]) realSubArray(array, startIndexInclusive, endIndexExclusive);
+    }
+
+    @Nullable
+    private static Object realSubArray(@Nullable Object array, int startIndexInclusive, int endIndexExclusive) {
+        if (array == null) {
+            return null;
+        }
+        if (startIndexInclusive < 0) {
+            startIndexInclusive = 0;
+        }
+        int length = getLength(array);
+        if (endIndexExclusive > length) {
+            endIndexExclusive = length;
+        }
+        int newSize = endIndexExclusive - startIndexInclusive;
+        Class type = array.getClass().getComponentType();
+        if (newSize <= 0) {
+            return Array.newInstance(type, 0);
+        }
+        Object subArray = Array.newInstance(type, newSize);
+        System.arraycopy(array, startIndexInclusive, subArray, 0, newSize);
+        return subArray;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // add
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>Copies the given array and adds the given element at the end of the new array.</p>
+     *
+     * <p>The new array contains the same elements of the input
+     * array plus the given element in the last position. The component type of
+     * the new array is the same as that of the input array.</p>
+     *
+     * <p>If the input array is <code>null</code>, a new one element array is returned
+     * whose component type is the same as the element.</p>
+     *
+     * <pre>
+     * ArrayUtils.realAdd(null, null)      = [null]
+     * ArrayUtils.realAdd(null, "a")       = ["a"]
+     * ArrayUtils.realAdd(["a"], null)     = ["a", null]
+     * ArrayUtils.realAdd(["a"], "b")      = ["a", "b"]
+     * ArrayUtils.realAdd(["a", "b"], "c") = ["a", "b", "c"]
+     * </pre>
+     *
+     * @param array   the array to "realAdd" the element to, may be <code>null</code>
+     * @param element the object to realAdd
+     * @return A new array containing the existing elements plus the new element
+     */
+    @NonNull
+    public static <T> T[] add(@Nullable T[] array, @Nullable T element) {
+        Class type = array != null ? array.getClass() : (element != null ? element.getClass() : Object.class);
+        return (T[]) realAddOne(array, element, type);
+    }
+
+    @NonNull
+    public static boolean[] add(@Nullable boolean[] array, boolean element) {
+        return (boolean[]) realAddOne(array, element, Boolean.TYPE);
+    }
+
+    @NonNull
+    public static byte[] add(@Nullable byte[] array, byte element) {
+        return (byte[]) realAddOne(array, element, Byte.TYPE);
+    }
+
+    @NonNull
+    public static char[] add(@Nullable char[] array, char element) {
+        return (char[]) realAddOne(array, element, Character.TYPE);
+    }
+
+    @NonNull
+    public static double[] add(@Nullable double[] array, double element) {
+        return (double[]) realAddOne(array, element, Double.TYPE);
+    }
+
+    @NonNull
+    public static float[] add(@Nullable float[] array, float element) {
+        return (float[]) realAddOne(array, element, Float.TYPE);
+    }
+
+    @NonNull
+    public static int[] add(@Nullable int[] array, int element) {
+        return (int[]) realAddOne(array, element, Integer.TYPE);
+    }
+
+    @NonNull
+    public static long[] add(@Nullable long[] array, long element) {
+        return (long[]) realAddOne(array, element, Long.TYPE);
+    }
+
+    @NonNull
+    public static short[] add(@Nullable short[] array, short element) {
+        return (short[]) realAddOne(array, element, Short.TYPE);
+    }
+
+    @NonNull
+    private static Object realAddOne(@Nullable Object array, @Nullable Object element, Class newArrayComponentType) {
+        Object newArray;
+        int arrayLength = 0;
+        if (array != null) {
+            arrayLength = getLength(array);
+            newArray = Array.newInstance(array.getClass().getComponentType(), arrayLength + 1);
+            System.arraycopy(array, 0, newArray, 0, arrayLength);
+        } else {
+            newArray = Array.newInstance(newArrayComponentType, 1);
+        }
+        Array.set(newArray, arrayLength, element);
+        return newArray;
+    }
+
+    /**
+     * <p>Adds all the elements of the given arrays into a new array.</p>
+     * <p>The new array contains all of the element of <code>array1</code> followed
+     * by all of the elements <code>array2</code>. When an array is returned, it is always
+     * a new array.</p>
+     *
+     * <pre>
+     * ArrayUtils.add(null, null)     = null
+     * ArrayUtils.add(array1, null)   = copy of array1
+     * ArrayUtils.add(null, array2)   = copy of array2
+     * ArrayUtils.add([], [])         = []
+     * ArrayUtils.add([null], [null]) = [null, null]
+     * ArrayUtils.add(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
+     * </pre>
+     *
+     * @param array1 the first array whose elements are added to the new array, may be <code>null</code>
+     * @param array2 the second array whose elements are added to the new array, may be <code>null</code>
+     * @return The new array, <code>null</code> if <code>null</code> array inputs.
+     * The type of the new array is the type of the first array.
+     */
+    @Nullable
+    public static <T> T[] add(@Nullable T[] array1, @Nullable T[] array2) {
+        return (T[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static boolean[] add(@Nullable boolean[] array1, @Nullable boolean[] array2) {
+        return (boolean[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static char[] add(@Nullable char[] array1, @Nullable char[] array2) {
+        return (char[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static byte[] add(@Nullable byte[] array1, @Nullable byte[] array2) {
+        return (byte[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static short[] add(@Nullable short[] array1, @Nullable short[] array2) {
+        return (short[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static int[] add(@Nullable int[] array1, @Nullable int[] array2) {
+        return (int[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static long[] add(@Nullable long[] array1, @Nullable long[] array2) {
+        return (long[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static float[] add(@Nullable float[] array1, @Nullable float[] array2) {
+        return (float[]) realAddArr(array1, array2);
+    }
+
+    @Nullable
+    public static double[] add(@Nullable double[] array1, @Nullable double[] array2) {
+        return (double[]) realAddArr(array1, array2);
+    }
+
+    private static Object realAddArr(@Nullable Object array1, @Nullable Object array2) {
+        if (array1 == null && array2 == null) return null;
+        if (array1 == null) {
+            return realCopy(array2);
+        }
+        if (array2 == null) {
+            return realCopy(array1);
+        }
+        int len1 = getLength(array1);
+        int len2 = getLength(array2);
+        Object joinedArray = Array.newInstance(array1.getClass().getComponentType(), len1 + len2);
+        System.arraycopy(array1, 0, joinedArray, 0, len1);
+        System.arraycopy(array2, 0, joinedArray, len1, len2);
+        return joinedArray;
+    }
+
+    /**
+     * <p>Inserts the specified element at the specified position in the array.
+     * Shifts the element currently at that position (if any) and any subsequent
+     * elements to the right (adds one to their indices).</p>
+     *
+     * <p>This method returns a new array with the same elements of the input
+     * array plus the given element on the specified position. The component
+     * type of the returned array is always the same as that of the input
+     * array.</p>
+     *
+     * <p>If the input array is <code>null</code>, a new one element array is returned
+     * whose component type is the same as the element.</p>
+     *
+     * <pre>
+     * ArrayUtils.add(null, 0, null)        = null
+     * ArrayUtils.add(null, 0, ["a"])       = ["a"]
+     * ArrayUtils.add(["a"], 1, null)       = ["a"]
+     * ArrayUtils.add(["a"], 1, ["b"])      = ["a", "b"]
+     * ArrayUtils.add(["a", "b"], 2, ["c"]) = ["a", "b", "c"]
+     * </pre>
+     *
+     * @param array1 the array to realAdd the element to, may be <code>null</code>
+     * @param index  the position of the new object
+     * @param array2 the array to realAdd
+     * @return A new array containing the existing elements and the new element
+     * @throws IndexOutOfBoundsException if the index is out of range
+     *                                   (index < 0 || index > array.length).
+     */
+    @Nullable
+    public static <T> T[] add(@Nullable T[] array1, int index, @Nullable T[] array2) {
+        Class clss;
+        if (array1 != null) {
+            clss = array1.getClass().getComponentType();
+        } else if (array2 != null) {
+            clss = array2.getClass().getComponentType();
+        } else {
+            return null;
+        }
+        return (T[]) realAddArr(array1, index, array2, clss);
+    }
+
+    @Nullable
+    public static boolean[] add(@Nullable boolean[] array1, int index, @Nullable boolean[] array2) {
+        Object result = realAddArr(array1, index, array2, Boolean.TYPE);
+        if (result == null) return null;
+        return (boolean[]) result;
+    }
+
+    public static char[] add(@Nullable char[] array1, int index, @Nullable char[] array2) {
+        Object result = realAddArr(array1, index, array2, Character.TYPE);
+        if (result == null) return null;
+        return (char[]) result;
+    }
+
+    @Nullable
+    public static byte[] add(@Nullable byte[] array1, int index, @Nullable byte[] array2) {
+        Object result = realAddArr(array1, index, array2, Byte.TYPE);
+        if (result == null) return null;
+        return (byte[]) result;
+    }
+
+    @Nullable
+    public static short[] add(@Nullable short[] array1, int index, @Nullable short[] array2) {
+        Object result = realAddArr(array1, index, array2, Short.TYPE);
+        if (result == null) return null;
+        return (short[]) result;
+    }
+
+    @Nullable
+    public static int[] add(@Nullable int[] array1, int index, @Nullable int[] array2) {
+        Object result = realAddArr(array1, index, array2, Integer.TYPE);
+        if (result == null) return null;
+        return (int[]) result;
+    }
+
+    @Nullable
+    public static long[] add(@Nullable long[] array1, int index, @Nullable long[] array2) {
+        Object result = realAddArr(array1, index, array2, Long.TYPE);
+        if (result == null) return null;
+        return (long[]) result;
+    }
+
+    @Nullable
+    public static float[] add(@Nullable float[] array1, int index, @Nullable float[] array2) {
+        Object result = realAddArr(array1, index, array2, Float.TYPE);
+        if (result == null) return null;
+        return (float[]) result;
+    }
+
+    @Nullable
+    public static double[] add(@Nullable double[] array1, int index, @Nullable double[] array2) {
+        Object result = realAddArr(array1, index, array2, Double.TYPE);
+        if (result == null) return null;
+        return (double[]) result;
+    }
+
+    @Nullable
+    private static Object realAddArr(@Nullable Object array1, int index, @Nullable Object array2, Class clss) {
+        if (array1 == null && array2 == null) return null;
+        int len1 = getLength(array1);
+        int len2 = getLength(array2);
+        if (len1 == 0) {
+            if (index != 0) {
+                throw new IndexOutOfBoundsException("Index: " + index + ", array1 Length: 0");
+            }
+            return realCopy(array2);
+        }
+        if (len2 == 0) {
+            return realCopy(array1);
+        }
+        if (index > len1 || index < 0) {
+            throw new IndexOutOfBoundsException("Index: " + index + ", array1 Length: " + len1);
+        }
+        Object joinedArray = Array.newInstance(array1.getClass().getComponentType(), len1 + len2);
+        if (index == len1) {
+            System.arraycopy(array1, 0, joinedArray, 0, len1);
+            System.arraycopy(array2, 0, joinedArray, len1, len2);
+        } else if (index == 0) {
+            System.arraycopy(array2, 0, joinedArray, 0, len2);
+            System.arraycopy(array1, 0, joinedArray, len2, len1);
+        } else {
+            System.arraycopy(array1, 0, joinedArray, 0, index);
+            System.arraycopy(array2, 0, joinedArray, index, len2);
+            System.arraycopy(array1, index, joinedArray, index + len2, len1 - index);
+        }
+        return joinedArray;
+    }
+
+    /**
+     * <p>Inserts the specified element at the specified position in the array.
+     * Shifts the element currently at that position (if any) and any subsequent
+     * elements to the right (adds one to their indices).</p>
+     *
+     * <p>This method returns a new array with the same elements of the input
+     * array plus the given element on the specified position. The component
+     * type of the returned array is always the same as that of the input
+     * array.</p>
+     *
+     * <p>If the input array is <code>null</code>, a new one element array is returned
+     * whose component type is the same as the element.</p>
+     *
+     * <pre>
+     * ArrayUtils.add(null, 0, null)      = [null]
+     * ArrayUtils.add(null, 0, "a")       = ["a"]
+     * ArrayUtils.add(["a"], 1, null)     = ["a", null]
+     * ArrayUtils.add(["a"], 1, "b")      = ["a", "b"]
+     * ArrayUtils.add(["a", "b"], 3, "c") = ["a", "b", "c"]
+     * </pre>
+     *
+     * @param array   the array to realAdd the element to, may be <code>null</code>
+     * @param index   the position of the new object
+     * @param element the object to realAdd
+     * @return A new array containing the existing elements and the new element
+     * @throws IndexOutOfBoundsException if the index is out of range
+     *                                   (index < 0 || index > array.length).
+     */
+    @NonNull
+    public static <T> T[] add(@Nullable T[] array, int index, @Nullable T element) {
+        Class clss;
+        if (array != null) {
+            clss = array.getClass().getComponentType();
+        } else if (element != null) {
+            clss = element.getClass();
+        } else {
+            return (T[]) new Object[]{null};
+        }
+        return (T[]) realAdd(array, index, element, clss);
+    }
+
+    @NonNull
+    public static boolean[] add(@Nullable boolean[] array, int index, boolean element) {
+        return (boolean[]) realAdd(array, index, element, Boolean.TYPE);
+    }
+
+    @NonNull
+    public static char[] add(@Nullable char[] array, int index, char element) {
+        return (char[]) realAdd(array, index, element, Character.TYPE);
+    }
+
+    @NonNull
+    public static byte[] add(@Nullable byte[] array, int index, byte element) {
+        return (byte[]) realAdd(array, index, element, Byte.TYPE);
+    }
+
+    @NonNull
+    public static short[] add(@Nullable short[] array, int index, short element) {
+        return (short[]) realAdd(array, index, element, Short.TYPE);
+    }
+
+    @NonNull
+    public static int[] add(@Nullable int[] array, int index, int element) {
+        return (int[]) realAdd(array, index, element, Integer.TYPE);
+    }
+
+    @NonNull
+    public static long[] add(@Nullable long[] array, int index, long element) {
+        return (long[]) realAdd(array, index, element, Long.TYPE);
+    }
+
+    @NonNull
+    public static float[] add(@Nullable float[] array, int index, float element) {
+        return (float[]) realAdd(array, index, element, Float.TYPE);
+    }
+
+    @NonNull
+    public static double[] add(@Nullable double[] array, int index, double element) {
+        return (double[]) realAdd(array, index, element, Double.TYPE);
+    }
+
+    @NonNull
+    private static Object realAdd(@Nullable Object array, int index, @Nullable Object element, Class clss) {
+        if (array == null) {
+            if (index != 0) {
+                throw new IndexOutOfBoundsException("Index: " + index + ", Length: 0");
+            }
+            Object joinedArray = Array.newInstance(clss, 1);
+            Array.set(joinedArray, 0, element);
+            return joinedArray;
+        }
+        int length = Array.getLength(array);
+        if (index > length || index < 0) {
+            throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+        }
+        Object result = Array.newInstance(clss, length + 1);
+        System.arraycopy(array, 0, result, 0, index);
+        Array.set(result, index, element);
+        if (index < length) {
+            System.arraycopy(array, index, result, index + 1, length - index);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // remove
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>Removes the element at the specified position from the specified array.
+     * All subsequent elements are shifted to the left (substracts one from
+     * their indices).</p>
+     *
+     * <p>This method returns a new array with the same elements of the input
+     * array except the element on the specified position. The component
+     * type of the returned array is always the same as that of the input
+     * array.</p>
+     *
+     * <p>If the input array is <code>null</code>, an IndexOutOfBoundsException
+     * will be thrown, because in that case no valid index can be specified.</p>
+     *
+     * <pre>
+     * ArrayUtils.remove(["a"], 0)           = []
+     * ArrayUtils.remove(["a", "b"], 0)      = ["b"]
+     * ArrayUtils.remove(["a", "b"], 1)      = ["a"]
+     * ArrayUtils.remove(["a", "b", "c"], 1) = ["a", "c"]
+     * </pre>
+     *
+     * @param array the array to remove the element from, may be <code>null</code>
+     * @param index the position of the element to be removed
+     * @return A new array containing the existing elements except the element
+     * at the specified position.
+     * @throws IndexOutOfBoundsException if the index is out of range
+     *                                   (index < 0 || index >= array.length)
+     */
+    @Nullable
+    public static Object[] remove(@Nullable Object[] array, int index) {
+        if (array == null) return null;
+        return (Object[]) remove((Object) array, index);
+    }
+
+    /**
+     * <p>Removes the first occurrence of the specified element from the
+     * specified array. All subsequent elements are shifted to the left
+     * (substracts one from their indices). If the array doesn't contains
+     * such an element, no elements are removed from the array.</p>
+     *
+     * <p>This method returns a new array with the same elements of the input
+     * array except the first occurrence of the specified element. The component
+     * type of the returned array is always the same as that of the input
+     * array.</p>
+     *
+     * <pre>
+     * ArrayUtils.removeElement(null, "a")            = null
+     * ArrayUtils.removeElement([], "a")              = []
+     * ArrayUtils.removeElement(["a"], "b")           = ["a"]
+     * ArrayUtils.removeElement(["a", "b"], "a")      = ["b"]
+     * ArrayUtils.removeElement(["a", "b", "a"], "a") = ["b", "a"]
+     * </pre>
+     *
+     * @param array   the array to remove the element from, may be <code>null</code>
+     * @param element the element to be removed
+     * @return A new array containing the existing elements except the first
+     * occurrence of the specified element.
+     */
+    @Nullable
+    public static Object[] removeElement(@Nullable Object[] array, @Nullable Object element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static boolean[] remove(@Nullable boolean[] array, int index) {
+        if (array == null) return null;
+        return (boolean[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static boolean[] removeElement(@Nullable boolean[] array, boolean element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static byte[] remove(@Nullable byte[] array, int index) {
+        if (array == null) return null;
+        return (byte[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static byte[] removeElement(@Nullable byte[] array, byte element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static char[] remove(@Nullable char[] array, int index) {
+        if (array == null) return null;
+        return (char[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static char[] removeElement(@Nullable char[] array, char element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static double[] remove(@Nullable double[] array, int index) {
+        if (array == null) return null;
+        return (double[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static double[] removeElement(@Nullable double[] array, double element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        //noinspection ConstantConditions
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static float[] remove(@Nullable float[] array, int index) {
+        if (array == null) return null;
+        return (float[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static float[] removeElement(@Nullable float[] array, float element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static int[] remove(@Nullable int[] array, int index) {
+        if (array == null) return null;
+        return (int[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static int[] removeElement(@Nullable int[] array, int element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static long[] remove(@Nullable long[] array, int index) {
+        if (array == null) return null;
+        return (long[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static long[] removeElement(@Nullable long[] array, long element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @Nullable
+    public static short[] remove(@Nullable short[] array, int index) {
+        if (array == null) return null;
+        return (short[]) remove((Object) array, index);
+    }
+
+    @Nullable
+    public static short[] removeElement(@Nullable short[] array, short element) {
+        int index = indexOf(array, element);
+        if (index == INDEX_NOT_FOUND) {
+            return copy(array);
+        }
+        return remove(array, index);
+    }
+
+    @NonNull
+    private static Object remove(@NonNull Object array, int index) {
+        int length = getLength(array);
+        if (index < 0 || index >= length) {
+            throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+        }
+
+        Object result = Array.newInstance(array.getClass().getComponentType(), length - 1);
+        System.arraycopy(array, 0, result, 0, index);
+        if (index < length - 1) {
+            System.arraycopy(array, index + 1, result, index, length - index - 1);
+        }
+
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // object indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable Object[] array, @Nullable Object objectToFind) {
+        return indexOf(array, objectToFind, 0);
+    }
+
+    public static int indexOf(@Nullable Object[] array, @Nullable final Object objectToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        if (objectToFind == null) {
+            for (int i = startIndex; i < array.length; i++) {
+                if (array[i] == null) {
+                    return i;
+                }
+            }
+        } else {
+            for (int i = startIndex; i < array.length; i++) {
+                if (objectToFind.equals(array[i])) {
+                    return i;
+                }
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable Object[] array, @Nullable Object objectToFind) {
+        return lastIndexOf(array, objectToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable Object[] array, @Nullable Object objectToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        if (objectToFind == null) {
+            for (int i = startIndex; i >= 0; i--) {
+                if (array[i] == null) {
+                    return i;
+                }
+            }
+        } else {
+            for (int i = startIndex; i >= 0; i--) {
+                if (objectToFind.equals(array[i])) {
+                    return i;
+                }
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable Object[] array, @Nullable Object objectToFind) {
+        return indexOf(array, objectToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // long indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable long[] array, long valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable long[] array, long valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable long[] array, long valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable long[] array, long valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable long[] array, long valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // int indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable int[] array, int valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable int[] array, int valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable int[] array, int valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable int[] array, int valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable int[] array, int valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // short indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable short[] array, short valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable short[] array, short valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable short[] array, short valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable short[] array, short valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable short[] array, short valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // char indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable char[] array, char valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable char[] array, char valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable char[] array, char valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable char[] array, char valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable char[] array, char valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // byte indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable byte[] array, byte valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable byte[] array, byte valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable byte[] array, byte valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable byte[] array, byte valueToFind, int startIndex) {
+        if (array == null) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable byte[] array, byte valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // double indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable double[] array, double valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable double[] array, double valueToFind, double tolerance) {
+        return indexOf(array, valueToFind, 0, tolerance);
+    }
+
+    public static int indexOf(@Nullable double[] array, double valueToFind, int startIndex) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int indexOf(@Nullable double[] array, double valueToFind, int startIndex, double tolerance) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        double min = valueToFind - tolerance;
+        double max = valueToFind + tolerance;
+        for (int i = startIndex; i < array.length; i++) {
+            if (array[i] >= min && array[i] <= max) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable double[] array, double valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable double[] array, double valueToFind, double tolerance) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE, tolerance);
+    }
+
+    public static int lastIndexOf(@Nullable double[] array, double valueToFind, int startIndex) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable double[] array, double valueToFind, int startIndex, double tolerance) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        double min = valueToFind - tolerance;
+        double max = valueToFind + tolerance;
+        for (int i = startIndex; i >= 0; i--) {
+            if (array[i] >= min && array[i] <= max) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable double[] array, double valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable double[] array, double valueToFind, double tolerance) {
+        return indexOf(array, valueToFind, 0, tolerance) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // float indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable float[] array, float valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable float[] array, float valueToFind, int startIndex) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable float[] array, float valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable float[] array, float valueToFind, int startIndex) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable float[] array, float valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // bool indexOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    public static int indexOf(@Nullable boolean[] array, boolean valueToFind) {
+        return indexOf(array, valueToFind, 0);
+    }
+
+    public static int indexOf(@Nullable boolean[] array, boolean valueToFind, int startIndex) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        for (int i = startIndex; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static int lastIndexOf(@Nullable boolean[] array, boolean valueToFind) {
+        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+    }
+
+    public static int lastIndexOf(@Nullable boolean[] array, boolean valueToFind, int startIndex) {
+        if (ArrayUtils.isEmpty(array)) {
+            return INDEX_NOT_FOUND;
+        }
+        if (startIndex < 0) {
+            return INDEX_NOT_FOUND;
+        } else if (startIndex >= array.length) {
+            startIndex = array.length - 1;
+        }
+        for (int i = startIndex; i >= 0; i--) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+        return INDEX_NOT_FOUND;
+    }
+
+    public static boolean contains(@Nullable boolean[] array, boolean valueToFind) {
+        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // char converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static char[] toPrimitive(@Nullable Character[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new char[0];
+        }
+        final char[] result = new char[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].charValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static char[] toPrimitive(@Nullable Character[] array, char valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new char[0];
+        }
+        final char[] result = new char[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Character b = array[i];
+            result[i] = (b == null ? valueForNull : b.charValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Character[] toObject(@Nullable char[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Character[0];
+        }
+        final Character[] result = new Character[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Character(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // long converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static long[] toPrimitive(@Nullable Long[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new long[0];
+        }
+        final long[] result = new long[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].longValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static long[] toPrimitive(@Nullable Long[] array, long valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new long[0];
+        }
+        final long[] result = new long[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Long b = array[i];
+            result[i] = (b == null ? valueForNull : b.longValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Long[] toObject(@Nullable long[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Long[0];
+        }
+        final Long[] result = new Long[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Long(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // int converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static int[] toPrimitive(@Nullable Integer[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new int[0];
+        }
+        final int[] result = new int[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].intValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static int[] toPrimitive(@Nullable Integer[] array, int valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new int[0];
+        }
+        final int[] result = new int[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Integer b = array[i];
+            result[i] = (b == null ? valueForNull : b.intValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Integer[] toObject(@Nullable int[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Integer[0];
+        }
+        final Integer[] result = new Integer[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Integer(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // short converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static short[] toPrimitive(@Nullable Short[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new short[0];
+        }
+        final short[] result = new short[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].shortValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static short[] toPrimitive(@Nullable Short[] array, short valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new short[0];
+        }
+        final short[] result = new short[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Short b = array[i];
+            result[i] = (b == null ? valueForNull : b.shortValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Short[] toObject(@Nullable short[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Short[0];
+        }
+        final Short[] result = new Short[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Short(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // byte converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static byte[] toPrimitive(@Nullable Byte[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new byte[0];
+        }
+        final byte[] result = new byte[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].byteValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static byte[] toPrimitive(@Nullable Byte[] array, byte valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new byte[0];
+        }
+        final byte[] result = new byte[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Byte b = array[i];
+            result[i] = (b == null ? valueForNull : b.byteValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Byte[] toObject(@Nullable byte[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Byte[0];
+        }
+        final Byte[] result = new Byte[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Byte(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // double converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static double[] toPrimitive(@Nullable Double[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new double[0];
+        }
+        final double[] result = new double[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].doubleValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static double[] toPrimitive(@Nullable Double[] array, double valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new double[0];
+        }
+        final double[] result = new double[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Double b = array[i];
+            result[i] = (b == null ? valueForNull : b.doubleValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Double[] toObject(@Nullable double[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Double[0];
+        }
+        final Double[] result = new Double[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Double(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // float converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static float[] toPrimitive(@Nullable Float[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new float[0];
+        }
+        final float[] result = new float[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].floatValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static float[] toPrimitive(@Nullable Float[] array, float valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new float[0];
+        }
+        final float[] result = new float[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Float b = array[i];
+            result[i] = (b == null ? valueForNull : b.floatValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Float[] toObject(@Nullable float[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Float[0];
+        }
+        final Float[] result = new Float[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = new Float(array[i]);
+        }
+        return result;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // boolean converters
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Nullable
+    public static boolean[] toPrimitive(@Nullable Boolean[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new boolean[0];
+        }
+        final boolean[] result = new boolean[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = array[i].booleanValue();
+        }
+        return result;
+    }
+
+    @Nullable
+    public static boolean[] toPrimitive(@Nullable Boolean[] array, boolean valueForNull) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new boolean[0];
+        }
+        final boolean[] result = new boolean[array.length];
+        for (int i = 0; i < array.length; i++) {
+            Boolean b = array[i];
+            result[i] = (b == null ? valueForNull : b.booleanValue());
+        }
+        return result;
+    }
+
+    @Nullable
+    public static Boolean[] toObject(@Nullable boolean[] array) {
+        if (array == null) {
+            return null;
+        } else if (array.length == 0) {
+            return new Boolean[0];
+        }
+        final Boolean[] result = new Boolean[array.length];
+        for (int i = 0; i < array.length; i++) {
+            result[i] = (array[i] ? Boolean.TRUE : Boolean.FALSE);
+        }
+        return result;
+    }
+
+    @NonNull
+    public static <T> List<T> asList(@Nullable T... array) {
+        if (array == null || array.length == 0) {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(array);
+    }
+
+    @NonNull
+    public static <T> List<T> asUnmodifiableList(@Nullable T... array) {
+        return Collections.unmodifiableList(asList(array));
+    }
+
+    @NonNull
+    public static <T> List<T> asArrayList(@Nullable T... array) {
+        List<T> list = new ArrayList<>();
+        if (array == null || array.length == 0) return list;
+        list.addAll(Arrays.asList(array));
+        return list;
+    }
+
+    @NonNull
+    public static <T> List<T> asLinkedList(@Nullable T... array) {
+        List<T> list = new LinkedList<>();
+        if (array == null || array.length == 0) return list;
+        list.addAll(Arrays.asList(array));
+        return list;
+    }
+
+    public static <T> void sort(@Nullable T[] array, Comparator<? super T> c) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array, c);
+    }
+
+    public static void sort(@Nullable byte[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    public static void sort(@Nullable char[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    public static void sort(@Nullable double[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    public static void sort(@Nullable float[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    public static void sort(@Nullable int[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    public static void sort(@Nullable long[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    public static void sort(@Nullable short[] array) {
+        if (array == null || array.length < 2) return;
+        Arrays.sort(array);
+    }
+
+    /**
+     * Executes the given closure on each element in the array.
+     * <p>
+     * If the input array or closure is null, there is no change made.
+     *
+     * @param array   The array.
+     * @param closure the closure to perform, may be null
+     */
+    public static <E> void forAllDo(@Nullable Object array, @Nullable Closure<E> closure) {
+        if (array == null || closure == null) return;
+        if (array instanceof Object[]) {
+            Object[] objects = (Object[]) array;
+            for (int i = 0, length = objects.length; i < length; i++) {
+                Object ele = objects[i];
+                closure.execute(i, (E) ele);
+            }
+        } else if (array instanceof boolean[]) {
+            boolean[] booleans = (boolean[]) array;
+            for (int i = 0, length = booleans.length; i < length; i++) {
+                boolean ele = booleans[i];
+                closure.execute(i, (E) (ele ? Boolean.TRUE : Boolean.FALSE));
+            }
+        } else if (array instanceof byte[]) {
+            byte[] bytes = (byte[]) array;
+            for (int i = 0, length = bytes.length; i < length; i++) {
+                byte ele = bytes[i];
+                closure.execute(i, (E) Byte.valueOf(ele));
+            }
+        } else if (array instanceof char[]) {
+            char[] chars = (char[]) array;
+            for (int i = 0, length = chars.length; i < length; i++) {
+                char ele = chars[i];
+                closure.execute(i, (E) Character.valueOf(ele));
+            }
+        } else if (array instanceof short[]) {
+            short[] shorts = (short[]) array;
+            for (int i = 0, length = shorts.length; i < length; i++) {
+                short ele = shorts[i];
+                closure.execute(i, (E) Short.valueOf(ele));
+            }
+        } else if (array instanceof int[]) {
+            int[] ints = (int[]) array;
+            for (int i = 0, length = ints.length; i < length; i++) {
+                int ele = ints[i];
+                closure.execute(i, (E) Integer.valueOf(ele));
+            }
+        } else if (array instanceof long[]) {
+            long[] longs = (long[]) array;
+            for (int i = 0, length = longs.length; i < length; i++) {
+                long ele = longs[i];
+                closure.execute(i, (E) Long.valueOf(ele));
+            }
+        } else if (array instanceof float[]) {
+            float[] floats = (float[]) array;
+            for (int i = 0, length = floats.length; i < length; i++) {
+                float ele = floats[i];
+                closure.execute(i, (E) Float.valueOf(ele));
+            }
+        } else if (array instanceof double[]) {
+            double[] doubles = (double[]) array;
+            for (int i = 0, length = doubles.length; i < length; i++) {
+                double ele = doubles[i];
+                closure.execute(i, (E) Double.valueOf(ele));
+            }
+        } else {
+            throw new IllegalArgumentException("Not an array: " + array.getClass());
+        }
+    }
+
+    /**
+     * Return the string of array.
+     *
+     * @param array The array.
+     * @return the string of array
+     */
+    @NonNull
+    public static String toString(@Nullable Object array) {
+        if (array == null) return "null";
+        if (array instanceof Object[]) {
+            return Arrays.deepToString((Object[]) array);
+        } else if (array instanceof boolean[]) {
+            return Arrays.toString((boolean[]) array);
+        } else if (array instanceof byte[]) {
+            return Arrays.toString((byte[]) array);
+        } else if (array instanceof char[]) {
+            return Arrays.toString((char[]) array);
+        } else if (array instanceof double[]) {
+            return Arrays.toString((double[]) array);
+        } else if (array instanceof float[]) {
+            return Arrays.toString((float[]) array);
+        } else if (array instanceof int[]) {
+            return Arrays.toString((int[]) array);
+        } else if (array instanceof long[]) {
+            return Arrays.toString((long[]) array);
+        } else if (array instanceof short[]) {
+            return Arrays.toString((short[]) array);
+        }
+        throw new IllegalArgumentException("Array has incompatible type: " + array.getClass());
+    }
+
+    public interface Closure<E> {
+        void execute(int index, E item);
+    }
+}

+ 718 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/BarUtils.java

@@ -0,0 +1,718 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+
+import java.lang.reflect.Method;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+import androidx.drawerlayout.widget.DrawerLayout;
+
+import static android.Manifest.permission.EXPAND_STATUS_BAR;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/23
+ *     desc  : utils about bar
+ * </pre>
+ */
+public final class BarUtils {
+
+    ///////////////////////////////////////////////////////////////////////////
+    // status bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static final String TAG_STATUS_BAR = "TAG_STATUS_BAR";
+    private static final String TAG_OFFSET     = "TAG_OFFSET";
+    private static final int    KEY_OFFSET     = -123;
+
+    private BarUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the status bar's height.
+     *
+     * @return the status bar's height
+     */
+    public static int getStatusBarHeight() {
+        Resources resources = Utils.getApp().getResources();
+        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+        return resources.getDimensionPixelSize(resourceId);
+    }
+
+    /**
+     * Set the status bar's visibility.
+     *
+     * @param activity  The activity.
+     * @param isVisible True to set status bar visible, false otherwise.
+     */
+    public static void setStatusBarVisibility(@NonNull final Activity activity,
+                                              final boolean isVisible) {
+        setStatusBarVisibility(activity.getWindow(), isVisible);
+    }
+
+    /**
+     * Set the status bar's visibility.
+     *
+     * @param window    The window.
+     * @param isVisible True to set status bar visible, false otherwise.
+     */
+    public static void setStatusBarVisibility(@NonNull final Window window,
+                                              final boolean isVisible) {
+        if (isVisible) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            showStatusBarView(window);
+            addMarginTopEqualStatusBarHeight(window);
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            hideStatusBarView(window);
+            subtractMarginTopEqualStatusBarHeight(window);
+        }
+    }
+
+    /**
+     * Return whether the status bar is visible.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isStatusBarVisible(@NonNull final Activity activity) {
+        int flags = activity.getWindow().getAttributes().flags;
+        return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0;
+    }
+
+    /**
+     * Set the status bar's light mode.
+     *
+     * @param activity    The activity.
+     * @param isLightMode True to set status bar light mode, false otherwise.
+     */
+    public static void setStatusBarLightMode(@NonNull final Activity activity,
+                                             final boolean isLightMode) {
+        setStatusBarLightMode(activity.getWindow(), isLightMode);
+    }
+
+    /**
+     * Set the status bar's light mode.
+     *
+     * @param window      The window.
+     * @param isLightMode True to set status bar light mode, false otherwise.
+     */
+    public static void setStatusBarLightMode(@NonNull final Window window,
+                                             final boolean isLightMode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            if (isLightMode) {
+                vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            } else {
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            }
+            decorView.setSystemUiVisibility(vis);
+        }
+    }
+
+    /**
+     * Is the status bar light mode.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isStatusBarLightMode(@NonNull final Activity activity) {
+        return isStatusBarLightMode(activity.getWindow());
+    }
+
+    /**
+     * Is the status bar light mode.
+     *
+     * @param window The window.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isStatusBarLightMode(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            return (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
+        }
+        return false;
+    }
+
+    /**
+     * Add the top margin size equals status bar's height for view.
+     *
+     * @param view The view.
+     */
+    public static void addMarginTopEqualStatusBarHeight(@NonNull View view) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        view.setTag(TAG_OFFSET);
+        Object haveSetOffset = view.getTag(KEY_OFFSET);
+        if (haveSetOffset != null && (Boolean) haveSetOffset) return;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+        layoutParams.setMargins(layoutParams.leftMargin,
+                layoutParams.topMargin + getStatusBarHeight(),
+                layoutParams.rightMargin,
+                layoutParams.bottomMargin);
+        view.setTag(KEY_OFFSET, true);
+    }
+
+    /**
+     * Subtract the top margin size equals status bar's height for view.
+     *
+     * @param view The view.
+     */
+    public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        Object haveSetOffset = view.getTag(KEY_OFFSET);
+        if (haveSetOffset == null || !(Boolean) haveSetOffset) return;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+        layoutParams.setMargins(layoutParams.leftMargin,
+                layoutParams.topMargin - getStatusBarHeight(),
+                layoutParams.rightMargin,
+                layoutParams.bottomMargin);
+        view.setTag(KEY_OFFSET, false);
+    }
+
+    private static void addMarginTopEqualStatusBarHeight(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
+        if (withTag == null) return;
+        addMarginTopEqualStatusBarHeight(withTag);
+    }
+
+    private static void subtractMarginTopEqualStatusBarHeight(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
+        if (withTag == null) return;
+        subtractMarginTopEqualStatusBarHeight(withTag);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param activity The activity.
+     * @param color    The status bar's color.
+     */
+    public static View setStatusBarColor(@NonNull final Activity activity,
+                                         @ColorInt final int color) {
+        return setStatusBarColor(activity, color, false);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param activity The activity.
+     * @param color    The status bar's color.
+     * @param isDecor  True to add fake status bar in DecorView,
+     *                 false to add fake status bar in ContentView.
+     */
+    public static View setStatusBarColor(@NonNull final Activity activity,
+                                         @ColorInt final int color,
+                                         final boolean isDecor) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null;
+        transparentStatusBar(activity);
+        return applyStatusBarColor(activity, color, isDecor);
+    }
+
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param window The window.
+     * @param color  The status bar's color.
+     */
+    public static View setStatusBarColor(@NonNull final Window window,
+                                         @ColorInt final int color) {
+        return setStatusBarColor(window, color, false);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param window  The window.
+     * @param color   The status bar's color.
+     * @param isDecor True to add fake status bar in DecorView,
+     *                false to add fake status bar in ContentView.
+     */
+    public static View setStatusBarColor(@NonNull final Window window,
+                                         @ColorInt final int color,
+                                         final boolean isDecor) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null;
+        transparentStatusBar(window);
+        return applyStatusBarColor(window, color, isDecor);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param fakeStatusBar The fake status bar view.
+     * @param color         The status bar's color.
+     */
+    public static void setStatusBarColor(@NonNull final View fakeStatusBar,
+                                         @ColorInt final int color) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        Activity activity = UtilsBridge.getActivityByContext(fakeStatusBar.getContext());
+        if (activity == null) return;
+        transparentStatusBar(activity);
+        fakeStatusBar.setVisibility(View.VISIBLE);
+        ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
+        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        layoutParams.height = getStatusBarHeight();
+        fakeStatusBar.setBackgroundColor(color);
+    }
+
+    /**
+     * Set the custom status bar.
+     *
+     * @param fakeStatusBar The fake status bar view.
+     */
+    public static void setStatusBarCustom(@NonNull final View fakeStatusBar) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        Activity activity = UtilsBridge.getActivityByContext(fakeStatusBar.getContext());
+        if (activity == null) return;
+        transparentStatusBar(activity);
+        fakeStatusBar.setVisibility(View.VISIBLE);
+        ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
+        if (layoutParams == null) {
+            layoutParams = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    getStatusBarHeight()
+            );
+            fakeStatusBar.setLayoutParams(layoutParams);
+        } else {
+            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.height = getStatusBarHeight();
+        }
+    }
+
+    /**
+     * Set the status bar's color for DrawerLayout.
+     * <p>DrawLayout must add {@code android:fitsSystemWindows="true"}</p>
+     *
+     * @param drawer        The DrawLayout.
+     * @param fakeStatusBar The fake status bar view.
+     * @param color         The status bar's color.
+     */
+    public static void setStatusBarColor4Drawer(@NonNull final DrawerLayout drawer,
+                                                @NonNull final View fakeStatusBar,
+                                                @ColorInt final int color) {
+        setStatusBarColor4Drawer(drawer, fakeStatusBar, color, false);
+    }
+
+    /**
+     * Set the status bar's color for DrawerLayout.
+     * <p>DrawLayout must add {@code android:fitsSystemWindows="true"}</p>
+     *
+     * @param drawer        The DrawLayout.
+     * @param fakeStatusBar The fake status bar view.
+     * @param color         The status bar's color.
+     * @param isTop         True to set DrawerLayout at the top layer, false otherwise.
+     */
+    public static void setStatusBarColor4Drawer(@NonNull final DrawerLayout drawer,
+                                                @NonNull final View fakeStatusBar,
+                                                @ColorInt final int color,
+                                                final boolean isTop) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        Activity activity = UtilsBridge.getActivityByContext(fakeStatusBar.getContext());
+        if (activity == null) return;
+        transparentStatusBar(activity);
+        drawer.setFitsSystemWindows(false);
+        setStatusBarColor(fakeStatusBar, color);
+        for (int i = 0, count = drawer.getChildCount(); i < count; i++) {
+            drawer.getChildAt(i).setFitsSystemWindows(false);
+        }
+        if (isTop) {
+            hideStatusBarView(activity);
+        } else {
+            setStatusBarColor(activity, color, false);
+        }
+    }
+
+    private static View applyStatusBarColor(@NonNull final Activity activity,
+                                            final int color,
+                                            boolean isDecor) {
+        return applyStatusBarColor(activity.getWindow(), color, isDecor);
+    }
+
+    private static View applyStatusBarColor(@NonNull final Window window,
+                                            final int color,
+                                            boolean isDecor) {
+        ViewGroup parent = isDecor ?
+                (ViewGroup) window.getDecorView() :
+                (ViewGroup) window.findViewById(android.R.id.content);
+        View fakeStatusBarView = parent.findViewWithTag(TAG_STATUS_BAR);
+        if (fakeStatusBarView != null) {
+            if (fakeStatusBarView.getVisibility() == View.GONE) {
+                fakeStatusBarView.setVisibility(View.VISIBLE);
+            }
+            fakeStatusBarView.setBackgroundColor(color);
+        } else {
+            fakeStatusBarView = createStatusBarView(window.getContext(), color);
+            parent.addView(fakeStatusBarView);
+        }
+        return fakeStatusBarView;
+    }
+
+    private static void hideStatusBarView(@NonNull final Activity activity) {
+        hideStatusBarView(activity.getWindow());
+    }
+
+    private static void hideStatusBarView(@NonNull final Window window) {
+        ViewGroup decorView = (ViewGroup) window.getDecorView();
+        View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
+        if (fakeStatusBarView == null) return;
+        fakeStatusBarView.setVisibility(View.GONE);
+    }
+
+    private static void showStatusBarView(@NonNull final Window window) {
+        ViewGroup decorView = (ViewGroup) window.getDecorView();
+        View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
+        if (fakeStatusBarView == null) return;
+        fakeStatusBarView.setVisibility(View.VISIBLE);
+    }
+
+    private static View createStatusBarView(@NonNull final Context context,
+                                            final int color) {
+        View statusBarView = new View(context);
+        statusBarView.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));
+        statusBarView.setBackgroundColor(color);
+        statusBarView.setTag(TAG_STATUS_BAR);
+        return statusBarView;
+    }
+
+    public static void transparentStatusBar(@NonNull final Activity activity) {
+        transparentStatusBar(activity.getWindow());
+    }
+
+    public static void transparentStatusBar(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            int vis = window.getDecorView().getSystemUiVisibility();
+            window.getDecorView().setSystemUiVisibility(option | vis);
+            window.setStatusBarColor(Color.TRANSPARENT);
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // action bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the action bar's height.
+     *
+     * @return the action bar's height
+     */
+    public static int getActionBarHeight() {
+        TypedValue tv = new TypedValue();
+        if (Utils.getApp().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+            return TypedValue.complexToDimensionPixelSize(
+                    tv.data, Utils.getApp().getResources().getDisplayMetrics()
+            );
+        }
+        return 0;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // notification bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Set the notification bar's visibility.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />}</p>
+     *
+     * @param isVisible True to set notification bar visible, false otherwise.
+     */
+    @RequiresPermission(EXPAND_STATUS_BAR)
+    public static void setNotificationBarVisibility(final boolean isVisible) {
+        String methodName;
+        if (isVisible) {
+            methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
+        } else {
+            methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
+        }
+        invokePanels(methodName);
+    }
+
+    private static void invokePanels(final String methodName) {
+        try {
+            @SuppressLint("WrongConstant")
+            Object service = Utils.getApp().getSystemService("statusbar");
+            @SuppressLint("PrivateApi")
+            Class<?> statusBarManager = Class.forName("android.app.StatusBarManager");
+            Method expand = statusBarManager.getMethod(methodName);
+            expand.invoke(service);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // navigation bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the navigation bar's height.
+     *
+     * @return the navigation bar's height
+     */
+    public static int getNavBarHeight() {
+        Resources res = Utils.getApp().getResources();
+        int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
+        if (resourceId != 0) {
+            return res.getDimensionPixelSize(resourceId);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Set the navigation bar's visibility.
+     *
+     * @param activity  The activity.
+     * @param isVisible True to set navigation bar visible, false otherwise.
+     */
+    public static void setNavBarVisibility(@NonNull final Activity activity, boolean isVisible) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        setNavBarVisibility(activity.getWindow(), isVisible);
+
+    }
+
+    /**
+     * Set the navigation bar's visibility.
+     *
+     * @param window    The window.
+     * @param isVisible True to set navigation bar visible, false otherwise.
+     */
+    public static void setNavBarVisibility(@NonNull final Window window, boolean isVisible) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
+        final ViewGroup decorView = (ViewGroup) window.getDecorView();
+        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
+            final View child = decorView.getChildAt(i);
+            final int id = child.getId();
+            if (id != View.NO_ID) {
+                String resourceEntryName = getResNameById(id);
+                if ("navigationBarBackground".equals(resourceEntryName)) {
+                    child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
+                }
+            }
+        }
+        final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        if (isVisible) {
+            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~uiOptions);
+        } else {
+            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | uiOptions);
+        }
+    }
+
+    /**
+     * Return whether the navigation bar visible.
+     * <p>Call it in onWindowFocusChanged will get right result.</p>
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarVisible(@NonNull final Activity activity) {
+        return isNavBarVisible(activity.getWindow());
+    }
+
+    /**
+     * Return whether the navigation bar visible.
+     * <p>Call it in onWindowFocusChanged will get right result.</p>
+     *
+     * @param window The window.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarVisible(@NonNull final Window window) {
+        boolean isVisible = false;
+        ViewGroup decorView = (ViewGroup) window.getDecorView();
+        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
+            final View child = decorView.getChildAt(i);
+            final int id = child.getId();
+            if (id != View.NO_ID) {
+                String resourceEntryName = getResNameById(id);
+                if ("navigationBarBackground".equals(resourceEntryName)
+                        && child.getVisibility() == View.VISIBLE) {
+                    isVisible = true;
+                    break;
+                }
+            }
+        }
+        if (isVisible) {
+            // 对于三星手机,android10以下非OneUI2的版本,比如 s8,note8 等设备上,
+            // 导航栏显示存在bug:"当用户隐藏导航栏时显示输入法的时候导航栏会跟随显示",会导致隐藏输入法之后判断错误
+            // 这个问题在 OneUI 2 & android 10 版本已修复
+            if (UtilsBridge.isSamsung()
+                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
+                    && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+                try {
+                    return Settings.Global.getInt(Utils.getApp().getContentResolver(), "navigationbar_hide_bar_enabled") == 0;
+                } catch (Exception ignore) {
+                }
+            }
+
+            int visibility = decorView.getSystemUiVisibility();
+            isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+        }
+
+        return isVisible;
+    }
+
+    private static String getResNameById(int id) {
+        try {
+            return Utils.getApp().getResources().getResourceEntryName(id);
+        } catch (Exception ignore) {
+            return "";
+        }
+    }
+
+    /**
+     * Set the navigation bar's color.
+     *
+     * @param activity The activity.
+     * @param color    The navigation bar's color.
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static void setNavBarColor(@NonNull final Activity activity, @ColorInt final int color) {
+        setNavBarColor(activity.getWindow(), color);
+    }
+
+    /**
+     * Set the navigation bar's color.
+     *
+     * @param window The window.
+     * @param color  The navigation bar's color.
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        window.setNavigationBarColor(color);
+    }
+
+    /**
+     * Return the color of navigation bar.
+     *
+     * @param activity The activity.
+     * @return the color of navigation bar
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static int getNavBarColor(@NonNull final Activity activity) {
+        return getNavBarColor(activity.getWindow());
+    }
+
+    /**
+     * Return the color of navigation bar.
+     *
+     * @param window The window.
+     * @return the color of navigation bar
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static int getNavBarColor(@NonNull final Window window) {
+        return window.getNavigationBarColor();
+    }
+
+    /**
+     * Return whether the navigation bar visible.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSupportNavBar() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);
+            if (wm == null) return false;
+            Display display = wm.getDefaultDisplay();
+            Point size = new Point();
+            Point realSize = new Point();
+            display.getSize(size);
+            display.getRealSize(realSize);
+            return realSize.y != size.y || realSize.x != size.x;
+        }
+        boolean menu = ViewConfiguration.get(Utils.getApp()).hasPermanentMenuKey();
+        boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+        return !menu && !back;
+    }
+
+    /**
+     * Set the nav bar's light mode.
+     *
+     * @param activity    The activity.
+     * @param isLightMode True to set nav bar light mode, false otherwise.
+     */
+    public static void setNavBarLightMode(@NonNull final Activity activity,
+                                          final boolean isLightMode) {
+        setNavBarLightMode(activity.getWindow(), isLightMode);
+    }
+
+    /**
+     * Set the nav bar's light mode.
+     *
+     * @param window      The window.
+     * @param isLightMode True to set nav bar light mode, false otherwise.
+     */
+    public static void setNavBarLightMode(@NonNull final Window window,
+                                          final boolean isLightMode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            if (isLightMode) {
+                vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            } else {
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            }
+            decorView.setSystemUiVisibility(vis);
+        }
+    }
+
+    /**
+     * Is the nav bar light mode.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarLightMode(@NonNull final Activity activity) {
+        return isNavBarLightMode(activity.getWindow());
+    }
+
+    /**
+     * Is the nav bar light mode.
+     *
+     * @param window The window.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarLightMode(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            return (vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
+        }
+        return false;
+    }
+}

+ 115 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/BrightnessUtils.java

@@ -0,0 +1,115 @@
+package com.cooleshow.base.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/02/08
+ *     desc  : utils about brightness
+ * </pre>
+ */
+public final class BrightnessUtils {
+
+    private BrightnessUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether automatic brightness mode is enabled.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAutoBrightnessEnabled() {
+        try {
+            int mode = Settings.System.getInt(
+                    Utils.getApp().getContentResolver(),
+                    Settings.System.SCREEN_BRIGHTNESS_MODE
+            );
+            return mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+        } catch (Settings.SettingNotFoundException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Enable or disable automatic brightness mode.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.WRITE_SETTINGS" />}</p>
+     *
+     * @param enabled True to enabled, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean setAutoBrightnessEnabled(final boolean enabled) {
+        return Settings.System.putInt(
+                Utils.getApp().getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                enabled ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
+                        : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
+        );
+    }
+
+    /**
+     * 获取屏幕亮度
+     *
+     * @return 屏幕亮度 0-255
+     */
+    public static int getBrightness() {
+        try {
+            return Settings.System.getInt(
+                    Utils.getApp().getContentResolver(),
+                    Settings.System.SCREEN_BRIGHTNESS
+            );
+        } catch (Settings.SettingNotFoundException e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 设置屏幕亮度
+     * <p>需添加权限 {@code <uses-permission android:name="android.permission.WRITE_SETTINGS" />}</p>
+     * 并得到授权
+     *
+     * @param brightness 亮度值
+     */
+    public static boolean setBrightness(@IntRange(from = 0, to = 255) final int brightness) {
+        ContentResolver resolver = Utils.getApp().getContentResolver();
+        boolean b = Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, brightness);
+        resolver.notifyChange(Settings.System.getUriFor("screen_brightness"), null);
+        return b;
+    }
+
+    /**
+     * 设置窗口亮度
+     *
+     * @param window     窗口
+     * @param brightness 亮度值
+     */
+    public static void setWindowBrightness(@NonNull final Window window,
+                                           @IntRange(from = 0, to = 255) final int brightness) {
+        WindowManager.LayoutParams lp = window.getAttributes();
+        lp.screenBrightness = brightness / 255f;
+        window.setAttributes(lp);
+    }
+
+    /**
+     * 获取窗口亮度
+     *
+     * @param window 窗口
+     * @return 屏幕亮度 0-255
+     */
+    public static int getWindowBrightness(@NonNull final Window window) {
+        WindowManager.LayoutParams lp = window.getAttributes();
+        float brightness = lp.screenBrightness;
+        if (brightness < 0) return getBrightness();
+        return (int) (brightness * 255);
+    }
+}

+ 459 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/BusUtils.java

@@ -0,0 +1,459 @@
+package com.cooleshow.base.utils;
+
+import android.util.Log;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/10/02
+ *     desc  : utils about bus
+ * </pre>
+ */
+public final class BusUtils {
+
+    private static final Object NULL = "nULl";
+    private static final String TAG  = "BusUtils";
+
+    private final Map<String, List<BusInfo>>       mTag_BusInfoListMap          = new ConcurrentHashMap<>();
+    private final Map<String, Set<Object>>         mClassName_BusesMap          = new ConcurrentHashMap<>();
+    private final Map<String, List<String>>        mClassName_TagsMap           = new ConcurrentHashMap<>();
+    private final Map<String, Map<String, Object>> mClassName_Tag_Arg4StickyMap = new ConcurrentHashMap<>();
+
+    private BusUtils() {
+        init();
+    }
+
+    /**
+     * It'll be injected the bus who have {@link Bus} annotation
+     * by function of {@link BusUtils#registerBus} when execute transform task.
+     */
+    private void init() {/*inject*/}
+
+    private void registerBus(String tag,
+                             String className, String funName, String paramType, String paramName,
+                             boolean sticky, String threadMode) {
+        registerBus(tag, className, funName, paramType, paramName, sticky, threadMode, 0);
+    }
+
+    private void registerBus(String tag,
+                             String className, String funName, String paramType, String paramName,
+                             boolean sticky, String threadMode, int priority) {
+        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);
+        if (busInfoList == null) {
+            busInfoList = new CopyOnWriteArrayList<>();
+            mTag_BusInfoListMap.put(tag, busInfoList);
+        }
+        busInfoList.add(new BusInfo(tag, className, funName, paramType, paramName, sticky, threadMode, priority));
+    }
+
+    public static void register(@Nullable final Object bus) {
+        getInstance().registerInner(bus);
+    }
+
+    public static void unregister(@Nullable final Object bus) {
+        getInstance().unregisterInner(bus);
+    }
+
+    public static void post(@NonNull final String tag) {
+        post(tag, NULL);
+    }
+
+    public static void post(@NonNull final String tag, @NonNull final Object arg) {
+        getInstance().postInner(tag, arg);
+    }
+
+    public static void postSticky(@NonNull final String tag) {
+        postSticky(tag, NULL);
+    }
+
+    public static void postSticky(@NonNull final String tag, final Object arg) {
+        getInstance().postStickyInner(tag, arg);
+    }
+
+    public static void removeSticky(final String tag) {
+        getInstance().removeStickyInner(tag);
+    }
+
+    public static String toString_() {
+        return getInstance().toString();
+    }
+
+    @Override
+    public String toString() {
+        return "BusUtils: " + mTag_BusInfoListMap;
+    }
+
+    private static BusUtils getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    private void registerInner(@Nullable final Object bus) {
+        if (bus == null) return;
+        Class<?> aClass = bus.getClass();
+        String className = aClass.getName();
+        boolean isNeedRecordTags = false;
+        synchronized (mClassName_BusesMap) {
+            Set<Object> buses = mClassName_BusesMap.get(className);
+            if (buses == null) {
+                buses = new CopyOnWriteArraySet<>();
+                mClassName_BusesMap.put(className, buses);
+                isNeedRecordTags = true;
+            }
+            if (buses.contains(bus)) {
+                Log.w(TAG, "The bus of <" + bus + "> already registered.");
+                return;
+            } else {
+                buses.add(bus);
+            }
+        }
+        if (isNeedRecordTags) {
+            recordTags(aClass, className);
+        }
+        consumeStickyIfExist(bus);
+    }
+
+    private void recordTags(Class<?> aClass, String className) {
+        List<String> tags = mClassName_TagsMap.get(className);
+        if (tags == null) {
+            synchronized (mClassName_TagsMap) {
+                tags = mClassName_TagsMap.get(className);
+                if (tags == null) {
+                    tags = new CopyOnWriteArrayList<>();
+                    for (Map.Entry<String, List<BusInfo>> entry : mTag_BusInfoListMap.entrySet()) {
+                        for (BusInfo busInfo : entry.getValue()) {
+                            try {
+                                if (Class.forName(busInfo.className).isAssignableFrom(aClass)) {
+                                    tags.add(entry.getKey());
+                                    busInfo.subClassNames.add(className);
+                                }
+                            } catch (ClassNotFoundException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                    mClassName_TagsMap.put(className, tags);
+                }
+            }
+        }
+    }
+
+    private void consumeStickyIfExist(final Object bus) {
+        Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(bus.getClass().getName());
+        if (tagArgMap == null) return;
+        synchronized (mClassName_Tag_Arg4StickyMap) {
+            for (Map.Entry<String, Object> tagArgEntry : tagArgMap.entrySet()) {
+                consumeSticky(bus, tagArgEntry.getKey(), tagArgEntry.getValue());
+            }
+        }
+    }
+
+    private void consumeSticky(final Object bus, final String tag, final Object arg) {
+        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);
+        if (busInfoList == null) {
+            Log.e(TAG, "The bus of tag <" + tag + "> is not exists.");
+            return;
+        }
+        for (BusInfo busInfo : busInfoList) {
+            if (!busInfo.subClassNames.contains(bus.getClass().getName())) {
+                continue;
+            }
+            if (!busInfo.sticky) {
+                continue;
+            }
+
+            synchronized (mClassName_Tag_Arg4StickyMap) {
+                Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(busInfo.className);
+                if (tagArgMap == null || !tagArgMap.containsKey(tag)) {
+                    continue;
+                }
+                invokeBus(bus, arg, busInfo, true);
+            }
+        }
+    }
+
+    private void unregisterInner(final Object bus) {
+        if (bus == null) return;
+        String className = bus.getClass().getName();
+        synchronized (mClassName_BusesMap) {
+            Set<Object> buses = mClassName_BusesMap.get(className);
+            if (buses == null || !buses.contains(bus)) {
+                Log.e(TAG, "The bus of <" + bus + "> was not registered before.");
+                return;
+            }
+            buses.remove(bus);
+        }
+    }
+
+    private void postInner(final String tag, final Object arg) {
+        postInner(tag, arg, false);
+    }
+
+    private void postInner(final String tag, final Object arg, final boolean sticky) {
+        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);
+        if (busInfoList == null) {
+            Log.e(TAG, "The bus of tag <" + tag + "> is not exists.");
+            if (mTag_BusInfoListMap.isEmpty()) {
+                Log.e(TAG, "Please check whether the bus plugin is applied.");
+            }
+            return;
+        }
+        for (BusInfo busInfo : busInfoList) {
+            invokeBus(arg, busInfo, sticky);
+        }
+    }
+
+    private void invokeBus(Object arg, BusInfo busInfo, boolean sticky) {
+        invokeBus(null, arg, busInfo, sticky);
+    }
+
+    private void invokeBus(Object bus, Object arg, BusInfo busInfo, boolean sticky) {
+        if (busInfo.method == null) {
+            Method method = getMethodByBusInfo(busInfo);
+            if (method == null) {
+                return;
+            }
+            busInfo.method = method;
+        }
+        invokeMethod(bus, arg, busInfo, sticky);
+    }
+
+    private Method getMethodByBusInfo(BusInfo busInfo) {
+        try {
+            if ("".equals(busInfo.paramType)) {
+                return Class.forName(busInfo.className).getDeclaredMethod(busInfo.funName);
+            } else {
+                return Class.forName(busInfo.className).getDeclaredMethod(busInfo.funName, getClassName(busInfo.paramType));
+            }
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private Class getClassName(String paramType) throws ClassNotFoundException {
+        switch (paramType) {
+            case "boolean":
+                return boolean.class;
+            case "int":
+                return int.class;
+            case "long":
+                return long.class;
+            case "short":
+                return short.class;
+            case "byte":
+                return byte.class;
+            case "double":
+                return double.class;
+            case "float":
+                return float.class;
+            case "char":
+                return char.class;
+            default:
+                return Class.forName(paramType);
+        }
+    }
+
+    private void invokeMethod(final Object arg, final BusInfo busInfo, final boolean sticky) {
+        invokeMethod(null, arg, busInfo, sticky);
+    }
+
+    private void invokeMethod(final Object bus, final Object arg, final BusInfo busInfo, final boolean sticky) {
+        Runnable runnable = new Runnable() {
+            @Override
+            public void run() {
+                realInvokeMethod(bus, arg, busInfo, sticky);
+            }
+        };
+        switch (busInfo.threadMode) {
+            case "MAIN":
+                ThreadUtils.runOnUiThread(runnable);
+                return;
+            case "IO":
+                ThreadUtils.getIoPool().execute(runnable);
+                return;
+            case "CPU":
+                ThreadUtils.getCpuPool().execute(runnable);
+                return;
+            case "CACHED":
+                ThreadUtils.getCachedPool().execute(runnable);
+                return;
+            case "SINGLE":
+                ThreadUtils.getSinglePool().execute(runnable);
+                return;
+            default:
+                runnable.run();
+        }
+    }
+
+    private void realInvokeMethod(Object bus, Object arg, BusInfo busInfo, boolean sticky) {
+        Set<Object> buses = new HashSet<>();
+        if (bus == null) {
+            for (String subClassName : busInfo.subClassNames) {
+                Set<Object> subBuses = mClassName_BusesMap.get(subClassName);
+                if (subBuses != null && !subBuses.isEmpty()) {
+                    buses.addAll(subBuses);
+                }
+            }
+            if (buses.size() == 0) {
+                if (!sticky) {
+                    Log.e(TAG, "The " + busInfo + " was not registered before.");
+                }
+                return;
+            }
+        } else {
+            buses.add(bus);
+        }
+        invokeBuses(arg, busInfo, buses);
+    }
+
+    private void invokeBuses(Object arg, BusInfo busInfo, Set<Object> buses) {
+        try {
+            if (arg == NULL) {
+                for (Object bus : buses) {
+                    busInfo.method.invoke(bus);
+                }
+            } else {
+                for (Object bus : buses) {
+                    busInfo.method.invoke(bus, arg);
+                }
+            }
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void postStickyInner(final String tag, final Object arg) {
+        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);
+        if (busInfoList == null) {
+            Log.e(TAG, "The bus of tag <" + tag + "> is not exists.");
+            return;
+        }
+        // 获取多对象,然后消费各个 busInfoList
+        for (BusInfo busInfo : busInfoList) {
+            if (!busInfo.sticky) { // not sticky bus will post directly.
+                invokeBus(arg, busInfo, false);
+                continue;
+            }
+            synchronized (mClassName_Tag_Arg4StickyMap) {
+                Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(busInfo.className);
+                if (tagArgMap == null) {
+                    tagArgMap = new ConcurrentHashMap<>();
+                    mClassName_Tag_Arg4StickyMap.put(busInfo.className, tagArgMap);
+                }
+                tagArgMap.put(tag, arg);
+            }
+            invokeBus(arg, busInfo, true);
+        }
+    }
+
+    private void removeStickyInner(final String tag) {
+        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);
+        if (busInfoList == null) {
+            Log.e(TAG, "The bus of tag <" + tag + "> is not exists.");
+            return;
+        }
+        for (BusInfo busInfo : busInfoList) {
+            if (!busInfo.sticky) {
+                continue;
+            }
+            synchronized (mClassName_Tag_Arg4StickyMap) {
+                Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(busInfo.className);
+                if (tagArgMap == null || !tagArgMap.containsKey(tag)) {
+                    return;
+                }
+                tagArgMap.remove(tag);
+            }
+        }
+    }
+
+    static void registerBus4Test(String tag,
+                                 String className, String funName, String paramType, String paramName,
+                                 boolean sticky, String threadMode, int priority) {
+        getInstance().registerBus(tag, className, funName, paramType, paramName, sticky, threadMode, priority);
+    }
+
+    private static final class BusInfo {
+
+        String       tag;
+        String       className;
+        String       funName;
+        String       paramType;
+        String       paramName;
+        boolean      sticky;
+        String       threadMode;
+        int          priority;
+        Method       method;
+        List<String> subClassNames;
+
+        BusInfo(String tag, String className, String funName, String paramType, String paramName,
+                boolean sticky, String threadMode, int priority) {
+            this.tag = tag;
+            this.className = className;
+            this.funName = funName;
+            this.paramType = paramType;
+            this.paramName = paramName;
+            this.sticky = sticky;
+            this.threadMode = threadMode;
+            this.priority = priority;
+            this.subClassNames = new CopyOnWriteArrayList<>();
+        }
+
+        @Override
+        public String toString() {
+            return "BusInfo { tag : " + tag +
+                    ", desc: " + getDesc() +
+                    ", sticky: " + sticky +
+                    ", threadMode: " + threadMode +
+                    ", method: " + method +
+                    ", priority: " + priority +
+                    " }";
+        }
+
+        private String getDesc() {
+            return className + "#" + funName +
+                    ("".equals(paramType) ? "()" : ("(" + paramType + " " + paramName + ")"));
+        }
+    }
+
+    public enum ThreadMode {
+        MAIN, IO, CPU, CACHED, SINGLE, POSTING
+    }
+
+    @Target({ElementType.METHOD})
+    @Retention(RetentionPolicy.CLASS)
+    public @interface Bus {
+        String tag();
+
+        boolean sticky() default false;
+
+        ThreadMode threadMode() default ThreadMode.POSTING;
+
+        int priority() default 0;
+    }
+
+    private static class LazyHolder {
+        private static final BusUtils INSTANCE = new BusUtils();
+    }
+}

+ 958 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDiskStaticUtils.java

@@ -0,0 +1,958 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/01/04
+ *     desc  : utils about disk cache
+ * </pre>
+ */
+public final class CacheDiskStaticUtils {
+
+    private static CacheDiskUtils sDefaultCacheDiskUtils;
+
+    /**
+     * Set the default instance of {@link CacheDiskUtils}.
+     *
+     * @param cacheDiskUtils The default instance of {@link CacheDiskUtils}.
+     */
+    public static void setDefaultCacheDiskUtils(@Nullable final CacheDiskUtils cacheDiskUtils) {
+        sDefaultCacheDiskUtils = cacheDiskUtils;
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final byte[] value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final byte[] value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key The key of cache.
+     * @return the bytes if cache exists or null otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key) {
+        return getBytes(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bytes if cache exists or defaultValue otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key, @Nullable final byte[] defaultValue) {
+        return getBytes(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about String
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final String value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final String value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key The key of cache.
+     * @return the string value if cache exists or null otherwise
+     */
+    public static String getString(@NonNull final String key) {
+        return getString(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the string value if cache exists or defaultValue otherwise
+     */
+    public static String getString(@NonNull final String key, @Nullable final String defaultValue) {
+        return getString(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONObject
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final JSONObject value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final JSONObject value,
+                           final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONObject if cache exists or null otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key) {
+        return getJSONObject(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONObject if cache exists or defaultValue otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key, @Nullable final JSONObject defaultValue) {
+        return getJSONObject(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final JSONArray value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final JSONArray value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONArray if cache exists or null otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key) {
+        return getJSONArray(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONArray if cache exists or defaultValue otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key, @Nullable final JSONArray defaultValue) {
+        return getJSONArray(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Bitmap
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final Bitmap value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final Bitmap value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key) {
+        return getBitmap(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key, @Nullable final Bitmap defaultValue) {
+        return getBitmap(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Drawable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final Drawable value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final Drawable value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key The key of cache.
+     * @return the drawable if cache exists or null otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key) {
+        return getDrawable(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the drawable if cache exists or defaultValue otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key, final @Nullable Drawable defaultValue) {
+        return getDrawable(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Parcelable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final Parcelable value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final Parcelable value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key     The key of cache.
+     * @param creator The creator.
+     * @param <T>     The value type.
+     * @return the parcelable if cache exists or null otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator) {
+        return getParcelable(key, creator, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key          The key of cache.
+     * @param creator      The creator.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @param <T>          The value type.
+     * @return the parcelable if cache exists or defaultValue otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator,
+                                      @Nullable final T defaultValue) {
+        return getParcelable(key, creator, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Serializable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, @Nullable final Serializable value) {
+        put(key, value, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, @Nullable final Serializable value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Object getSerializable(@NonNull final String key) {
+        return getSerializable(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Object getSerializable(@NonNull final String key, @Nullable final Object defaultValue) {
+        return getSerializable(key, defaultValue, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the size of cache, in bytes.
+     *
+     * @return the size of cache, in bytes
+     */
+    public static long getCacheSize() {
+        return getCacheSize(getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Return the count of cache.
+     *
+     * @return the count of cache
+     */
+    public static int getCacheCount() {
+        return getCacheCount(getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key The key of cache.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean remove(@NonNull final String key) {
+        return remove(key, getDefaultCacheDiskUtils());
+    }
+
+    /**
+     * Clear all of the cache.
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean clear() {
+        return clear(getDefaultCacheDiskUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // dividing line
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final byte[] value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final byte[] value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the bytes if cache exists or null otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getBytes(key);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the bytes if cache exists or defaultValue otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key,
+                                  @Nullable final byte[] defaultValue,
+                                  @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getBytes(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about String
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final String value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final String value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the string value if cache exists or null otherwise
+     */
+    public static String getString(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getString(key);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the string value if cache exists or defaultValue otherwise
+     */
+    public static String getString(@NonNull final String key,
+                                   @Nullable final String defaultValue,
+                                   @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getString(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONObject
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final JSONObject value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final JSONObject value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the JSONObject if cache exists or null otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getJSONObject(key);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the JSONObject if cache exists or defaultValue otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key,
+                                           @Nullable final JSONObject defaultValue,
+                                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getJSONObject(key, defaultValue);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final JSONArray value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final JSONArray value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the JSONArray if cache exists or null otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getJSONArray(key);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the JSONArray if cache exists or defaultValue otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key,
+                                         @Nullable final JSONArray defaultValue,
+                                         @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getJSONArray(key, defaultValue);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Bitmap
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Bitmap value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Bitmap value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getBitmap(key);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key,
+                                   @Nullable final Bitmap defaultValue,
+                                   @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getBitmap(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Drawable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Drawable value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Drawable value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the drawable if cache exists or null otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getDrawable(key);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the drawable if cache exists or defaultValue otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key,
+                                       @Nullable final Drawable defaultValue,
+                                       @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getDrawable(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Parcelable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Parcelable value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Parcelable value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key            The key of cache.
+     * @param creator        The creator.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @param <T>            The value type.
+     * @return the parcelable if cache exists or null otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator,
+                                      @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getParcelable(key, creator);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key            The key of cache.
+     * @param creator        The creator.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @param <T>            The value type.
+     * @return the parcelable if cache exists or defaultValue otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator,
+                                      @Nullable final T defaultValue,
+                                      @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getParcelable(key, creator, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Serializable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Serializable value,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value);
+    }
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key            The key of cache.
+     * @param value          The value of cache.
+     * @param saveTime       The save time of cache, in seconds.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           @Nullable final Serializable value,
+                           final int saveTime,
+                           @NonNull final CacheDiskUtils cacheDiskUtils) {
+        cacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Object getSerializable(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getSerializable(key);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key            The key of cache.
+     * @param defaultValue   The default value if the cache doesn't exist.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Object getSerializable(@NonNull final String key,
+                                         @Nullable final Object defaultValue,
+                                         @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getSerializable(key, defaultValue);
+    }
+
+    /**
+     * Return the size of cache, in bytes.
+     *
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the size of cache, in bytes
+     */
+    public static long getCacheSize(@NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getCacheSize();
+    }
+
+    /**
+     * Return the count of cache.
+     *
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return the count of cache
+     */
+    public static int getCacheCount(@NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.getCacheCount();
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key            The key of cache.
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean remove(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.remove(key);
+    }
+
+    /**
+     * Clear all of the cache.
+     *
+     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean clear(@NonNull final CacheDiskUtils cacheDiskUtils) {
+        return cacheDiskUtils.clear();
+    }
+
+    @NonNull
+    private static CacheDiskUtils getDefaultCacheDiskUtils() {
+        return sDefaultCacheDiskUtils != null ? sDefaultCacheDiskUtils : CacheDiskUtils.getInstance();
+    }
+}

+ 875 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDiskUtils.java

@@ -0,0 +1,875 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.cooleshow.base.constanst.CacheConstants;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/05/24
+ *     desc  : utils about disk cache
+ * </pre>
+ */
+public final class CacheDiskUtils implements CacheConstants {
+
+    private static final long   DEFAULT_MAX_SIZE  = Long.MAX_VALUE;
+    private static final int    DEFAULT_MAX_COUNT = Integer.MAX_VALUE;
+    private static final String CACHE_PREFIX      = "cdu_";
+    private static final String TYPE_BYTE         = "by_";
+    private static final String TYPE_STRING       = "st_";
+    private static final String TYPE_JSON_OBJECT  = "jo_";
+    private static final String TYPE_JSON_ARRAY   = "ja_";
+    private static final String TYPE_BITMAP       = "bi_";
+    private static final String TYPE_DRAWABLE     = "dr_";
+    private static final String TYPE_PARCELABLE   = "pa_";
+    private static final String TYPE_SERIALIZABLE = "se_";
+
+    private static final Map<String, CacheDiskUtils> CACHE_MAP = new HashMap<>();
+
+    private final String           mCacheKey;
+    private final File             mCacheDir;
+    private final long             mMaxSize;
+    private final int              mMaxCount;
+    private       DiskCacheManager mDiskCacheManager;
+
+    /**
+     * Return the single {@link CacheDiskUtils} instance.
+     * <p>cache directory: /data/data/package/cache/cacheUtils</p>
+     * <p>cache size: unlimited</p>
+     * <p>cache count: unlimited</p>
+     *
+     * @return the single {@link CacheDiskUtils} instance
+     */
+    public static CacheDiskUtils getInstance() {
+        return getInstance("", DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);
+    }
+
+    /**
+     * Return the single {@link CacheDiskUtils} instance.
+     * <p>cache directory: /data/data/package/cache/cacheUtils</p>
+     * <p>cache size: unlimited</p>
+     * <p>cache count: unlimited</p>
+     *
+     * @param cacheName The name of cache.
+     * @return the single {@link CacheDiskUtils} instance
+     */
+    public static CacheDiskUtils getInstance(final String cacheName) {
+        return getInstance(cacheName, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);
+    }
+
+    /**
+     * Return the single {@link CacheDiskUtils} instance.
+     * <p>cache directory: /data/data/package/cache/cacheUtils</p>
+     *
+     * @param maxSize  The max size of cache, in bytes.
+     * @param maxCount The max count of cache.
+     * @return the single {@link CacheDiskUtils} instance
+     */
+    public static CacheDiskUtils getInstance(final long maxSize, final int maxCount) {
+        return getInstance("", maxSize, maxCount);
+    }
+
+    /**
+     * Return the single {@link CacheDiskUtils} instance.
+     * <p>cache directory: /data/data/package/cache/cacheName</p>
+     *
+     * @param cacheName The name of cache.
+     * @param maxSize   The max size of cache, in bytes.
+     * @param maxCount  The max count of cache.
+     * @return the single {@link CacheDiskUtils} instance
+     */
+    public static CacheDiskUtils getInstance(String cacheName, final long maxSize, final int maxCount) {
+        if (UtilsBridge.isSpace(cacheName)) cacheName = "cacheUtils";
+        File file = new File(Utils.getApp().getCacheDir(), cacheName);
+        return getInstance(file, maxSize, maxCount);
+    }
+
+    /**
+     * Return the single {@link CacheDiskUtils} instance.
+     * <p>cache size: unlimited</p>
+     * <p>cache count: unlimited</p>
+     *
+     * @param cacheDir The directory of cache.
+     * @return the single {@link CacheDiskUtils} instance
+     */
+    public static CacheDiskUtils getInstance(@NonNull final File cacheDir) {
+        return getInstance(cacheDir, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);
+    }
+
+    /**
+     * Return the single {@link CacheDiskUtils} instance.
+     *
+     * @param cacheDir The directory of cache.
+     * @param maxSize  The max size of cache, in bytes.
+     * @param maxCount The max count of cache.
+     * @return the single {@link CacheDiskUtils} instance
+     */
+    public static CacheDiskUtils getInstance(@NonNull final File cacheDir,
+                                             final long maxSize,
+                                             final int maxCount) {
+        final String cacheKey = cacheDir.getAbsoluteFile() + "_" + maxSize + "_" + maxCount;
+        CacheDiskUtils cache = CACHE_MAP.get(cacheKey);
+        if (cache == null) {
+            synchronized (CacheDiskUtils.class) {
+                cache = CACHE_MAP.get(cacheKey);
+                if (cache == null) {
+                    cache = new CacheDiskUtils(cacheKey, cacheDir, maxSize, maxCount);
+                    CACHE_MAP.put(cacheKey, cache);
+                }
+            }
+        }
+        return cache;
+    }
+
+    private CacheDiskUtils(final String cacheKey,
+                           final File cacheDir,
+                           final long maxSize,
+                           final int maxCount) {
+        mCacheKey = cacheKey;
+        mCacheDir = cacheDir;
+        mMaxSize = maxSize;
+        mMaxCount = maxCount;
+    }
+
+    private DiskCacheManager getDiskCacheManager() {
+        if (mCacheDir.exists()) {
+            if (mDiskCacheManager == null) {
+                mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);
+            }
+        } else {
+            if (mCacheDir.mkdirs()) {
+                mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);
+            } else {
+                Log.e("CacheDiskUtils", "can't make dirs in " + mCacheDir.getAbsolutePath());
+            }
+        }
+        return mDiskCacheManager;
+    }
+
+    @Override
+    public String toString() {
+        return mCacheKey + "@" + Integer.toHexString(hashCode());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about bytes
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final byte[] value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final byte[] value, final int saveTime) {
+        realPutBytes(TYPE_BYTE + key, value, saveTime);
+    }
+
+    private void realPutBytes(final String key, byte[] value, int saveTime) {
+        if (value == null) return;
+        DiskCacheManager diskCacheManager = getDiskCacheManager();
+        if (diskCacheManager == null) return;
+        if (saveTime >= 0) value = DiskCacheHelper.newByteArrayWithTime(saveTime, value);
+        File file = diskCacheManager.getFileBeforePut(key);
+        UtilsBridge.writeFileFromBytes(file, value);
+        diskCacheManager.updateModify(file);
+        diskCacheManager.put(file);
+    }
+
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key The key of cache.
+     * @return the bytes if cache exists or null otherwise
+     */
+    public byte[] getBytes(@NonNull final String key) {
+        return getBytes(key, null);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bytes if cache exists or defaultValue otherwise
+     */
+    public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {
+        return realGetBytes(TYPE_BYTE + key, defaultValue);
+    }
+
+    private byte[] realGetBytes(@NonNull final String key) {
+        return realGetBytes(key, null);
+    }
+
+    private byte[] realGetBytes(@NonNull final String key, final byte[] defaultValue) {
+        DiskCacheManager diskCacheManager = getDiskCacheManager();
+        if (diskCacheManager == null) return defaultValue;
+        final File file = diskCacheManager.getFileIfExists(key);
+        if (file == null) return defaultValue;
+        byte[] data = UtilsBridge.readFile2Bytes(file);
+        if (DiskCacheHelper.isDue(data)) {
+            diskCacheManager.removeByKey(key);
+            return defaultValue;
+        }
+        diskCacheManager.updateModify(file);
+        return DiskCacheHelper.getDataWithoutDueTime(data);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about String
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final String value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final String value, final int saveTime) {
+        realPutBytes(TYPE_STRING + key, UtilsBridge.string2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key The key of cache.
+     * @return the string value if cache exists or null otherwise
+     */
+    public String getString(@NonNull final String key) {
+        return getString(key, null);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the string value if cache exists or defaultValue otherwise
+     */
+    public String getString(@NonNull final String key, final String defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_STRING + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2String(bytes);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONObject
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final JSONObject value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key,
+                    final JSONObject value,
+                    final int saveTime) {
+        realPutBytes(TYPE_JSON_OBJECT + key, UtilsBridge.jsonObject2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONObject if cache exists or null otherwise
+     */
+    public JSONObject getJSONObject(@NonNull final String key) {
+        return getJSONObject(key, null);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONObject if cache exists or defaultValue otherwise
+     */
+    public JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_JSON_OBJECT + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2JSONObject(bytes);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final JSONArray value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final JSONArray value, final int saveTime) {
+        realPutBytes(TYPE_JSON_ARRAY + key, UtilsBridge.jsonArray2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONArray if cache exists or null otherwise
+     */
+    public JSONArray getJSONArray(@NonNull final String key) {
+        return getJSONArray(key, null);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONArray if cache exists or defaultValue otherwise
+     */
+    public JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_JSON_ARRAY + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2JSONArray(bytes);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Bitmap
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Bitmap value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Bitmap value, final int saveTime) {
+        realPutBytes(TYPE_BITMAP + key, UtilsBridge.bitmap2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public Bitmap getBitmap(@NonNull final String key) {
+        return getBitmap(key, null);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_BITMAP + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2Bitmap(bytes);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Drawable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Drawable value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Drawable value, final int saveTime) {
+        realPutBytes(TYPE_DRAWABLE + key, UtilsBridge.drawable2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key The key of cache.
+     * @return the drawable if cache exists or null otherwise
+     */
+    public Drawable getDrawable(@NonNull final String key) {
+        return getDrawable(key, null);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the drawable if cache exists or defaultValue otherwise
+     */
+    public Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_DRAWABLE + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2Drawable(bytes);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Parcelable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Parcelable value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Parcelable value, final int saveTime) {
+        realPutBytes(TYPE_PARCELABLE + key, UtilsBridge.parcelable2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key     The key of cache.
+     * @param creator The creator.
+     * @param <T>     The value type.
+     * @return the parcelable if cache exists or null otherwise
+     */
+    public <T> T getParcelable(@NonNull final String key,
+                               @NonNull final Parcelable.Creator<T> creator) {
+        return getParcelable(key, creator, null);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key          The key of cache.
+     * @param creator      The creator.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @param <T>          The value type.
+     * @return the parcelable if cache exists or defaultValue otherwise
+     */
+    public <T> T getParcelable(@NonNull final String key,
+                               @NonNull final Parcelable.Creator<T> creator,
+                               final T defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_PARCELABLE + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2Parcelable(bytes, creator);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Serializable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Serializable value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Serializable value, final int saveTime) {
+        realPutBytes(TYPE_SERIALIZABLE + key, UtilsBridge.serializable2Bytes(value), saveTime);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public Object getSerializable(@NonNull final String key) {
+        return getSerializable(key, null);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public Object getSerializable(@NonNull final String key, final Object defaultValue) {
+        byte[] bytes = realGetBytes(TYPE_SERIALIZABLE + key);
+        if (bytes == null) return defaultValue;
+        return UtilsBridge.bytes2Object(bytes);
+    }
+
+    /**
+     * Return the size of cache, in bytes.
+     *
+     * @return the size of cache, in bytes
+     */
+    public long getCacheSize() {
+        DiskCacheManager diskCacheManager = getDiskCacheManager();
+        if (diskCacheManager == null) return 0;
+        return diskCacheManager.getCacheSize();
+    }
+
+    /**
+     * Return the count of cache.
+     *
+     * @return the count of cache
+     */
+    public int getCacheCount() {
+        DiskCacheManager diskCacheManager = getDiskCacheManager();
+        if (diskCacheManager == null) return 0;
+        return diskCacheManager.getCacheCount();
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key The key of cache.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public boolean remove(@NonNull final String key) {
+        DiskCacheManager diskCacheManager = getDiskCacheManager();
+        if (diskCacheManager == null) return true;
+        return diskCacheManager.removeByKey(TYPE_BYTE + key)
+                && diskCacheManager.removeByKey(TYPE_STRING + key)
+                && diskCacheManager.removeByKey(TYPE_JSON_OBJECT + key)
+                && diskCacheManager.removeByKey(TYPE_JSON_ARRAY + key)
+                && diskCacheManager.removeByKey(TYPE_BITMAP + key)
+                && diskCacheManager.removeByKey(TYPE_DRAWABLE + key)
+                && diskCacheManager.removeByKey(TYPE_PARCELABLE + key)
+                && diskCacheManager.removeByKey(TYPE_SERIALIZABLE + key);
+    }
+
+    /**
+     * Clear all of the cache.
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public boolean clear() {
+        DiskCacheManager diskCacheManager = getDiskCacheManager();
+        if (diskCacheManager == null) return true;
+        return diskCacheManager.clear();
+    }
+
+    private static final class DiskCacheManager {
+        private final AtomicLong      cacheSize;
+        private final AtomicInteger   cacheCount;
+        private final long            sizeLimit;
+        private final int             countLimit;
+        private final Map<File, Long> lastUsageDates
+                = Collections.synchronizedMap(new HashMap<File, Long>());
+        private final File            cacheDir;
+        private final Thread          mThread;
+
+        private DiskCacheManager(final File cacheDir, final long sizeLimit, final int countLimit) {
+            this.cacheDir = cacheDir;
+            this.sizeLimit = sizeLimit;
+            this.countLimit = countLimit;
+            cacheSize = new AtomicLong();
+            cacheCount = new AtomicInteger();
+            mThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    int size = 0;
+                    int count = 0;
+                    final File[] cachedFiles = cacheDir.listFiles(new FilenameFilter() {
+                        @Override
+                        public boolean accept(File dir, String name) {
+                            return name.startsWith(CACHE_PREFIX);
+                        }
+                    });
+                    if (cachedFiles != null) {
+                        for (File cachedFile : cachedFiles) {
+                            size += cachedFile.length();
+                            count += 1;
+                            lastUsageDates.put(cachedFile, cachedFile.lastModified());
+                        }
+                        cacheSize.getAndAdd(size);
+                        cacheCount.getAndAdd(count);
+                    }
+                }
+            });
+            mThread.start();
+        }
+
+        private long getCacheSize() {
+            wait2InitOk();
+            return cacheSize.get();
+        }
+
+        private int getCacheCount() {
+            wait2InitOk();
+            return cacheCount.get();
+        }
+
+        private File getFileBeforePut(final String key) {
+            wait2InitOk();
+            File file = new File(cacheDir, getCacheNameByKey(key));
+            if (file.exists()) {
+                cacheCount.addAndGet(-1);
+                cacheSize.addAndGet(-file.length());
+            }
+            return file;
+        }
+
+        private void wait2InitOk() {
+            try {
+                mThread.join();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        private File getFileIfExists(final String key) {
+            File file = new File(cacheDir, getCacheNameByKey(key));
+            if (!file.exists()) return null;
+            return file;
+        }
+
+        private String getCacheNameByKey(final String key) {
+            return CACHE_PREFIX + key.substring(0, 3) + key.substring(3).hashCode();
+        }
+
+        private void put(final File file) {
+            cacheCount.addAndGet(1);
+            cacheSize.addAndGet(file.length());
+            while (cacheCount.get() > countLimit || cacheSize.get() > sizeLimit) {
+                cacheSize.addAndGet(-removeOldest());
+                cacheCount.addAndGet(-1);
+            }
+        }
+
+        private void updateModify(final File file) {
+            Long millis = System.currentTimeMillis();
+            file.setLastModified(millis);
+            lastUsageDates.put(file, millis);
+        }
+
+        private boolean removeByKey(final String key) {
+            File file = getFileIfExists(key);
+            if (file == null) return true;
+            if (!file.delete()) return false;
+            cacheSize.addAndGet(-file.length());
+            cacheCount.addAndGet(-1);
+            lastUsageDates.remove(file);
+            return true;
+        }
+
+        private boolean clear() {
+            File[] files = cacheDir.listFiles(new FilenameFilter() {
+                @Override
+                public boolean accept(File dir, String name) {
+                    return name.startsWith(CACHE_PREFIX);
+                }
+            });
+            if (files == null || files.length <= 0) return true;
+            boolean flag = true;
+            for (File file : files) {
+                if (!file.delete()) {
+                    flag = false;
+                    continue;
+                }
+                cacheSize.addAndGet(-file.length());
+                cacheCount.addAndGet(-1);
+                lastUsageDates.remove(file);
+            }
+            if (flag) {
+                lastUsageDates.clear();
+                cacheSize.set(0);
+                cacheCount.set(0);
+            }
+            return flag;
+        }
+
+        /**
+         * Remove the oldest files.
+         *
+         * @return the size of oldest files, in bytes
+         */
+        private long removeOldest() {
+            if (lastUsageDates.isEmpty()) return 0;
+            Long oldestUsage = Long.MAX_VALUE;
+            File oldestFile = null;
+            Set<Map.Entry<File, Long>> entries = lastUsageDates.entrySet();
+            synchronized (lastUsageDates) {
+                for (Map.Entry<File, Long> entry : entries) {
+                    Long lastValueUsage = entry.getValue();
+                    if (lastValueUsage < oldestUsage) {
+                        oldestUsage = lastValueUsage;
+                        oldestFile = entry.getKey();
+                    }
+                }
+            }
+            if (oldestFile == null) return 0;
+            long fileSize = oldestFile.length();
+            if (oldestFile.delete()) {
+                lastUsageDates.remove(oldestFile);
+                return fileSize;
+            }
+            return 0;
+        }
+    }
+
+    private static final class DiskCacheHelper {
+
+        static final int TIME_INFO_LEN = 14;
+
+        private static byte[] newByteArrayWithTime(final int second, final byte[] data) {
+            byte[] time = createDueTime(second).getBytes();
+            byte[] content = new byte[time.length + data.length];
+            System.arraycopy(time, 0, content, 0, time.length);
+            System.arraycopy(data, 0, content, time.length, data.length);
+            return content;
+        }
+
+        /**
+         * Return the string of due time.
+         *
+         * @param seconds The seconds.
+         * @return the string of due time
+         */
+        private static String createDueTime(final int seconds) {
+            return String.format(
+                    Locale.getDefault(), "_$%010d$_",
+                    System.currentTimeMillis() / 1000 + seconds
+            );
+        }
+
+        private static boolean isDue(final byte[] data) {
+            long millis = getDueTime(data);
+            return millis != -1 && System.currentTimeMillis() > millis;
+        }
+
+        private static long getDueTime(final byte[] data) {
+            if (hasTimeInfo(data)) {
+                String millis = new String(copyOfRange(data, 2, 12));
+                try {
+                    return Long.parseLong(millis) * 1000;
+                } catch (NumberFormatException e) {
+                    return -1;
+                }
+            }
+            return -1;
+        }
+
+        private static byte[] getDataWithoutDueTime(final byte[] data) {
+            if (hasTimeInfo(data)) {
+                return copyOfRange(data, TIME_INFO_LEN, data.length);
+            }
+            return data;
+        }
+
+        private static byte[] copyOfRange(final byte[] original, final int from, final int to) {
+            int newLength = to - from;
+            if (newLength < 0) throw new IllegalArgumentException(from + " > " + to);
+            byte[] copy = new byte[newLength];
+            System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+            return copy;
+        }
+
+        private static boolean hasTimeInfo(final byte[] data) {
+            return data != null
+                    && data.length >= TIME_INFO_LEN
+                    && data[0] == '_'
+                    && data[1] == '$'
+                    && data[12] == '$'
+                    && data[13] == '_';
+        }
+    }
+}

+ 969 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDoubleStaticUtils.java

@@ -0,0 +1,969 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/01/04
+ *     desc  : utils about double cache
+ * </pre>
+ */
+public final class CacheDoubleStaticUtils {
+
+    private static CacheDoubleUtils sDefaultCacheDoubleUtils;
+
+    /**
+     * Set the default instance of {@link CacheDoubleUtils}.
+     *
+     * @param cacheDoubleUtils The default instance of {@link CacheDoubleUtils}.
+     */
+    public static void setDefaultCacheDoubleUtils(final CacheDoubleUtils cacheDoubleUtils) {
+        sDefaultCacheDoubleUtils = cacheDoubleUtils;
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final byte[] value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, byte[] value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key The key of cache.
+     * @return the bytes if cache exists or null otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key) {
+        return getBytes(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bytes if cache exists or defaultValue otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {
+        return getBytes(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about String
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final String value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final String value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key The key of cache.
+     * @return the string value if cache exists or null otherwise
+     */
+    public static String getString(@NonNull final String key) {
+        return getString(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the string value if cache exists or defaultValue otherwise
+     */
+    public static String getString(@NonNull final String key, final String defaultValue) {
+        return getString(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONObject
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final JSONObject value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key,
+                           final JSONObject value,
+                           final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONObject if cache exists or null otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key) {
+        return getJSONObject(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONObject if cache exists or defaultValue otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {
+        return getJSONObject(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final JSONArray value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final JSONArray value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONArray if cache exists or null otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key) {
+        return getJSONArray(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONArray if cache exists or defaultValue otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {
+        return getJSONArray(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Bitmap cache
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final Bitmap value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final Bitmap value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key) {
+        return getBitmap(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {
+        return getBitmap(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Drawable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final Drawable value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final Drawable value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key The key of cache.
+     * @return the drawable if cache exists or null otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key) {
+        return getDrawable(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the drawable if cache exists or defaultValue otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {
+        return getDrawable(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Parcelable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final Parcelable value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final Parcelable value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key     The key of cache.
+     * @param creator The creator.
+     * @param <T>     The value type.
+     * @return the parcelable if cache exists or null otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator) {
+        return getParcelable(key, creator, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key          The key of cache.
+     * @param creator      The creator.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @param <T>          The value type.
+     * @return the parcelable if cache exists or defaultValue otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator,
+                                      final T defaultValue) {
+        return getParcelable(key, creator, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Serializable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final Serializable value) {
+        put(key, value, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final Serializable value, final int saveTime) {
+        put(key, value, saveTime, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Object getSerializable(@NonNull final String key) {
+        return getSerializable(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Object getSerializable(@NonNull final String key, final Object defaultValue) {
+        return getSerializable(key, defaultValue, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the size of cache in disk.
+     *
+     * @return the size of cache in disk
+     */
+    public static long getCacheDiskSize() {
+        return getCacheDiskSize(getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the count of cache in disk.
+     *
+     * @return the count of cache in disk
+     */
+    public static int getCacheDiskCount() {
+        return getCacheDiskCount(getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Return the count of cache in memory.
+     *
+     * @return the count of cache in memory.
+     */
+    public static int getCacheMemoryCount() {
+        return getCacheMemoryCount(getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key The key of cache.
+     */
+    public static void remove(@NonNull String key) {
+        remove(key, getDefaultCacheDoubleUtils());
+    }
+
+    /**
+     * Clear all of the cache.
+     */
+    public static void clear() {
+        clear(getDefaultCacheDoubleUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // dividing line
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final byte[] value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final byte[] value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the bytes if cache exists or null otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getBytes(key);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the bytes if cache exists or defaultValue otherwise
+     */
+    public static byte[] getBytes(@NonNull final String key,
+                                  final byte[] defaultValue,
+                                  @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getBytes(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about String
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final String value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final String value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the string value if cache exists or null otherwise
+     */
+    public static String getString(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getString(key);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the string value if cache exists or defaultValue otherwise
+     */
+    public static String getString(@NonNull final String key,
+                                   final String defaultValue,
+                                   @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getString(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONObject
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final JSONObject value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final JSONObject value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the JSONObject if cache exists or null otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key,
+                                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getJSONObject(key);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the JSONObject if cache exists or defaultValue otherwise
+     */
+    public static JSONObject getJSONObject(@NonNull final String key,
+                                           final JSONObject defaultValue,
+                                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getJSONObject(key, defaultValue);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final JSONArray value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final JSONArray value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the JSONArray if cache exists or null otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getJSONArray(key);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the JSONArray if cache exists or defaultValue otherwise
+     */
+    public static JSONArray getJSONArray(@NonNull final String key,
+                                         final JSONArray defaultValue,
+                                         @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getJSONArray(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Bitmap cache
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Bitmap value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Bitmap value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getBitmap(key);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Bitmap getBitmap(@NonNull final String key,
+                                   final Bitmap defaultValue,
+                                   @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getBitmap(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Drawable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Drawable value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Drawable value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the drawable if cache exists or null otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getDrawable(key);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the drawable if cache exists or defaultValue otherwise
+     */
+    public static Drawable getDrawable(@NonNull final String key,
+                                       final Drawable defaultValue,
+                                       @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getDrawable(key, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Parcelable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Parcelable value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Parcelable value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key              The key of cache.
+     * @param creator          The creator.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @param <T>              The value type.
+     * @return the parcelable if cache exists or null otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator,
+                                      @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getParcelable(key, creator);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key              The key of cache.
+     * @param creator          The creator.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @param <T>              The value type.
+     * @return the parcelable if cache exists or defaultValue otherwise
+     */
+    public static <T> T getParcelable(@NonNull final String key,
+                                      @NonNull final Parcelable.Creator<T> creator,
+                                      final T defaultValue,
+                                      @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getParcelable(key, creator, defaultValue);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Serializable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Serializable value,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value);
+    }
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Serializable value,
+                           final int saveTime,
+                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public static Object getSerializable(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getSerializable(key);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public static Object getSerializable(@NonNull final String key,
+                                         final Object defaultValue,
+                                         @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getSerializable(key, defaultValue);
+    }
+
+    /**
+     * Return the size of cache in disk.
+     *
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the size of cache in disk
+     */
+    public static long getCacheDiskSize(@NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getCacheDiskSize();
+    }
+
+    /**
+     * Return the count of cache in disk.
+     *
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the count of cache in disk
+     */
+    public static int getCacheDiskCount(@NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getCacheDiskCount();
+    }
+
+    /**
+     * Return the count of cache in memory.
+     *
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     * @return the count of cache in memory.
+     */
+    public static int getCacheMemoryCount(@NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        return cacheDoubleUtils.getCacheMemoryCount();
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key              The key of cache.
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void remove(@NonNull String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.remove(key);
+    }
+
+    /**
+     * Clear all of the cache.
+     *
+     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.
+     */
+    public static void clear(@NonNull final CacheDoubleUtils cacheDoubleUtils) {
+        cacheDoubleUtils.clear();
+    }
+
+    private static CacheDoubleUtils getDefaultCacheDoubleUtils() {
+        return sDefaultCacheDoubleUtils != null ? sDefaultCacheDoubleUtils : CacheDoubleUtils.getInstance();
+    }
+}

+ 556 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheDoubleUtils.java

@@ -0,0 +1,556 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import androidx.annotation.NonNull;
+
+import com.cooleshow.base.constanst.CacheConstants;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/06/13
+ *     desc  : utils about double cache
+ * </pre>
+ */
+public final class CacheDoubleUtils implements CacheConstants {
+
+    private static final Map<String, CacheDoubleUtils> CACHE_MAP = new HashMap<>();
+
+    private final CacheMemoryUtils mCacheMemoryUtils;
+    private final CacheDiskUtils   mCacheDiskUtils;
+
+    /**
+     * Return the single {@link CacheDoubleUtils} instance.
+     *
+     * @return the single {@link CacheDoubleUtils} instance
+     */
+    public static CacheDoubleUtils getInstance() {
+        return getInstance(CacheMemoryUtils.getInstance(), CacheDiskUtils.getInstance());
+    }
+
+    /**
+     * Return the single {@link CacheDoubleUtils} instance.
+     *
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     * @param cacheDiskUtils   The instance of {@link CacheDiskUtils}.
+     * @return the single {@link CacheDoubleUtils} instance
+     */
+    public static CacheDoubleUtils getInstance(@NonNull final CacheMemoryUtils cacheMemoryUtils,
+                                               @NonNull final CacheDiskUtils cacheDiskUtils) {
+        final String cacheKey = cacheDiskUtils.toString() + "_" + cacheMemoryUtils.toString();
+        CacheDoubleUtils cache = CACHE_MAP.get(cacheKey);
+        if (cache == null) {
+            synchronized (CacheDoubleUtils.class) {
+                cache = CACHE_MAP.get(cacheKey);
+                if (cache == null) {
+                    cache = new CacheDoubleUtils(cacheMemoryUtils, cacheDiskUtils);
+                    CACHE_MAP.put(cacheKey, cache);
+                }
+            }
+        }
+        return cache;
+    }
+
+    private CacheDoubleUtils(CacheMemoryUtils cacheMemoryUtils, CacheDiskUtils cacheUtils) {
+        mCacheMemoryUtils = cacheMemoryUtils;
+        mCacheDiskUtils = cacheUtils;
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about bytes
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final byte[] value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, byte[] value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key The key of cache.
+     * @return the bytes if cache exists or null otherwise
+     */
+    public byte[] getBytes(@NonNull final String key) {
+        return getBytes(key, null);
+    }
+
+    /**
+     * Return the bytes in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bytes if cache exists or defaultValue otherwise
+     */
+    public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {
+        byte[] obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        byte[] bytes = mCacheDiskUtils.getBytes(key);
+        if (bytes != null) {
+            mCacheMemoryUtils.put(key, bytes);
+            return bytes;
+        }
+        return defaultValue;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about String
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final String value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put string value in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final String value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key The key of cache.
+     * @return the string value if cache exists or null otherwise
+     */
+    public String getString(@NonNull final String key) {
+        return getString(key, null);
+    }
+
+    /**
+     * Return the string value in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the string value if cache exists or defaultValue otherwise
+     */
+    public String getString(@NonNull final String key, final String defaultValue) {
+        String obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        String string = mCacheDiskUtils.getString(key);
+        if (string != null) {
+            mCacheMemoryUtils.put(key, string);
+            return string;
+        }
+        return defaultValue;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONObject
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final JSONObject value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put JSONObject in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key,
+                    final JSONObject value,
+                    final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONObject if cache exists or null otherwise
+     */
+    public JSONObject getJSONObject(@NonNull final String key) {
+        return getJSONObject(key, null);
+    }
+
+    /**
+     * Return the JSONObject in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONObject if cache exists or defaultValue otherwise
+     */
+    public JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {
+        JSONObject obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        JSONObject jsonObject = mCacheDiskUtils.getJSONObject(key);
+        if (jsonObject != null) {
+            mCacheMemoryUtils.put(key, jsonObject);
+            return jsonObject;
+        }
+        return defaultValue;
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about JSONArray
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final JSONArray value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put JSONArray in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final JSONArray value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key The key of cache.
+     * @return the JSONArray if cache exists or null otherwise
+     */
+    public JSONArray getJSONArray(@NonNull final String key) {
+        return getJSONArray(key, null);
+    }
+
+    /**
+     * Return the JSONArray in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the JSONArray if cache exists or defaultValue otherwise
+     */
+    public JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {
+        JSONArray obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        JSONArray jsonArray = mCacheDiskUtils.getJSONArray(key);
+        if (jsonArray != null) {
+            mCacheMemoryUtils.put(key, jsonArray);
+            return jsonArray;
+        }
+        return defaultValue;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Bitmap cache
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Bitmap value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put bitmap in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Bitmap value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public Bitmap getBitmap(@NonNull final String key) {
+        return getBitmap(key, null);
+    }
+
+    /**
+     * Return the bitmap in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {
+        Bitmap obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        Bitmap bitmap = mCacheDiskUtils.getBitmap(key);
+        if (bitmap != null) {
+            mCacheMemoryUtils.put(key, bitmap);
+            return bitmap;
+        }
+        return defaultValue;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Drawable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Drawable value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put drawable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Drawable value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key The key of cache.
+     * @return the drawable if cache exists or null otherwise
+     */
+    public Drawable getDrawable(@NonNull final String key) {
+        return getDrawable(key, null);
+    }
+
+    /**
+     * Return the drawable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the drawable if cache exists or defaultValue otherwise
+     */
+    public Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {
+        Drawable obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        Drawable drawable = mCacheDiskUtils.getDrawable(key);
+        if (drawable != null) {
+            mCacheMemoryUtils.put(key, drawable);
+            return drawable;
+        }
+        return defaultValue;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Parcelable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Parcelable value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put parcelable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Parcelable value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key     The key of cache.
+     * @param creator The creator.
+     * @param <T>     The value type.
+     * @return the parcelable if cache exists or null otherwise
+     */
+    public <T> T getParcelable(@NonNull final String key,
+                               @NonNull final Parcelable.Creator<T> creator) {
+        return getParcelable(key, creator, null);
+    }
+
+    /**
+     * Return the parcelable in cache.
+     *
+     * @param key          The key of cache.
+     * @param creator      The creator.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @param <T>          The value type.
+     * @return the parcelable if cache exists or defaultValue otherwise
+     */
+    public <T> T getParcelable(@NonNull final String key,
+                               @NonNull final Parcelable.Creator<T> creator,
+                               final T defaultValue) {
+        T value = mCacheMemoryUtils.get(key);
+        if (value != null) return value;
+        T val = mCacheDiskUtils.getParcelable(key, creator);
+        if (val != null) {
+            mCacheMemoryUtils.put(key, val);
+            return val;
+        }
+        return defaultValue;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about Serializable
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Serializable value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put serializable in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Serializable value, final int saveTime) {
+        mCacheMemoryUtils.put(key, value, saveTime);
+        mCacheDiskUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key The key of cache.
+     * @return the bitmap if cache exists or null otherwise
+     */
+    public Object getSerializable(@NonNull final String key) {
+        return getSerializable(key, null);
+    }
+
+    /**
+     * Return the serializable in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @return the bitmap if cache exists or defaultValue otherwise
+     */
+    public Object getSerializable(@NonNull final String key, final Object defaultValue) {
+        Object obj = mCacheMemoryUtils.get(key);
+        if (obj != null) return obj;
+        Object serializable = mCacheDiskUtils.getSerializable(key);
+        if (serializable != null) {
+            mCacheMemoryUtils.put(key, serializable);
+            return serializable;
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Return the size of cache in disk.
+     *
+     * @return the size of cache in disk
+     */
+    public long getCacheDiskSize() {
+        return mCacheDiskUtils.getCacheSize();
+    }
+
+    /**
+     * Return the count of cache in disk.
+     *
+     * @return the count of cache in disk
+     */
+    public int getCacheDiskCount() {
+        return mCacheDiskUtils.getCacheCount();
+    }
+
+    /**
+     * Return the count of cache in memory.
+     *
+     * @return the count of cache in memory.
+     */
+    public int getCacheMemoryCount() {
+        return mCacheMemoryUtils.getCacheCount();
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key The key of cache.
+     */
+    public void remove(@NonNull String key) {
+        mCacheMemoryUtils.remove(key);
+        mCacheDiskUtils.remove(key);
+    }
+
+    /**
+     * Clear all of the cache.
+     */
+    public void clear() {
+        mCacheMemoryUtils.clear();
+        mCacheDiskUtils.clear();
+    }
+}

+ 188 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheMemoryStaticUtils.java

@@ -0,0 +1,188 @@
+package com.cooleshow.base.utils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/01/04
+ *     desc  : utils about memory cache
+ * </pre>
+ */
+public final class CacheMemoryStaticUtils {
+
+    private static CacheMemoryUtils sDefaultCacheMemoryUtils;
+
+    /**
+     * Set the default instance of {@link CacheMemoryUtils}.
+     *
+     * @param cacheMemoryUtils The default instance of {@link CacheMemoryUtils}.
+     */
+    public static void setDefaultCacheMemoryUtils(final CacheMemoryUtils cacheMemoryUtils) {
+        sDefaultCacheMemoryUtils = cacheMemoryUtils;
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public static void put(@NonNull final String key, final Object value) {
+        put(key, value, getDefaultCacheMemoryUtils());
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public static void put(@NonNull final String key, final Object value, int saveTime) {
+        put(key, value, saveTime, getDefaultCacheMemoryUtils());
+    }
+
+    /**
+     * Return the value in cache.
+     *
+     * @param key The key of cache.
+     * @param <T> The value type.
+     * @return the value if cache exists or null otherwise
+     */
+    public static <T> T get(@NonNull final String key) {
+        return get(key, getDefaultCacheMemoryUtils());
+    }
+
+    /**
+     * Return the value in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @param <T>          The value type.
+     * @return the value if cache exists or defaultValue otherwise
+     */
+    public static <T> T get(@NonNull final String key, final T defaultValue) {
+        return get(key, defaultValue, getDefaultCacheMemoryUtils());
+    }
+
+    /**
+     * Return the count of cache.
+     *
+     * @return the count of cache
+     */
+    public static int getCacheCount() {
+        return getCacheCount(getDefaultCacheMemoryUtils());
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key The key of cache.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static Object remove(@NonNull final String key) {
+        return remove(key, getDefaultCacheMemoryUtils());
+    }
+
+    /**
+     * Clear all of the cache.
+     */
+    public static void clear() {
+        clear(getDefaultCacheMemoryUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // dividing line
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Object value,
+                           @NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        cacheMemoryUtils.put(key, value);
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key              The key of cache.
+     * @param value            The value of cache.
+     * @param saveTime         The save time of cache, in seconds.
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Object value,
+                           int saveTime,
+                           @NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        cacheMemoryUtils.put(key, value, saveTime);
+    }
+
+    /**
+     * Return the value in cache.
+     *
+     * @param key              The key of cache.
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     * @param <T>              The value type.
+     * @return the value if cache exists or null otherwise
+     */
+    public static <T> T get(@NonNull final String key, @NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        return cacheMemoryUtils.get(key);
+    }
+
+    /**
+     * Return the value in cache.
+     *
+     * @param key              The key of cache.
+     * @param defaultValue     The default value if the cache doesn't exist.
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     * @param <T>              The value type.
+     * @return the value if cache exists or defaultValue otherwise
+     */
+    public static <T> T get(@NonNull final String key,
+                            final T defaultValue,
+                            @NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        return cacheMemoryUtils.get(key, defaultValue);
+    }
+
+    /**
+     * Return the count of cache.
+     *
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     * @return the count of cache
+     */
+    public static int getCacheCount(@NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        return cacheMemoryUtils.getCacheCount();
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key              The key of cache.
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static Object remove(@NonNull final String key, @NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        return cacheMemoryUtils.remove(key);
+    }
+
+    /**
+     * Clear all of the cache.
+     *
+     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.
+     */
+    public static void clear(@NonNull final CacheMemoryUtils cacheMemoryUtils) {
+        cacheMemoryUtils.clear();
+    }
+
+    private static CacheMemoryUtils getDefaultCacheMemoryUtils() {
+        return sDefaultCacheMemoryUtils != null ? sDefaultCacheMemoryUtils : CacheMemoryUtils.getInstance();
+    }
+}

+ 168 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CacheMemoryUtils.java

@@ -0,0 +1,168 @@
+package com.cooleshow.base.utils;
+
+import androidx.annotation.NonNull;
+import androidx.collection.LruCache;
+
+import com.cooleshow.base.constanst.CacheConstants;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/05/24
+ *     desc  : utils about memory cache
+ * </pre>
+ */
+public final class CacheMemoryUtils implements CacheConstants {
+
+    private static final int DEFAULT_MAX_COUNT = 256;
+
+    private static final Map<String, CacheMemoryUtils> CACHE_MAP = new HashMap<>();
+
+    private final String                       mCacheKey;
+    private final LruCache<String, CacheValue> mMemoryCache;
+
+    /**
+     * Return the single {@link CacheMemoryUtils} instance.
+     *
+     * @return the single {@link CacheMemoryUtils} instance
+     */
+    public static CacheMemoryUtils getInstance() {
+        return getInstance(DEFAULT_MAX_COUNT);
+    }
+
+    /**
+     * Return the single {@link CacheMemoryUtils} instance.
+     *
+     * @param maxCount The max count of cache.
+     * @return the single {@link CacheMemoryUtils} instance
+     */
+    public static CacheMemoryUtils getInstance(final int maxCount) {
+        return getInstance(String.valueOf(maxCount), maxCount);
+    }
+
+    /**
+     * Return the single {@link CacheMemoryUtils} instance.
+     *
+     * @param cacheKey The key of cache.
+     * @param maxCount The max count of cache.
+     * @return the single {@link CacheMemoryUtils} instance
+     */
+    public static CacheMemoryUtils getInstance(final String cacheKey, final int maxCount) {
+        CacheMemoryUtils cache = CACHE_MAP.get(cacheKey);
+        if (cache == null) {
+            synchronized (CacheMemoryUtils.class) {
+                cache = CACHE_MAP.get(cacheKey);
+                if (cache == null) {
+                    cache = new CacheMemoryUtils(cacheKey, new LruCache<String, CacheValue>(maxCount));
+                    CACHE_MAP.put(cacheKey, cache);
+                }
+            }
+        }
+        return cache;
+    }
+
+    private CacheMemoryUtils(String cacheKey, LruCache<String, CacheValue> memoryCache) {
+        mCacheKey = cacheKey;
+        mMemoryCache = memoryCache;
+    }
+
+    @Override
+    public String toString() {
+        return mCacheKey + "@" + Integer.toHexString(hashCode());
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key   The key of cache.
+     * @param value The value of cache.
+     */
+    public void put(@NonNull final String key, final Object value) {
+        put(key, value, -1);
+    }
+
+    /**
+     * Put bytes in cache.
+     *
+     * @param key      The key of cache.
+     * @param value    The value of cache.
+     * @param saveTime The save time of cache, in seconds.
+     */
+    public void put(@NonNull final String key, final Object value, int saveTime) {
+        if (value == null) return;
+        long dueTime = saveTime < 0 ? -1 : System.currentTimeMillis() + saveTime * 1000;
+        mMemoryCache.put(key, new CacheValue(dueTime, value));
+    }
+
+    /**
+     * Return the value in cache.
+     *
+     * @param key The key of cache.
+     * @param <T> The value type.
+     * @return the value if cache exists or null otherwise
+     */
+    public <T> T get(@NonNull final String key) {
+        return get(key, null);
+    }
+
+    /**
+     * Return the value in cache.
+     *
+     * @param key          The key of cache.
+     * @param defaultValue The default value if the cache doesn't exist.
+     * @param <T>          The value type.
+     * @return the value if cache exists or defaultValue otherwise
+     */
+    public <T> T get(@NonNull final String key, final T defaultValue) {
+        CacheValue val = mMemoryCache.get(key);
+        if (val == null) return defaultValue;
+        if (val.dueTime == -1 || val.dueTime >= System.currentTimeMillis()) {
+            //noinspection unchecked
+            return (T) val.value;
+        }
+        mMemoryCache.remove(key);
+        return defaultValue;
+    }
+
+    /**
+     * Return the count of cache.
+     *
+     * @return the count of cache
+     */
+    public int getCacheCount() {
+        return mMemoryCache.size();
+    }
+
+    /**
+     * Remove the cache by key.
+     *
+     * @param key The key of cache.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public Object remove(@NonNull final String key) {
+        CacheValue remove = mMemoryCache.remove(key);
+        if (remove == null) return null;
+        return remove.value;
+    }
+
+    /**
+     * Clear all of the cache.
+     */
+    public void clear() {
+        mMemoryCache.evictAll();
+    }
+
+    private static final class CacheValue {
+        long   dueTime;
+        Object value;
+
+        CacheValue(long dueTime, Object value) {
+            this.dueTime = dueTime;
+            this.value = value;
+        }
+    }
+}

+ 104 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CleanUtils.java

@@ -0,0 +1,104 @@
+package com.cooleshow.base.utils;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+
+import java.io.File;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/27
+ *     desc  : utils about clean
+ * </pre>
+ */
+public final class CleanUtils {
+
+    private CleanUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Clean the internal cache.
+     * <p>directory: /data/data/package/cache</p>
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanInternalCache() {
+        return UtilsBridge.deleteAllInDir(Utils.getApp().getCacheDir());
+    }
+
+    /**
+     * Clean the internal files.
+     * <p>directory: /data/data/package/files</p>
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanInternalFiles() {
+        return UtilsBridge.deleteAllInDir(Utils.getApp().getFilesDir());
+    }
+
+    /**
+     * Clean the internal databases.
+     * <p>directory: /data/data/package/databases</p>
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanInternalDbs() {
+        return UtilsBridge.deleteAllInDir(new File(Utils.getApp().getFilesDir().getParent(), "databases"));
+    }
+
+    /**
+     * Clean the internal database by name.
+     * <p>directory: /data/data/package/databases/dbName</p>
+     *
+     * @param dbName The name of database.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanInternalDbByName(final String dbName) {
+        return Utils.getApp().deleteDatabase(dbName);
+    }
+
+    /**
+     * Clean the internal shared preferences.
+     * <p>directory: /data/data/package/shared_prefs</p>
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanInternalSp() {
+        return UtilsBridge.deleteAllInDir(new File(Utils.getApp().getFilesDir().getParent(), "shared_prefs"));
+    }
+
+    /**
+     * Clean the external cache.
+     * <p>directory: /storage/emulated/0/android/data/package/cache</p>
+     *
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanExternalCache() {
+        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+                && UtilsBridge.deleteAllInDir(Utils.getApp().getExternalCacheDir());
+    }
+
+    /**
+     * Clean the custom directory.
+     *
+     * @param dirPath The path of directory.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean cleanCustomDir(final String dirPath) {
+        return UtilsBridge.deleteAllInDir(UtilsBridge.getFileByPath(dirPath));
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    public static void cleanAppUserData() {
+        ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        //noinspection ConstantConditions
+        am.clearApplicationUserData();
+    }
+}

+ 621 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ClickUtils.java

@@ -0,0 +1,621 @@
+package com.cooleshow.base.utils;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.StateSet;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
+import android.view.View;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/06/12
+ *     desc  : utils about click
+ * </pre>
+ */
+public class ClickUtils {
+
+    private static final int   PRESSED_VIEW_SCALE_TAG           = -1;
+    private static final float PRESSED_VIEW_SCALE_DEFAULT_VALUE = -0.06f;
+
+    private static final int   PRESSED_VIEW_ALPHA_TAG           = -2;
+    private static final int   PRESSED_VIEW_ALPHA_SRC_TAG       = -3;
+    private static final float PRESSED_VIEW_ALPHA_DEFAULT_VALUE = 0.8f;
+
+    private static final int   PRESSED_BG_ALPHA_STYLE         = 4;
+    private static final float PRESSED_BG_ALPHA_DEFAULT_VALUE = 0.9f;
+
+    private static final int   PRESSED_BG_DARK_STYLE         = 5;
+    private static final float PRESSED_BG_DARK_DEFAULT_VALUE = 0.9f;
+
+    private static final long DEBOUNCING_DEFAULT_VALUE = 1000;
+
+    private ClickUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Apply scale animation for the views' click.
+     *
+     * @param views The views.
+     */
+    public static void applyPressedViewScale(final View... views) {
+        applyPressedViewScale(views, null);
+    }
+
+    /**
+     * Apply scale animation for the views' click.
+     *
+     * @param views        The views.
+     * @param scaleFactors The factors of scale for the views.
+     */
+    public static void applyPressedViewScale(final View[] views, final float[] scaleFactors) {
+        if (views == null || views.length == 0) {
+            return;
+        }
+        for (int i = 0; i < views.length; i++) {
+            if (scaleFactors == null || i >= scaleFactors.length) {
+                applyPressedViewScale(views[i], PRESSED_VIEW_SCALE_DEFAULT_VALUE);
+            } else {
+                applyPressedViewScale(views[i], scaleFactors[i]);
+            }
+        }
+    }
+
+    /**
+     * Apply scale animation for the views' click.
+     *
+     * @param view        The view.
+     * @param scaleFactor The factor of scale for the view.
+     */
+    public static void applyPressedViewScale(final View view, final float scaleFactor) {
+        if (view == null) {
+            return;
+        }
+        view.setTag(PRESSED_VIEW_SCALE_TAG, scaleFactor);
+        view.setClickable(true);
+        view.setOnTouchListener(OnUtilsTouchListener.getInstance());
+    }
+
+    /**
+     * Apply alpha for the views' click.
+     *
+     * @param views The views.
+     */
+    public static void applyPressedViewAlpha(final View... views) {
+        applyPressedViewAlpha(views, null);
+    }
+
+    /**
+     * Apply alpha for the views' click.
+     *
+     * @param views  The views.
+     * @param alphas The alphas for the views.
+     */
+    public static void applyPressedViewAlpha(final View[] views, final float[] alphas) {
+        if (views == null || views.length == 0) return;
+        for (int i = 0; i < views.length; i++) {
+            if (alphas == null || i >= alphas.length) {
+                applyPressedViewAlpha(views[i], PRESSED_VIEW_ALPHA_DEFAULT_VALUE);
+            } else {
+                applyPressedViewAlpha(views[i], alphas[i]);
+            }
+        }
+    }
+
+
+    /**
+     * Apply scale animation for the views' click.
+     *
+     * @param view  The view.
+     * @param alpha The alpha for the view.
+     */
+    public static void applyPressedViewAlpha(final View view, final float alpha) {
+        if (view == null) {
+            return;
+        }
+        view.setTag(PRESSED_VIEW_ALPHA_TAG, alpha);
+        view.setTag(PRESSED_VIEW_ALPHA_SRC_TAG, view.getAlpha());
+        view.setClickable(true);
+        view.setOnTouchListener(OnUtilsTouchListener.getInstance());
+    }
+
+    /**
+     * Apply alpha for the view's background.
+     *
+     * @param view The views.
+     */
+    public static void applyPressedBgAlpha(View view) {
+        applyPressedBgAlpha(view, PRESSED_BG_ALPHA_DEFAULT_VALUE);
+    }
+
+    /**
+     * Apply alpha for the view's background.
+     *
+     * @param view  The views.
+     * @param alpha The alpha.
+     */
+    public static void applyPressedBgAlpha(View view, float alpha) {
+        applyPressedBgStyle(view, PRESSED_BG_ALPHA_STYLE, alpha);
+    }
+
+    /**
+     * Apply alpha of dark for the view's background.
+     *
+     * @param view The views.
+     */
+    public static void applyPressedBgDark(View view) {
+        applyPressedBgDark(view, PRESSED_BG_DARK_DEFAULT_VALUE);
+    }
+
+    /**
+     * Apply alpha of dark for the view's background.
+     *
+     * @param view      The views.
+     * @param darkAlpha The alpha of dark.
+     */
+    public static void applyPressedBgDark(View view, float darkAlpha) {
+        applyPressedBgStyle(view, PRESSED_BG_DARK_STYLE, darkAlpha);
+    }
+
+    private static void applyPressedBgStyle(View view, int style, float value) {
+        if (view == null) return;
+        Drawable background = view.getBackground();
+        Object tag = view.getTag(-style);
+        if (tag instanceof Drawable) {
+            ViewCompat.setBackground(view, (Drawable) tag);
+        } else {
+            background = createStyleDrawable(background, style, value);
+            ViewCompat.setBackground(view, background);
+            view.setTag(-style, background);
+        }
+    }
+
+    private static Drawable createStyleDrawable(Drawable src, int style, float value) {
+        if (src == null) {
+            src = new ColorDrawable(0);
+        }
+        if (src.getConstantState() == null) return src;
+
+        Drawable pressed = src.getConstantState().newDrawable().mutate();
+        if (style == PRESSED_BG_ALPHA_STYLE) {
+            pressed = createAlphaDrawable(pressed, value);
+        } else if (style == PRESSED_BG_DARK_STYLE) {
+            pressed = createDarkDrawable(pressed, value);
+        }
+
+        Drawable disable = src.getConstantState().newDrawable().mutate();
+        disable = createAlphaDrawable(disable, 0.5f);
+
+        StateListDrawable drawable = new StateListDrawable();
+        drawable.addState(new int[]{android.R.attr.state_pressed}, pressed);
+        drawable.addState(new int[]{-android.R.attr.state_enabled}, disable);
+        drawable.addState(StateSet.WILD_CARD, src);
+        return drawable;
+    }
+
+    private static Drawable createAlphaDrawable(Drawable drawable, float alpha) {
+        ClickDrawableWrapper drawableWrapper = new ClickDrawableWrapper(drawable);
+        drawableWrapper.setAlpha((int) (alpha * 255));
+        return drawableWrapper;
+    }
+
+    private static Drawable createDarkDrawable(Drawable drawable, float alpha) {
+        ClickDrawableWrapper drawableWrapper = new ClickDrawableWrapper(drawable);
+        drawableWrapper.setColorFilter(getDarkColorFilter(alpha));
+        return drawableWrapper;
+    }
+
+    private static ColorMatrixColorFilter getDarkColorFilter(float darkAlpha) {
+        return new ColorMatrixColorFilter(new ColorMatrix(new float[]{
+                darkAlpha, 0, 0, 0, 0,
+                0, darkAlpha, 0, 0, 0,
+                0, 0, darkAlpha, 0, 0,
+                0, 0, 0, 2, 0
+        }));
+    }
+
+    /**
+     * Apply single debouncing for the view's click.
+     *
+     * @param view     The view.
+     * @param listener The listener.
+     */
+    public static void applySingleDebouncing(final View view, final View.OnClickListener listener) {
+        applySingleDebouncing(new View[]{view}, listener);
+    }
+
+    /**
+     * Apply single debouncing for the view's click.
+     *
+     * @param view     The view.
+     * @param duration The duration of debouncing.
+     * @param listener The listener.
+     */
+    public static void applySingleDebouncing(final View view, @IntRange(from = 0) long duration,
+                                             final View.OnClickListener listener) {
+        applySingleDebouncing(new View[]{view}, duration, listener);
+    }
+
+    /**
+     * Apply single debouncing for the views' click.
+     *
+     * @param views    The views.
+     * @param listener The listener.
+     */
+    public static void applySingleDebouncing(final View[] views, final View.OnClickListener listener) {
+        applySingleDebouncing(views, DEBOUNCING_DEFAULT_VALUE, listener);
+    }
+
+    /**
+     * Apply single debouncing for the views' click.
+     *
+     * @param views    The views.
+     * @param duration The duration of debouncing.
+     * @param listener The listener.
+     */
+    public static void applySingleDebouncing(final View[] views,
+                                             @IntRange(from = 0) long duration,
+                                             final View.OnClickListener listener) {
+        applyDebouncing(views, false, duration, listener);
+    }
+
+    /**
+     * Apply global debouncing for the view's click.
+     *
+     * @param view     The view.
+     * @param listener The listener.
+     */
+    public static void applyGlobalDebouncing(final View view, final View.OnClickListener listener) {
+        applyGlobalDebouncing(new View[]{view}, listener);
+    }
+
+    /**
+     * Apply global debouncing for the view's click.
+     *
+     * @param view     The view.
+     * @param duration The duration of debouncing.
+     * @param listener The listener.
+     */
+    public static void applyGlobalDebouncing(final View view, @IntRange(from = 0) long duration,
+                                             final View.OnClickListener listener) {
+        applyGlobalDebouncing(new View[]{view}, duration, listener);
+    }
+
+
+    /**
+     * Apply global debouncing for the views' click.
+     *
+     * @param views    The views.
+     * @param listener The listener.
+     */
+    public static void applyGlobalDebouncing(final View[] views, final View.OnClickListener listener) {
+        applyGlobalDebouncing(views, DEBOUNCING_DEFAULT_VALUE, listener);
+    }
+
+    /**
+     * Apply global debouncing for the views' click.
+     *
+     * @param views    The views.
+     * @param duration The duration of debouncing.
+     * @param listener The listener.
+     */
+    public static void applyGlobalDebouncing(final View[] views,
+                                             @IntRange(from = 0) long duration,
+                                             final View.OnClickListener listener) {
+        applyDebouncing(views, true, duration, listener);
+    }
+
+    private static void applyDebouncing(final View[] views,
+                                        final boolean isGlobal,
+                                        @IntRange(from = 0) long duration,
+                                        final View.OnClickListener listener) {
+        if (views == null || views.length == 0 || listener == null) return;
+        for (View view : views) {
+            if (view == null) continue;
+            view.setOnClickListener(new OnDebouncingClickListener(isGlobal, duration) {
+                @Override
+                public void onDebouncingClick(View v) {
+                    listener.onClick(v);
+                }
+            });
+        }
+    }
+
+    /**
+     * Expand the click area of ​​the view
+     *
+     * @param view       The view.
+     * @param expandSize The size.
+     */
+    public static void expandClickArea(@NonNull final View view, final int expandSize) {
+        expandClickArea(view, expandSize, expandSize, expandSize, expandSize);
+    }
+
+    public static void expandClickArea(@NonNull final View view,
+                                       final int expandSizeTop,
+                                       final int expandSizeLeft,
+                                       final int expandSizeRight,
+                                       final int expandSizeBottom) {
+        final View parentView = (View) view.getParent();
+        if (parentView == null) {
+            Log.e("ClickUtils", "expandClickArea must have parent view.");
+            return;
+        }
+        parentView.post(new Runnable() {
+            @Override
+            public void run() {
+                final Rect rect = new Rect();
+                view.getHitRect(rect);
+                rect.top -= expandSizeTop;
+                rect.bottom += expandSizeBottom;
+                rect.left -= expandSizeLeft;
+                rect.right += expandSizeRight;
+                parentView.setTouchDelegate(new TouchDelegate(rect, view));
+            }
+        });
+    }
+
+    private static final long TIP_DURATION = 2000L;
+    private static       long sLastClickMillis;
+    private static       int  sClickCount;
+
+    public static void back2HomeFriendly(final CharSequence tip) {
+        back2HomeFriendly(tip, TIP_DURATION, Back2HomeFriendlyListener.DEFAULT);
+    }
+
+    public static void back2HomeFriendly(@NonNull final CharSequence tip,
+                                         final long duration,
+                                         @NonNull Back2HomeFriendlyListener listener) {
+        long nowMillis = SystemClock.elapsedRealtime();
+        if (Math.abs(nowMillis - sLastClickMillis) < duration) {
+            sClickCount++;
+            if (sClickCount == 2) {
+                UtilsBridge.startHomeActivity();
+                listener.dismiss();
+                sLastClickMillis = 0;
+            }
+        } else {
+            sClickCount = 1;
+            listener.show(tip, duration);
+            sLastClickMillis = nowMillis;
+        }
+    }
+
+    public interface Back2HomeFriendlyListener {
+        Back2HomeFriendlyListener DEFAULT = new Back2HomeFriendlyListener() {
+            @Override
+            public void show(CharSequence text, long duration) {
+                UtilsBridge.toastShowShort(text);
+            }
+
+            @Override
+            public void dismiss() {
+                UtilsBridge.toastCancel();
+            }
+        };
+
+        void show(CharSequence text, long duration);
+
+        void dismiss();
+    }
+
+    public static abstract class OnDebouncingClickListener implements View.OnClickListener {
+
+        private static boolean mEnabled = true;
+
+        private static final Runnable ENABLE_AGAIN = new Runnable() {
+            @Override
+            public void run() {
+                mEnabled = true;
+            }
+        };
+
+        private static boolean isValid(@NonNull final View view, final long duration) {
+            return UtilsBridge.isValid(view, duration);
+        }
+
+        private long    mDuration;
+        private boolean mIsGlobal;
+
+        public OnDebouncingClickListener() {
+            this(true, DEBOUNCING_DEFAULT_VALUE);
+        }
+
+        public OnDebouncingClickListener(final boolean isGlobal) {
+            this(isGlobal, DEBOUNCING_DEFAULT_VALUE);
+        }
+
+        public OnDebouncingClickListener(final long duration) {
+            this(true, duration);
+        }
+
+        public OnDebouncingClickListener(final boolean isGlobal, final long duration) {
+            mIsGlobal = isGlobal;
+            mDuration = duration;
+        }
+
+        public abstract void onDebouncingClick(View v);
+
+        @Override
+        public final void onClick(View v) {
+            if (mIsGlobal) {
+                if (mEnabled) {
+                    mEnabled = false;
+                    v.postDelayed(ENABLE_AGAIN, mDuration);
+                    onDebouncingClick(v);
+                }
+            } else {
+                if (isValid(v, mDuration)) {
+                    onDebouncingClick(v);
+                }
+            }
+        }
+    }
+
+    public static abstract class OnMultiClickListener implements View.OnClickListener {
+
+        private static final long INTERVAL_DEFAULT_VALUE = 666;
+
+        private final int  mTriggerClickCount;
+        private final long mClickInterval;
+
+        private long mLastClickTime;
+        private int  mClickCount;
+
+        public OnMultiClickListener(int triggerClickCount) {
+            this(triggerClickCount, INTERVAL_DEFAULT_VALUE);
+        }
+
+        public OnMultiClickListener(int triggerClickCount, long clickInterval) {
+            this.mTriggerClickCount = triggerClickCount;
+            this.mClickInterval = clickInterval;
+        }
+
+        public abstract void onTriggerClick(View v);
+
+        public abstract void onBeforeTriggerClick(View v, int count);
+
+        @Override
+        public void onClick(View v) {
+            if (mTriggerClickCount <= 1) {
+                onTriggerClick(v);
+                return;
+            }
+            long curTime = System.currentTimeMillis();
+
+            if (curTime - mLastClickTime < mClickInterval) {
+                mClickCount++;
+                if (mClickCount == mTriggerClickCount) {
+                    onTriggerClick(v);
+                } else if (mClickCount < mTriggerClickCount) {
+                    onBeforeTriggerClick(v, mClickCount);
+                } else {
+                    mClickCount = 1;
+                    onBeforeTriggerClick(v, mClickCount);
+                }
+            } else {
+                mClickCount = 1;
+                onBeforeTriggerClick(v, mClickCount);
+            }
+            mLastClickTime = curTime;
+        }
+    }
+
+    private static class OnUtilsTouchListener implements View.OnTouchListener {
+
+        public static OnUtilsTouchListener getInstance() {
+            return LazyHolder.INSTANCE;
+        }
+
+        private OnUtilsTouchListener() {/**/}
+
+        @Override
+        public boolean onTouch(final View v, MotionEvent event) {
+            int action = event.getAction();
+            if (action == MotionEvent.ACTION_DOWN) {
+                processScale(v, true);
+                processAlpha(v, true);
+            } else if (action == MotionEvent.ACTION_UP
+                    || action == MotionEvent.ACTION_CANCEL) {
+                processScale(v, false);
+                processAlpha(v, false);
+            }
+            return false;
+        }
+
+        private void processScale(final View view, boolean isDown) {
+            Object tag = view.getTag(PRESSED_VIEW_SCALE_TAG);
+            if (!(tag instanceof Float)) return;
+            float value = isDown ? 1 + (Float) tag : 1;
+            view.animate()
+                    .scaleX(value)
+                    .scaleY(value)
+                    .setDuration(200)
+                    .start();
+        }
+
+        private void processAlpha(final View view, boolean isDown) {
+            Object tag = view.getTag(isDown ? PRESSED_VIEW_ALPHA_TAG : PRESSED_VIEW_ALPHA_SRC_TAG);
+            if (!(tag instanceof Float)) return;
+            view.setAlpha((Float) tag);
+        }
+
+        private static class LazyHolder {
+            private static final OnUtilsTouchListener INSTANCE = new OnUtilsTouchListener();
+        }
+    }
+
+    static class ClickDrawableWrapper extends ShadowUtils.DrawableWrapper {
+
+        private BitmapDrawable mBitmapDrawable = null;
+
+        // 低版本ColorDrawable.setColorFilter无效,这里直接用画笔画上
+        private Paint mColorPaint = null;
+
+        public ClickDrawableWrapper(Drawable drawable) {
+            super(drawable);
+            if (drawable instanceof ColorDrawable) {
+                mColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+                mColorPaint.setColor(((ColorDrawable) drawable).getColor());
+            }
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter cf) {
+            super.setColorFilter(cf);
+            // 低版本 StateListDrawable.selectDrawable 会重置 ColorFilter
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                if (mColorPaint != null) {
+                    mColorPaint.setColorFilter(cf);
+                }
+            }
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            super.setAlpha(alpha);
+            // 低版本 StateListDrawable.selectDrawable 会重置 Alpha
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                if (mColorPaint != null) {
+                    mColorPaint.setColor(((ColorDrawable) getWrappedDrawable()).getColor());
+                }
+            }
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            if (mBitmapDrawable == null) {
+                Bitmap bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Bitmap.Config.ARGB_8888);
+                Canvas myCanvas = new Canvas(bitmap);
+                if (mColorPaint != null) {
+                    myCanvas.drawRect(getBounds(), mColorPaint);
+                } else {
+                    super.draw(myCanvas);
+                }
+                mBitmapDrawable = new BitmapDrawable(Resources.getSystem(), bitmap);
+                mBitmapDrawable.setBounds(getBounds());
+            }
+            mBitmapDrawable.draw(canvas);
+        }
+    }
+}

+ 109 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ClipboardUtils.java

@@ -0,0 +1,109 @@
+package com.cooleshow.base.utils;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Context;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/25
+ *     desc  : utils about clipboard
+ * </pre>
+ */
+public final class ClipboardUtils {
+
+    private ClipboardUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Copy the text to clipboard.
+     * <p>The label equals name of package.</p>
+     *
+     * @param text The text.
+     */
+    public static void copyText(final CharSequence text) {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        cm.setPrimaryClip(ClipData.newPlainText(Utils.getApp().getPackageName(), text));
+    }
+
+    /**
+     * Copy the text to clipboard.
+     *
+     * @param label The label.
+     * @param text  The text.
+     */
+    public static void copyText(final CharSequence label, final CharSequence text) {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        cm.setPrimaryClip(ClipData.newPlainText(label, text));
+    }
+
+    /**
+     * Clear the clipboard.
+     */
+    public static void clear() {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        cm.setPrimaryClip(ClipData.newPlainText(null, ""));
+    }
+
+    /**
+     * Return the label for clipboard.
+     *
+     * @return the label for clipboard
+     */
+    public static CharSequence getLabel() {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        ClipDescription des = cm.getPrimaryClipDescription();
+        if (des == null) {
+            return "";
+        }
+        CharSequence label = des.getLabel();
+        if (label == null) {
+            return "";
+        }
+        return label;
+    }
+
+    /**
+     * Return the text for clipboard.
+     *
+     * @return the text for clipboard
+     */
+    public static CharSequence getText() {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        ClipData clip = cm.getPrimaryClip();
+        if (clip != null && clip.getItemCount() > 0) {
+            CharSequence text = clip.getItemAt(0).coerceToText(Utils.getApp());
+            if (text != null) {
+                return text;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Add the clipboard changed listener.
+     */
+    public static void addChangedListener(final ClipboardManager.OnPrimaryClipChangedListener listener) {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        cm.addPrimaryClipChangedListener(listener);
+    }
+
+    /**
+     * Remove the clipboard changed listener.
+     */
+    public static void removeChangedListener(final ClipboardManager.OnPrimaryClipChangedListener listener) {
+        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
+        //noinspection ConstantConditions
+        cm.removePrimaryClipChangedListener(listener);
+    }
+}

+ 35 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CloneUtils.java

@@ -0,0 +1,35 @@
+package com.cooleshow.base.utils;
+
+import java.lang.reflect.Type;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/01/30
+ *     desc  : utils about clone
+ * </pre>
+ */
+public final class CloneUtils {
+
+    private CloneUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Deep clone.
+     *
+     * @param data The data.
+     * @param type The type.
+     * @param <T>  The value type.
+     * @return The object of cloned.
+     */
+    public static <T> T deepClone(final T data, final Type type) {
+        try {
+            return UtilsBridge.fromJson(UtilsBridge.toJson(data), type);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}

+ 54 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CloseUtils.java

@@ -0,0 +1,54 @@
+package com.cooleshow.base.utils;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/10/09
+ *     desc  : utils about close
+ * </pre>
+ */
+public final class CloseUtils {
+
+    private CloseUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Close the io stream.
+     *
+     * @param closeables The closeables.
+     */
+    public static void closeIO(final Closeable... closeables) {
+        if (closeables == null) return;
+        for (Closeable closeable : closeables) {
+            if (closeable != null) {
+                try {
+                    closeable.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Close the io stream quietly.
+     *
+     * @param closeables The closeables.
+     */
+    public static void closeIOQuietly(final Closeable... closeables) {
+        if (closeables == null) return;
+        for (Closeable closeable : closeables) {
+            if (closeable != null) {
+                try {
+                    closeable.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+    }
+}

+ 949 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CollectionUtils.java

@@ -0,0 +1,949 @@
+package com.cooleshow.base.utils;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * <pre>
+ *     author: blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/07/26
+ *     desc  : utils about collection
+ * </pre>
+ */
+public final class CollectionUtils {
+
+    private CollectionUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // listOf
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns a new read-only list of given elements.
+     *
+     * @param array The array.
+     * @return a new read-only list of given elements
+     */
+    @SafeVarargs
+    public static <E> List<E> newUnmodifiableList(E... array) {
+        return Collections.unmodifiableList(newArrayList(array));
+    }
+
+    /**
+     * Returns a new read-only list only of those given elements, that are not null.
+     *
+     * @param array The array.
+     * @return a new read-only list only of those given elements, that are not null
+     */
+    @SafeVarargs
+    public static <E> List<E> newUnmodifiableListNotNull(E... array) {
+        return Collections.unmodifiableList(newArrayListNotNull(array));
+    }
+
+    @SafeVarargs
+    public static <E> ArrayList<E> newArrayList(E... array) {
+        ArrayList<E> list = new ArrayList<>();
+        if (array == null || array.length == 0) return list;
+        for (E e : array) {
+            list.add(e);
+        }
+        return list;
+    }
+
+    @SafeVarargs
+    public static <E> ArrayList<E> newArrayListNotNull(E... array) {
+        ArrayList<E> list = new ArrayList<>();
+        if (array == null || array.length == 0) return list;
+        for (E e : array) {
+            if (e == null) continue;
+            list.add(e);
+        }
+        return list;
+    }
+
+    @SafeVarargs
+    public static <E> LinkedList<E> newLinkedList(E... array) {
+        LinkedList<E> list = new LinkedList<>();
+        if (array == null || array.length == 0) return list;
+        for (E e : array) {
+            list.add(e);
+        }
+        return list;
+    }
+
+    @SafeVarargs
+    public static <E> LinkedList<E> newLinkedListNotNull(E... array) {
+        LinkedList<E> list = new LinkedList<>();
+        if (array == null || array.length == 0) return list;
+        for (E e : array) {
+            if (e == null) continue;
+            list.add(e);
+        }
+        return list;
+    }
+
+    @SafeVarargs
+    public static <E> HashSet<E> newHashSet(E... array) {
+        HashSet<E> set = new HashSet<>();
+        if (array == null || array.length == 0) return set;
+        for (E e : array) {
+            set.add(e);
+        }
+        return set;
+    }
+
+    @SafeVarargs
+    public static <E> HashSet<E> newHashSetNotNull(E... array) {
+        HashSet<E> set = new HashSet<>();
+        if (array == null || array.length == 0) return set;
+        for (E e : array) {
+            if (e == null) continue;
+            set.add(e);
+        }
+        return set;
+    }
+
+    @SafeVarargs
+    public static <E> TreeSet<E> newTreeSet(Comparator<E> comparator, E... array) {
+        TreeSet<E> set = new TreeSet<>(comparator);
+        if (array == null || array.length == 0) return set;
+        for (E e : array) {
+            set.add(e);
+        }
+        return set;
+    }
+
+    @SafeVarargs
+    public static <E> TreeSet<E> newTreeSetNotNull(Comparator<E> comparator, E... array) {
+        TreeSet<E> set = new TreeSet<>(comparator);
+        if (array == null || array.length == 0) return set;
+        for (E e : array) {
+            if (e == null) continue;
+            set.add(e);
+        }
+        return set;
+    }
+
+    public static Collection newSynchronizedCollection(Collection collection) {
+        return Collections.synchronizedCollection(collection);
+    }
+
+    public static Collection newUnmodifiableCollection(Collection collection) {
+        return Collections.unmodifiableCollection(collection);
+    }
+
+    /**
+     * Returns a {@link Collection} containing the union
+     * of the given {@link Collection}s.
+     * <p>
+     * The cardinality of each element in the returned {@link Collection}
+     * will be equal to the maximum of the cardinality of that element
+     * in the two given {@link Collection}s.
+     *
+     * @param a the first collection
+     * @param b the second collection
+     * @return the union of the two collections
+     * @see Collection#addAll
+     */
+    public static Collection union(final Collection a, final Collection b) {
+        if (a == null && b == null) return new ArrayList();
+        if (a == null) return new ArrayList<Object>(b);
+        if (b == null) return new ArrayList<Object>(a);
+        ArrayList<Object> list = new ArrayList<>();
+        Map<Object, Integer> mapA = getCardinalityMap(a);
+        Map<Object, Integer> mapB = getCardinalityMap(b);
+        Set<Object> elts = new HashSet<Object>(a);
+        elts.addAll(b);
+        for (Object obj : elts) {
+            for (int i = 0, m = Math.max(getFreq(obj, mapA), getFreq(obj, mapB)); i < m; i++) {
+                list.add(obj);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Returns a {@link Collection} containing the intersection
+     * of the given {@link Collection}s.
+     * <p>
+     * The cardinality of each element in the returned {@link Collection}
+     * will be equal to the minimum of the cardinality of that element
+     * in the two given {@link Collection}s.
+     *
+     * @param a the first collection
+     * @param b the second collection
+     * @return the intersection of the two collections
+     * @see Collection#retainAll
+     */
+    public static Collection intersection(final Collection a, final Collection b) {
+        if (a == null || b == null) return new ArrayList();
+        ArrayList<Object> list = new ArrayList<>();
+        Map mapA = getCardinalityMap(a);
+        Map mapB = getCardinalityMap(b);
+        Set<Object> elts = new HashSet<Object>(a);
+        elts.addAll(b);
+        for (Object obj : elts) {
+            for (int i = 0, m = Math.min(getFreq(obj, mapA), getFreq(obj, mapB)); i < m; i++) {
+                list.add(obj);
+            }
+        }
+        return list;
+    }
+
+    private static int getFreq(final Object obj, final Map freqMap) {
+        Integer count = (Integer) freqMap.get(obj);
+        if (count != null) {
+            return count;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns a {@link Collection} containing the exclusive disjunction
+     * (symmetric difference) of the given {@link Collection}s.
+     * <p>
+     * The cardinality of each element <i>e</i> in the returned {@link Collection}
+     * will be equal to
+     * <tt>max(cardinality(<i>e</i>,<i>a</i>),cardinality(<i>e</i>,<i>b</i>)) - min(cardinality(<i>e</i>,<i>a</i>),cardinality(<i>e</i>,<i>b</i>))</tt>.
+     * <p>
+     * This is equivalent to
+     * <tt>{@link #subtract subtract}({@link #union union(a,b)},{@link #intersection intersection(a,b)})</tt>
+     * or
+     * <tt>{@link #union union}({@link #subtract subtract(a,b)},{@link #subtract subtract(b,a)})</tt>.
+     *
+     * @param a the first collection
+     * @param b the second collection
+     * @return the symmetric difference of the two collections
+     */
+    public static Collection disjunction(final Collection a, final Collection b) {
+        if (a == null && b == null) return new ArrayList();
+        if (a == null) return new ArrayList<Object>(b);
+        if (b == null) return new ArrayList<Object>(a);
+        ArrayList<Object> list = new ArrayList<>();
+        Map mapA = getCardinalityMap(a);
+        Map mapB = getCardinalityMap(b);
+        Set<Object> elts = new HashSet<Object>(a);
+        elts.addAll(b);
+        for (Object obj : elts) {
+            for (int i = 0, m = ((Math.max(getFreq(obj, mapA), getFreq(obj, mapB)))
+                    - (Math.min(getFreq(obj, mapA), getFreq(obj, mapB)))); i < m; i++) {
+                list.add(obj);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Returns a new {@link Collection} containing <tt><i>a</i> - <i>b</i></tt>.
+     * The cardinality of each element <i>e</i> in the returned {@link Collection}
+     * will be the cardinality of <i>e</i> in <i>a</i> minus the cardinality
+     * of <i>e</i> in <i>b</i>, or zero, whichever is greater.
+     *
+     * @param a the collection to subtract from
+     * @param b the collection to subtract
+     * @return a new collection with the results
+     * @see Collection#removeAll
+     */
+    public static Collection subtract(final Collection a, final Collection b) {
+        if (a == null) return new ArrayList();
+        if (b == null) return new ArrayList<Object>(a);
+        ArrayList<Object> list = new ArrayList<Object>(a);
+        for (Object o : b) {
+            list.remove(o);
+        }
+        return list;
+    }
+
+    /**
+     * Returns <code>true</code> iff at least one element is in both collections.
+     * <p>
+     * In other words, this method returns <code>true</code> iff the
+     * {@link #intersection} of <i>coll1</i> and <i>coll2</i> is not empty.
+     *
+     * @param coll1 the first collection
+     * @param coll2 the first collection
+     * @return <code>true</code> iff the intersection of the collections is non-empty
+     * @see #intersection
+     */
+    public static boolean containsAny(final Collection coll1, final Collection coll2) {
+        if (coll1 == null || coll2 == null) return false;
+        if (coll1.size() < coll2.size()) {
+            for (Object o : coll1) {
+                if (coll2.contains(o)) {
+                    return true;
+                }
+            }
+        } else {
+            for (Object o : coll2) {
+                if (coll1.contains(o)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Returns a {@link Map} mapping each unique element in the given
+     * {@link Collection} to an {@link Integer} representing the number
+     * of occurrences of that element in the {@link Collection}.
+     * <p>
+     * Only those elements present in the collection will appear as
+     * keys in the map.
+     *
+     * @param coll the collection to get the cardinality map for, must not be null
+     * @return the populated cardinality map
+     */
+    public static Map<Object, Integer> getCardinalityMap(final Collection coll) {
+        Map<Object, Integer> count = new HashMap<>();
+        if (coll == null) return count;
+        for (Object obj : coll) {
+            Integer c = count.get(obj);
+            if (c == null) {
+                count.put(obj, 1);
+            } else {
+                count.put(obj, c + 1);
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Returns <tt>true</tt> iff <i>a</i> is a sub-collection of <i>b</i>,
+     * that is, iff the cardinality of <i>e</i> in <i>a</i> is less
+     * than or equal to the cardinality of <i>e</i> in <i>b</i>,
+     * for each element <i>e</i> in <i>a</i>.
+     *
+     * @param a the first (sub?) collection
+     * @param b the second (super?) collection
+     * @return <code>true</code> iff <i>a</i> is a sub-collection of <i>b</i>
+     * @see #isProperSubCollection
+     * @see Collection#containsAll
+     */
+    public static boolean isSubCollection(final Collection a, final Collection b) {
+        if (a == null || b == null) return false;
+        Map mapA = getCardinalityMap(a);
+        Map mapB = getCardinalityMap(b);
+        for (Object obj : a) {
+            if (getFreq(obj, mapA) > getFreq(obj, mapB)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns <tt>true</tt> iff <i>a</i> is a <i>proper</i> sub-collection of <i>b</i>,
+     * that is, iff the cardinality of <i>e</i> in <i>a</i> is less
+     * than or equal to the cardinality of <i>e</i> in <i>b</i>,
+     * for each element <i>e</i> in <i>a</i>, and there is at least one
+     * element <i>f</i> such that the cardinality of <i>f</i> in <i>b</i>
+     * is strictly greater than the cardinality of <i>f</i> in <i>a</i>.
+     * <p>
+     * The implementation assumes
+     * <ul>
+     * <li><code>a.size()</code> and <code>b.size()</code> represent the
+     * total cardinality of <i>a</i> and <i>b</i>, resp. </li>
+     * <li><code>a.size() < Integer.MAXVALUE</code></li>
+     * </ul>
+     *
+     * @param a the first (sub?) collection
+     * @param b the second (super?) collection
+     * @return <code>true</code> iff <i>a</i> is a <i>proper</i> sub-collection of <i>b</i>
+     * @see #isSubCollection
+     * @see Collection#containsAll
+     */
+    public static boolean isProperSubCollection(final Collection a, final Collection b) {
+        if (a == null || b == null) return false;
+        return a.size() < b.size() && isSubCollection(a, b);
+    }
+
+    /**
+     * Returns <tt>true</tt> iff the given {@link Collection}s contain
+     * exactly the same elements with exactly the same cardinalities.
+     * <p>
+     * That is, iff the cardinality of <i>e</i> in <i>a</i> is
+     * equal to the cardinality of <i>e</i> in <i>b</i>,
+     * for each element <i>e</i> in <i>a</i> or <i>b</i>.
+     *
+     * @param a the first collection
+     * @param b the second collection
+     * @return <code>true</code> iff the collections contain the same elements with the same cardinalities.
+     */
+    public static boolean isEqualCollection(final Collection a, final Collection b) {
+        if (a == null || b == null) return false;
+        if (a.size() != b.size()) {
+            return false;
+        } else {
+            Map mapA = getCardinalityMap(a);
+            Map mapB = getCardinalityMap(b);
+            if (mapA.size() != mapB.size()) {
+                return false;
+            } else {
+                for (Object obj : mapA.keySet()) {
+                    if (getFreq(obj, mapA) != getFreq(obj, mapB)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Returns the number of occurrences of <i>obj</i> in <i>coll</i>.
+     *
+     * @param obj  the object to find the cardinality of
+     * @param coll the collection to search
+     * @return the the number of occurrences of obj in coll
+     */
+    public static <E> int cardinality(E obj, final Collection<E> coll) {
+        if (coll == null) return 0;
+        if (coll instanceof Set) {
+            return (coll.contains(obj) ? 1 : 0);
+        }
+        int count = 0;
+        if (obj == null) {
+            for (E e : coll) {
+                if (e == null) {
+                    count++;
+                }
+            }
+        } else {
+            for (E e : coll) {
+                if (obj.equals(e)) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Finds the first element in the given collection which matches the given predicate.
+     * <p>
+     * If the input collection or predicate is null, or no element of the collection
+     * matches the predicate, null is returned.
+     *
+     * @param collection the collection to search, may be null
+     * @param predicate  the predicate to use, may be null
+     * @return the first element of the collection which matches the predicate or null if none could be found
+     */
+    public static <E> E find(Collection<E> collection, Predicate<E> predicate) {
+        if (collection == null || predicate == null) return null;
+        for (E item : collection) {
+            if (predicate.evaluate(item)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Executes the given closure on each element in the collection.
+     * <p>
+     * If the input collection or closure is null, there is no change made.
+     *
+     * @param collection the collection to get the input from, may be null
+     * @param closure    the closure to perform, may be null
+     */
+    public static <E> void forAllDo(Collection<E> collection, Closure<E> closure) {
+        if (collection == null || closure == null) return;
+        int index = 0;
+        for (E e : collection) {
+            closure.execute(index++, e);
+        }
+    }
+
+    /**
+     * Filter the collection by applying a Predicate to each element. If the
+     * predicate returns false, remove the element.
+     * <p>
+     * If the input collection or predicate is null, there is no change made.
+     *
+     * @param collection the collection to get the input from, may be null
+     * @param predicate  the predicate to use as a filter, may be null
+     */
+    public static <E> void filter(Collection<E> collection, Predicate<E> predicate) {
+        if (collection == null || predicate == null) return;
+        for (Iterator it = collection.iterator(); it.hasNext(); ) {
+            if (!predicate.evaluate((E) it.next())) {
+                it.remove();
+            }
+        }
+    }
+
+    /**
+     * Selects all elements from input collection which match the given predicate
+     * into an output collection.
+     * <p>
+     * A <code>null</code> predicate matches no elements.
+     *
+     * @param inputCollection the collection to get the input from, may not be null
+     * @param predicate       the predicate to use, may be null
+     * @return the elements matching the predicate (new list)
+     * @throws NullPointerException if the input collection is null
+     */
+    public static <E> Collection<E> select(Collection<E> inputCollection, Predicate<E> predicate) {
+        if (inputCollection == null || predicate == null) return new ArrayList<>();
+        ArrayList<E> answer = new ArrayList<>(inputCollection.size());
+        for (E o : inputCollection) {
+            if (predicate.evaluate(o)) {
+                answer.add(o);
+            }
+        }
+        return answer;
+    }
+
+    /**
+     * Selects all elements from inputCollection which don't match the given predicate
+     * into an output collection.
+     * <p>
+     * If the input predicate is <code>null</code>, the result is an empty list.
+     *
+     * @param inputCollection the collection to get the input from, may not be null
+     * @param predicate       the predicate to use, may be null
+     * @return the elements <b>not</b> matching the predicate (new list)
+     * @throws NullPointerException if the input collection is null
+     */
+    public static <E> Collection<E> selectRejected(Collection<E> inputCollection, Predicate<E> predicate) {
+        if (inputCollection == null || predicate == null) return new ArrayList<>();
+        ArrayList<E> answer = new ArrayList<>(inputCollection.size());
+        for (E o : inputCollection) {
+            if (!predicate.evaluate(o)) {
+                answer.add(o);
+            }
+        }
+        return answer;
+    }
+
+    /**
+     * Transform the collection by applying a Transformer to each element.
+     * <p>
+     * If the input collection or transformer is null, there is no change made.
+     * <p>
+     * This routine is best for Lists, for which set() is used to do the
+     * transformations "in place."  For other Collections, clear() and addAll()
+     * are used to replace elements.
+     * <p>
+     * If the input collection controls its input, such as a Set, and the
+     * Transformer creates duplicates (or are otherwise invalid), the
+     * collection may reduce in size due to calling this method.
+     *
+     * @param collection  the collection to get the input from, may be null
+     * @param transformer the transformer to perform, may be null
+     */
+    public static <E1, E2> void transform(Collection<E1> collection, Transformer<E1, E2> transformer) {
+        if (collection == null || transformer == null) return;
+        if (collection instanceof List) {
+            List list = (List) collection;
+            for (ListIterator it = list.listIterator(); it.hasNext(); ) {
+                it.set(transformer.transform((E1) it.next()));
+            }
+        } else {
+            Collection resultCollection = collect(collection, transformer);
+            collection.clear();
+            collection.addAll(resultCollection);
+        }
+    }
+
+
+    /**
+     * Returns a new Collection consisting of the elements of inputCollection transformed
+     * by the given transformer.
+     * <p>
+     * If the input transformer is null, the result is an empty list.
+     *
+     * @param inputCollection the collection to get the input from, may be null
+     * @param transformer     the transformer to use, may be null
+     * @return the transformed result (new list)
+     */
+    public static <E1, E2> Collection<E2> collect(final Collection<E1> inputCollection,
+                                                  final Transformer<E1, E2> transformer) {
+        List<E2> answer = new ArrayList<>();
+        if (inputCollection == null || transformer == null) return answer;
+        for (E1 e1 : inputCollection) {
+            answer.add(transformer.transform(e1));
+        }
+        return answer;
+    }
+
+    /**
+     * Counts the number of elements in the input collection that match the predicate.
+     * <p>
+     * A <code>null</code> collection or predicate matches no elements.
+     *
+     * @param collection the collection to get the input from, may be null
+     * @param predicate  the predicate to use, may be null
+     * @return the number of matches for the predicate in the collection
+     */
+    public static <E> int countMatches(Collection<E> collection, Predicate<E> predicate) {
+        if (collection == null || predicate == null) return 0;
+        int count = 0;
+        for (E o : collection) {
+            if (predicate.evaluate(o)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Answers true if a predicate is true for at least one element of a collection.
+     * <p>
+     * A <code>null</code> collection or predicate returns false.
+     *
+     * @param collection the collection to get the input from, may be null
+     * @param predicate  the predicate to use, may be null
+     * @return true if at least one element of the collection matches the predicate
+     */
+    public static <E> boolean exists(Collection<E> collection, Predicate<E> predicate) {
+        if (collection == null || predicate == null) return false;
+        for (E o : collection) {
+            if (predicate.evaluate(o)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Adds an element to the collection unless the element is null.
+     *
+     * @param collection the collection to add to, may be null
+     * @param object     the object to add, if null it will not be added
+     * @return true if the collection changed
+     */
+    public static <E> boolean addIgnoreNull(Collection<E> collection, E object) {
+        if (collection == null) return false;
+        return (object != null && collection.add(object));
+    }
+
+    /**
+     * Adds all elements in the iteration to the given collection.
+     *
+     * @param collection the collection to add to, may be null
+     * @param iterator   the iterator of elements to add, may be null
+     */
+    public static <E> void addAll(Collection<E> collection, Iterator<E> iterator) {
+        if (collection == null || iterator == null) return;
+        while (iterator.hasNext()) {
+            collection.add(iterator.next());
+        }
+    }
+
+    /**
+     * Adds all elements in the enumeration to the given collection.
+     *
+     * @param collection  the collection to add to, may be null
+     * @param enumeration the enumeration of elements to add, may be null
+     */
+    public static <E> void addAll(Collection<E> collection, Enumeration<E> enumeration) {
+        if (collection == null || enumeration == null) return;
+        while (enumeration.hasMoreElements()) {
+            collection.add(enumeration.nextElement());
+        }
+    }
+
+    /**
+     * Adds all elements in the array to the given collection.
+     *
+     * @param collection the collection to add to, may be null
+     * @param elements   the array of elements to add, may be null
+     */
+    public static <E> void addAll(Collection<E> collection, E[] elements) {
+        if (collection == null || elements == null || elements.length == 0) return;
+        collection.addAll(Arrays.asList(elements));
+    }
+
+    /**
+     * Returns the <code>index</code>-th value in <code>object</code>, throwing
+     * <code>IndexOutOfBoundsException</code> if there is no such element or
+     * <code>IllegalArgumentException</code> if <code>object</code> is not an
+     * instance of one of the supported types.
+     * <p>
+     * The supported types, and associated semantics are:
+     * <ul>
+     * <li> Map -- the value returned is the <code>Map.Entry</code> in position
+     * <code>index</code> in the map's <code>entrySet</code> iterator,
+     * if there is such an entry.</li>
+     * <li> List -- this method is equivalent to the list's get method.</li>
+     * <li> Array -- the <code>index</code>-th array entry is returned,
+     * if there is such an entry; otherwise an <code>IndexOutOfBoundsException</code>
+     * is thrown.</li>
+     * <li> Collection -- the value returned is the <code>index</code>-th object
+     * returned by the collection's default iterator, if there is such an element.</li>
+     * <li> Iterator or Enumeration -- the value returned is the
+     * <code>index</code>-th object in the Iterator/Enumeration, if there
+     * is such an element.  The Iterator/Enumeration is advanced to
+     * <code>index</code> (or to the end, if <code>index</code> exceeds the
+     * number of entries) as a side effect of this method.</li>
+     * </ul>
+     *
+     * @param object the object to get a value from
+     * @param index  the index to get
+     * @return the object at the specified index
+     * @throws IndexOutOfBoundsException if the index is invalid
+     * @throws IllegalArgumentException  if the object type is invalid
+     */
+    public static Object get(Object object, int index) {
+        if (object == null) return null;
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Index cannot be negative: " + index);
+        }
+        if (object instanceof Map) {
+            Map map = (Map) object;
+            Iterator iterator = map.entrySet().iterator();
+            return get(iterator, index);
+        } else if (object instanceof List) {
+            return ((List) object).get(index);
+        } else if (object instanceof Object[]) {
+            return ((Object[]) object)[index];
+        } else if (object instanceof Iterator) {
+            Iterator it = (Iterator) object;
+            while (it.hasNext()) {
+                index--;
+                if (index == -1) {
+                    return it.next();
+                } else {
+                    it.next();
+                }
+            }
+            throw new IndexOutOfBoundsException("Entry does not exist: " + index);
+        } else if (object instanceof Collection) {
+            Iterator iterator = ((Collection) object).iterator();
+            return get(iterator, index);
+        } else if (object instanceof Enumeration) {
+            Enumeration it = (Enumeration) object;
+            while (it.hasMoreElements()) {
+                index--;
+                if (index == -1) {
+                    return it.nextElement();
+                } else {
+                    it.nextElement();
+                }
+            }
+            throw new IndexOutOfBoundsException("Entry does not exist: " + index);
+        } else {
+            try {
+                return Array.get(object, index);
+            } catch (IllegalArgumentException ex) {
+                throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
+            }
+        }
+    }
+
+    /**
+     * Gets the size of the collection/iterator specified.
+     * <p>
+     * This method can handles objects as follows
+     * <ul>
+     * <li>Collection - the collection size
+     * <li>Map - the map size
+     * <li>Array - the array size
+     * <li>Iterator - the number of elements remaining in the iterator
+     * <li>Enumeration - the number of elements remaining in the enumeration
+     * </ul>
+     *
+     * @param object the object to get the size of
+     * @return the size of the specified collection
+     * @throws IllegalArgumentException thrown if object is not recognised or null
+     */
+    public static int size(Object object) {
+        if (object == null) return 0;
+        int total = 0;
+        if (object instanceof Map) {
+            total = ((Map) object).size();
+        } else if (object instanceof Collection) {
+            total = ((Collection) object).size();
+        } else if (object instanceof Object[]) {
+            total = ((Object[]) object).length;
+        } else if (object instanceof Iterator) {
+            Iterator it = (Iterator) object;
+            while (it.hasNext()) {
+                total++;
+                it.next();
+            }
+        } else if (object instanceof Enumeration) {
+            Enumeration it = (Enumeration) object;
+            while (it.hasMoreElements()) {
+                total++;
+                it.nextElement();
+            }
+        } else {
+            try {
+                total = Array.getLength(object);
+            } catch (IllegalArgumentException ex) {
+                throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
+            }
+        }
+        return total;
+    }
+
+    /**
+     * Checks if the specified collection/array/iterator is empty.
+     * <p>
+     * This method can handles objects as follows
+     * <ul>
+     * <li>Collection - via collection isEmpty
+     * <li>Map - via map isEmpty
+     * <li>Array - using array size
+     * <li>Iterator - via hasNext
+     * <li>Enumeration - via hasMoreElements
+     * </ul>
+     * <p>
+     * Note: This method is named to avoid clashing with
+     * {@link #isEmpty(Collection)}.
+     *
+     * @param object the object to get the size of, not null
+     * @return true if empty
+     * @throws IllegalArgumentException thrown if object is not recognised or null
+     */
+    public static boolean sizeIsEmpty(Object object) {
+        if (object == null) return true;
+        if (object instanceof Collection) {
+            return ((Collection) object).isEmpty();
+        } else if (object instanceof Map) {
+            return ((Map) object).isEmpty();
+        } else if (object instanceof Object[]) {
+            return ((Object[]) object).length == 0;
+        } else if (object instanceof Iterator) {
+            return !((Iterator) object).hasNext();
+        } else if (object instanceof Enumeration) {
+            return !((Enumeration) object).hasMoreElements();
+        } else {
+            try {
+                return Array.getLength(object) == 0;
+            } catch (IllegalArgumentException ex) {
+                throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
+            }
+        }
+    }
+
+    /**
+     * Null-safe check if the specified collection is empty.
+     * <p>
+     * Null returns true.
+     *
+     * @param coll the collection to check, may be null
+     * @return true if empty or null
+     */
+    public static boolean isEmpty(Collection coll) {
+        return coll == null || coll.size() == 0;
+    }
+
+    /**
+     * Null-safe check if the specified collection is not empty.
+     * <p>
+     * Null returns false.
+     *
+     * @param coll the collection to check, may be null
+     * @return true if non-null and non-empty
+     */
+    public static boolean isNotEmpty(Collection coll) {
+        return !isEmpty(coll);
+    }
+
+
+    /**
+     * Returns a collection containing all the elements in <code>collection</code>
+     * that are also in <code>retain</code>. The cardinality of an element <code>e</code>
+     * in the returned collection is the same as the cardinality of <code>e</code>
+     * in <code>collection</code> unless <code>retain</code> does not contain <code>e</code>, in which
+     * case the cardinality is zero. This method is useful if you do not wish to modify
+     * the collection <code>c</code> and thus cannot call <code>c.retainAll(retain);</code>.
+     *
+     * @param collection the collection whose contents are the target of the #retailAll operation
+     * @param retain     the collection containing the elements to be retained in the returned collection
+     * @return a <code>Collection</code> containing all the elements of <code>collection</code>
+     * that occur at least once in <code>retain</code>.
+     */
+    public static <E> Collection<E> retainAll(Collection<E> collection, Collection<E> retain) {
+        if (collection == null || retain == null) return new ArrayList<>();
+        List<E> list = new ArrayList<>();
+        for (E item : collection) {
+            if (retain.contains(item)) {
+                list.add(item);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Removes the elements in <code>remove</code> from <code>collection</code>. That is, this
+     * method returns a collection containing all the elements in <code>c</code>
+     * that are not in <code>remove</code>. The cardinality of an element <code>e</code>
+     * in the returned collection is the same as the cardinality of <code>e</code>
+     * in <code>collection</code> unless <code>remove</code> contains <code>e</code>, in which
+     * case the cardinality is zero. This method is useful if you do not wish to modify
+     * the collection <code>c</code> and thus cannot call <code>collection.removeAll(remove);</code>.
+     *
+     * @param collection the collection from which items are removed (in the returned collection)
+     * @param remove     the items to be removed from the returned <code>collection</code>
+     * @return a <code>Collection</code> containing all the elements of <code>collection</code> except
+     * any elements that also occur in <code>remove</code>.
+     */
+    public static <E> Collection<E> removeAll(Collection<E> collection, Collection<E> remove) {
+        if (collection == null) return new ArrayList<>();
+        if (remove == null) return new ArrayList<>(collection);
+        List<E> list = new ArrayList<>();
+        for (E obj : collection) {
+            if (!remove.contains(obj)) {
+                list.add(obj);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Randomly permutes the specified list using a default source of randomness.
+     *
+     * @param list the list to be shuffled.
+     * @throws UnsupportedOperationException if the specified list or
+     *                                       its list-iterator does not support the <tt>set</tt> operation.
+     */
+    public static <T> void shuffle(List<T> list) {
+        Collections.shuffle(list);
+    }
+
+    /**
+     * Return the string of collection.
+     *
+     * @param collection The collection.
+     * @return the string of collection
+     */
+    public static String toString(Collection collection) {
+        if (collection == null) return "null";
+        return collection.toString();
+    }
+
+    public interface Closure<E> {
+        void execute(int index, E item);
+    }
+
+    public interface Transformer<E1, E2> {
+        E2 transform(E1 input);
+    }
+
+    public interface Predicate<E> {
+        boolean evaluate(E item);
+    }
+}

+ 217 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ColorUtils.java

@@ -0,0 +1,217 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.Color;
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/01/15
+ *     desc  : utils about color
+ * </pre>
+ */
+public final class ColorUtils {
+
+    private ColorUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Returns a color associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier.
+     * @return a color associated with a particular resource ID
+     */
+    public static int getColor(@ColorRes int id) {
+        return ContextCompat.getColor(Utils.getApp(), id);
+    }
+
+    /**
+     * Set the alpha component of {@code color} to be {@code alpha}.
+     *
+     * @param color The color.
+     * @param alpha Alpha component \([0..255]\) of the color.
+     * @return the {@code color} with {@code alpha} component
+     */
+    public static int setAlphaComponent(@ColorInt final int color,
+                                        @IntRange(from = 0x0, to = 0xFF) int alpha) {
+        return (color & 0x00ffffff) | (alpha << 24);
+    }
+
+    /**
+     * Set the alpha component of {@code color} to be {@code alpha}.
+     *
+     * @param color The color.
+     * @param alpha Alpha component \([0..1]\) of the color.
+     * @return the {@code color} with {@code alpha} component
+     */
+    public static int setAlphaComponent(@ColorInt int color,
+                                        @FloatRange(from = 0, to = 1) float alpha) {
+        return (color & 0x00ffffff) | ((int) (alpha * 255.0f + 0.5f) << 24);
+    }
+
+    /**
+     * Set the red component of {@code color} to be {@code red}.
+     *
+     * @param color The color.
+     * @param red   Red component \([0..255]\) of the color.
+     * @return the {@code color} with {@code red} component
+     */
+    public static int setRedComponent(@ColorInt int color,
+                                      @IntRange(from = 0x0, to = 0xFF) int red) {
+        return (color & 0xff00ffff) | (red << 16);
+    }
+
+    /**
+     * Set the red component of {@code color} to be {@code red}.
+     *
+     * @param color The color.
+     * @param red   Red component \([0..1]\) of the color.
+     * @return the {@code color} with {@code red} component
+     */
+    public static int setRedComponent(@ColorInt int color,
+                                      @FloatRange(from = 0, to = 1) float red) {
+        return (color & 0xff00ffff) | ((int) (red * 255.0f + 0.5f) << 16);
+    }
+
+    /**
+     * Set the green component of {@code color} to be {@code green}.
+     *
+     * @param color The color.
+     * @param green Green component \([0..255]\) of the color.
+     * @return the {@code color} with {@code green} component
+     */
+    public static int setGreenComponent(@ColorInt int color,
+                                        @IntRange(from = 0x0, to = 0xFF) int green) {
+        return (color & 0xffff00ff) | (green << 8);
+    }
+
+    /**
+     * Set the green component of {@code color} to be {@code green}.
+     *
+     * @param color The color.
+     * @param green Green component \([0..1]\) of the color.
+     * @return the {@code color} with {@code green} component
+     */
+    public static int setGreenComponent(@ColorInt int color,
+                                        @FloatRange(from = 0, to = 1) float green) {
+        return (color & 0xffff00ff) | ((int) (green * 255.0f + 0.5f) << 8);
+    }
+
+    /**
+     * Set the blue component of {@code color} to be {@code blue}.
+     *
+     * @param color The color.
+     * @param blue  Blue component \([0..255]\) of the color.
+     * @return the {@code color} with {@code blue} component
+     */
+    public static int setBlueComponent(@ColorInt int color,
+                                       @IntRange(from = 0x0, to = 0xFF) int blue) {
+        return (color & 0xffffff00) | blue;
+    }
+
+    /**
+     * Set the blue component of {@code color} to be {@code blue}.
+     *
+     * @param color The color.
+     * @param blue  Blue component \([0..1]\) of the color.
+     * @return the {@code color} with {@code blue} component
+     */
+    public static int setBlueComponent(@ColorInt int color,
+                                       @FloatRange(from = 0, to = 1) float blue) {
+        return (color & 0xffffff00) | (int) (blue * 255.0f + 0.5f);
+    }
+
+    /**
+     * Color-string to color-int.
+     * <p>Supported formats are:</p>
+     *
+     * <ul>
+     * <li><code>#RRGGBB</code></li>
+     * <li><code>#AARRGGBB</code></li>
+     * </ul>
+     *
+     * <p>The following names are also accepted: <code>red</code>, <code>blue</code>,
+     * <code>green</code>, <code>black</code>, <code>white</code>, <code>gray</code>,
+     * <code>cyan</code>, <code>magenta</code>, <code>yellow</code>, <code>lightgray</code>,
+     * <code>darkgray</code>, <code>grey</code>, <code>lightgrey</code>, <code>darkgrey</code>,
+     * <code>aqua</code>, <code>fuchsia</code>, <code>lime</code>, <code>maroon</code>,
+     * <code>navy</code>, <code>olive</code>, <code>purple</code>, <code>silver</code>,
+     * and <code>teal</code>.</p>
+     *
+     * @param colorString The color-string.
+     * @return color-int
+     * @throws IllegalArgumentException The string cannot be parsed.
+     */
+    public static int string2Int(@NonNull String colorString) {
+        return Color.parseColor(colorString);
+    }
+
+    /**
+     * Color-int to color-string.
+     *
+     * @param colorInt The color-int.
+     * @return color-string
+     */
+    public static String int2RgbString(@ColorInt int colorInt) {
+        colorInt = colorInt & 0x00ffffff;
+        String color = Integer.toHexString(colorInt);
+        while (color.length() < 6) {
+            color = "0" + color;
+        }
+        return "#" + color;
+    }
+
+    /**
+     * Color-int to color-string.
+     *
+     * @param colorInt The color-int.
+     * @return color-string
+     */
+    public static String int2ArgbString(@ColorInt final int colorInt) {
+        String color = Integer.toHexString(colorInt);
+        while (color.length() < 6) {
+            color = "0" + color;
+        }
+        while (color.length() < 8) {
+            color = "f" + color;
+        }
+        return "#" + color;
+    }
+
+    /**
+     * Return the random color.
+     *
+     * @return the random color
+     */
+    public static int getRandomColor() {
+        return getRandomColor(true);
+    }
+
+    /**
+     * Return the random color.
+     *
+     * @param supportAlpha True to support alpha, false otherwise.
+     * @return the random color
+     */
+    public static int getRandomColor(final boolean supportAlpha) {
+        int high = supportAlpha ? (int) (Math.random() * 0x100) << 24 : 0xFF000000;
+        return high | (int) (Math.random() * 0x1000000);
+    }
+
+    /**
+     * Return whether the color is light.
+     *
+     * @param color The color.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLightColor(@ColorInt int color) {
+        return 0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color) >= 127.5;
+    }
+}

+ 762 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ConvertUtils.java

@@ -0,0 +1,762 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+
+import com.cooleshow.base.constanst.MemoryConstants;
+import com.cooleshow.base.constanst.TimeConstants;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/13
+ *     desc  : utils about convert
+ * </pre>
+ */
+public final class ConvertUtils {
+
+    private static final int    BUFFER_SIZE      = 8192;
+    private static final char[] HEX_DIGITS_UPPER =
+            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+    private static final char[] HEX_DIGITS_LOWER =
+            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+    private ConvertUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Int to hex string.
+     *
+     * @param num The int number.
+     * @return the hex string
+     */
+    public static String int2HexString(int num) {
+        return Integer.toHexString(num);
+    }
+
+    /**
+     * Hex string to int.
+     *
+     * @param hexString The hex string.
+     * @return the int
+     */
+    public static int hexString2Int(String hexString) {
+        return Integer.parseInt(hexString, 16);
+    }
+
+    /**
+     * Bytes to bits.
+     *
+     * @param bytes The bytes.
+     * @return bits
+     */
+    public static String bytes2Bits(final byte[] bytes) {
+        if (bytes == null || bytes.length == 0) return "";
+        StringBuilder sb = new StringBuilder();
+        for (byte aByte : bytes) {
+            for (int j = 7; j >= 0; --j) {
+                sb.append(((aByte >> j) & 0x01) == 0 ? '0' : '1');
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Bits to bytes.
+     *
+     * @param bits The bits.
+     * @return bytes
+     */
+    public static byte[] bits2Bytes(String bits) {
+        int lenMod = bits.length() % 8;
+        int byteLen = bits.length() / 8;
+        // add "0" until length to 8 times
+        if (lenMod != 0) {
+            for (int i = lenMod; i < 8; i++) {
+                bits = "0" + bits;
+            }
+            byteLen++;
+        }
+        byte[] bytes = new byte[byteLen];
+        for (int i = 0; i < byteLen; ++i) {
+            for (int j = 0; j < 8; ++j) {
+                bytes[i] <<= 1;
+                bytes[i] |= bits.charAt(i * 8 + j) - '0';
+            }
+        }
+        return bytes;
+    }
+
+    /**
+     * Bytes to chars.
+     *
+     * @param bytes The bytes.
+     * @return chars
+     */
+    public static char[] bytes2Chars(final byte[] bytes) {
+        if (bytes == null) return null;
+        int len = bytes.length;
+        if (len <= 0) return null;
+        char[] chars = new char[len];
+        for (int i = 0; i < len; i++) {
+            chars[i] = (char) (bytes[i] & 0xff);
+        }
+        return chars;
+    }
+
+    /**
+     * Chars to bytes.
+     *
+     * @param chars The chars.
+     * @return bytes
+     */
+    public static byte[] chars2Bytes(final char[] chars) {
+        if (chars == null || chars.length <= 0) return null;
+        int len = chars.length;
+        byte[] bytes = new byte[len];
+        for (int i = 0; i < len; i++) {
+            bytes[i] = (byte) (chars[i]);
+        }
+        return bytes;
+    }
+
+    /**
+     * Bytes to hex string.
+     * <p>e.g. bytes2HexString(new byte[] { 0, (byte) 0xa8 }) returns "00A8"</p>
+     *
+     * @param bytes The bytes.
+     * @return hex string
+     */
+    public static String bytes2HexString(final byte[] bytes) {
+        return bytes2HexString(bytes, true);
+    }
+
+    /**
+     * Bytes to hex string.
+     * <p>e.g. bytes2HexString(new byte[] { 0, (byte) 0xa8 }, true) returns "00A8"</p>
+     *
+     * @param bytes       The bytes.
+     * @param isUpperCase True to use upper case, false otherwise.
+     * @return hex string
+     */
+    public static String bytes2HexString(final byte[] bytes, boolean isUpperCase) {
+        if (bytes == null) return "";
+        char[] hexDigits = isUpperCase ? HEX_DIGITS_UPPER : HEX_DIGITS_LOWER;
+        int len = bytes.length;
+        if (len <= 0) return "";
+        char[] ret = new char[len << 1];
+        for (int i = 0, j = 0; i < len; i++) {
+            ret[j++] = hexDigits[bytes[i] >> 4 & 0x0f];
+            ret[j++] = hexDigits[bytes[i] & 0x0f];
+        }
+        return new String(ret);
+    }
+
+    /**
+     * Hex string to bytes.
+     * <p>e.g. hexString2Bytes("00A8") returns { 0, (byte) 0xA8 }</p>
+     *
+     * @param hexString The hex string.
+     * @return the bytes
+     */
+    public static byte[] hexString2Bytes(String hexString) {
+        if (UtilsBridge.isSpace(hexString)) return new byte[0];
+        int len = hexString.length();
+        if (len % 2 != 0) {
+            hexString = "0" + hexString;
+            len = len + 1;
+        }
+        char[] hexBytes = hexString.toUpperCase().toCharArray();
+        byte[] ret = new byte[len >> 1];
+        for (int i = 0; i < len; i += 2) {
+            ret[i >> 1] = (byte) (hex2Dec(hexBytes[i]) << 4 | hex2Dec(hexBytes[i + 1]));
+        }
+        return ret;
+    }
+
+    private static int hex2Dec(final char hexChar) {
+        if (hexChar >= '0' && hexChar <= '9') {
+            return hexChar - '0';
+        } else if (hexChar >= 'A' && hexChar <= 'F') {
+            return hexChar - 'A' + 10;
+        } else {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Bytes to string.
+     */
+    public static String bytes2String(final byte[] bytes) {
+        return bytes2String(bytes, "");
+    }
+
+    /**
+     * Bytes to string.
+     */
+    public static String bytes2String(final byte[] bytes, final String charsetName) {
+        if (bytes == null) return null;
+        try {
+            return new String(bytes, getSafeCharset(charsetName));
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return new String(bytes);
+        }
+    }
+
+    /**
+     * String to bytes.
+     */
+    public static byte[] string2Bytes(final String string) {
+        return string2Bytes(string, "");
+    }
+
+    /**
+     * String to bytes.
+     */
+    public static byte[] string2Bytes(final String string, final String charsetName) {
+        if (string == null) return null;
+        try {
+            return string.getBytes(getSafeCharset(charsetName));
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return string.getBytes();
+        }
+    }
+
+    /**
+     * Bytes to JSONObject.
+     */
+    public static JSONObject bytes2JSONObject(final byte[] bytes) {
+        if (bytes == null) return null;
+        try {
+            return new JSONObject(new String(bytes));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * JSONObject to bytes.
+     */
+    public static byte[] jsonObject2Bytes(final JSONObject jsonObject) {
+        if (jsonObject == null) return null;
+        return jsonObject.toString().getBytes();
+    }
+
+    /**
+     * Bytes to JSONArray.
+     */
+    public static JSONArray bytes2JSONArray(final byte[] bytes) {
+        if (bytes == null) return null;
+        try {
+            return new JSONArray(new String(bytes));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * JSONArray to bytes.
+     */
+    public static byte[] jsonArray2Bytes(final JSONArray jsonArray) {
+        if (jsonArray == null) return null;
+        return jsonArray.toString().getBytes();
+    }
+
+    /**
+     * Bytes to Parcelable
+     */
+    public static <T> T bytes2Parcelable(final byte[] bytes,
+                                         final Parcelable.Creator<T> creator) {
+        if (bytes == null) return null;
+        Parcel parcel = Parcel.obtain();
+        parcel.unmarshall(bytes, 0, bytes.length);
+        parcel.setDataPosition(0);
+        T result = creator.createFromParcel(parcel);
+        parcel.recycle();
+        return result;
+    }
+
+    /**
+     * Parcelable to bytes.
+     */
+    public static byte[] parcelable2Bytes(final Parcelable parcelable) {
+        if (parcelable == null) return null;
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        byte[] bytes = parcel.marshall();
+        parcel.recycle();
+        return bytes;
+    }
+
+    /**
+     * Bytes to Serializable.
+     */
+    public static Object bytes2Object(final byte[] bytes) {
+        if (bytes == null) return null;
+        ObjectInputStream ois = null;
+        try {
+            ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+            return ois.readObject();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (ois != null) {
+                    ois.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Serializable to bytes.
+     */
+    public static byte[] serializable2Bytes(final Serializable serializable) {
+        if (serializable == null) return null;
+        ByteArrayOutputStream baos;
+        ObjectOutputStream oos = null;
+        try {
+            oos = new ObjectOutputStream(baos = new ByteArrayOutputStream());
+            oos.writeObject(serializable);
+            return baos.toByteArray();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (oos != null) {
+                    oos.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Bytes to bitmap.
+     */
+    public static Bitmap bytes2Bitmap(final byte[] bytes) {
+        return UtilsBridge.bytes2Bitmap(bytes);
+    }
+
+    /**
+     * Bitmap to bytes.
+     */
+    public static byte[] bitmap2Bytes(final Bitmap bitmap) {
+        return UtilsBridge.bitmap2Bytes(bitmap);
+    }
+
+    /**
+     * Bitmap to bytes.
+     */
+    public static byte[] bitmap2Bytes(final Bitmap bitmap, final Bitmap.CompressFormat format, int quality) {
+        return UtilsBridge.bitmap2Bytes(bitmap, format, quality);
+    }
+
+    /**
+     * Bytes to drawable.
+     */
+    public static Drawable bytes2Drawable(final byte[] bytes) {
+        return UtilsBridge.bytes2Drawable(bytes);
+    }
+
+    /**
+     * Drawable to bytes.
+     */
+    public static byte[] drawable2Bytes(final Drawable drawable) {
+        return UtilsBridge.drawable2Bytes(drawable);
+    }
+
+    /**
+     * Drawable to bytes.
+     */
+    public static byte[] drawable2Bytes(final Drawable drawable, final Bitmap.CompressFormat format, int quality) {
+        return UtilsBridge.drawable2Bytes(drawable, format, quality);
+    }
+
+    /**
+     * Size of memory in unit to size of byte.
+     *
+     * @param memorySize Size of memory.
+     * @param unit       The unit of memory size.
+     *                   <ul>
+     *                   <li>{@link MemoryConstants#BYTE}</li>
+     *                   <li>{@link MemoryConstants#KB}</li>
+     *                   <li>{@link MemoryConstants#MB}</li>
+     *                   <li>{@link MemoryConstants#GB}</li>
+     *                   </ul>
+     * @return size of byte
+     */
+    public static long memorySize2Byte(final long memorySize,
+                                       @MemoryConstants.Unit final int unit) {
+        if (memorySize < 0) return -1;
+        return memorySize * unit;
+    }
+
+    /**
+     * Size of byte to size of memory in unit.
+     *
+     * @param byteSize Size of byte.
+     * @param unit     The unit of memory size.
+     *                 <ul>
+     *                 <li>{@link MemoryConstants#BYTE}</li>
+     *                 <li>{@link MemoryConstants#KB}</li>
+     *                 <li>{@link MemoryConstants#MB}</li>
+     *                 <li>{@link MemoryConstants#GB}</li>
+     *                 </ul>
+     * @return size of memory in unit
+     */
+    public static double byte2MemorySize(final long byteSize,
+                                         @MemoryConstants.Unit final int unit) {
+        if (byteSize < 0) return -1;
+        return (double) byteSize / unit;
+    }
+
+    /**
+     * Size of byte to fit size of memory.
+     * <p>to three decimal places</p>
+     *
+     * @param byteSize Size of byte.
+     * @return fit size of memory
+     */
+    @SuppressLint("DefaultLocale")
+    public static String byte2FitMemorySize(final long byteSize) {
+        return byte2FitMemorySize(byteSize, 3);
+    }
+
+    /**
+     * Size of byte to fit size of memory.
+     * <p>to three decimal places</p>
+     *
+     * @param byteSize  Size of byte.
+     * @param precision The precision
+     * @return fit size of memory
+     */
+    @SuppressLint("DefaultLocale")
+    public static String byte2FitMemorySize(final long byteSize, int precision) {
+        if (precision < 0) {
+            throw new IllegalArgumentException("precision shouldn't be less than zero!");
+        }
+        if (byteSize < 0) {
+            throw new IllegalArgumentException("byteSize shouldn't be less than zero!");
+        } else if (byteSize < MemoryConstants.KB) {
+            return String.format("%." + precision + "fB", (double) byteSize);
+        } else if (byteSize < MemoryConstants.MB) {
+            return String.format("%." + precision + "fKB", (double) byteSize / MemoryConstants.KB);
+        } else if (byteSize < MemoryConstants.GB) {
+            return String.format("%." + precision + "fMB", (double) byteSize / MemoryConstants.MB);
+        } else {
+            return String.format("%." + precision + "fGB", (double) byteSize / MemoryConstants.GB);
+        }
+    }
+
+    /**
+     * Time span in unit to milliseconds.
+     *
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return milliseconds
+     */
+    public static long timeSpan2Millis(final long timeSpan, @TimeConstants.Unit final int unit) {
+        return timeSpan * unit;
+    }
+
+    /**
+     * Milliseconds to time span in unit.
+     *
+     * @param millis The milliseconds.
+     * @param unit   The unit of time span.
+     *               <ul>
+     *               <li>{@link TimeConstants#MSEC}</li>
+     *               <li>{@link TimeConstants#SEC }</li>
+     *               <li>{@link TimeConstants#MIN }</li>
+     *               <li>{@link TimeConstants#HOUR}</li>
+     *               <li>{@link TimeConstants#DAY }</li>
+     *               </ul>
+     * @return time span in unit
+     */
+    public static long millis2TimeSpan(final long millis, @TimeConstants.Unit final int unit) {
+        return millis / unit;
+    }
+
+    /**
+     * Milliseconds to fit time span.
+     *
+     * @param millis    The milliseconds.
+     *                  <p>millis &lt;= 0, return null</p>
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0, return null</li>
+     *                  <li>precision = 1, return 天</li>
+     *                  <li>precision = 2, return 天, 小时</li>
+     *                  <li>precision = 3, return 天, 小时, 分钟</li>
+     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>
+     *                  <li>precision &gt;= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
+     *                  </ul>
+     * @return fit time span
+     */
+    public static String millis2FitTimeSpan(long millis, int precision) {
+        return UtilsBridge.millis2FitTimeSpan(millis, precision);
+    }
+
+    /**
+     * Input stream to output stream.
+     */
+    public static ByteArrayOutputStream input2OutputStream(final InputStream is) {
+        if (is == null) return null;
+        try {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            byte[] b = new byte[BUFFER_SIZE];
+            int len;
+            while ((len = is.read(b, 0, BUFFER_SIZE)) != -1) {
+                os.write(b, 0, len);
+            }
+            return os;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Output stream to input stream.
+     */
+    public static ByteArrayInputStream output2InputStream(final OutputStream out) {
+        if (out == null) return null;
+        return new ByteArrayInputStream(((ByteArrayOutputStream) out).toByteArray());
+    }
+
+    /**
+     * Input stream to bytes.
+     */
+    public static byte[] inputStream2Bytes(final InputStream is) {
+        if (is == null) return null;
+        return input2OutputStream(is).toByteArray();
+    }
+
+    /**
+     * Bytes to input stream.
+     */
+    public static InputStream bytes2InputStream(final byte[] bytes) {
+        if (bytes == null || bytes.length <= 0) return null;
+        return new ByteArrayInputStream(bytes);
+    }
+
+    /**
+     * Output stream to bytes.
+     */
+    public static byte[] outputStream2Bytes(final OutputStream out) {
+        if (out == null) return null;
+        return ((ByteArrayOutputStream) out).toByteArray();
+    }
+
+    /**
+     * Bytes to output stream.
+     */
+    public static OutputStream bytes2OutputStream(final byte[] bytes) {
+        if (bytes == null || bytes.length <= 0) return null;
+        ByteArrayOutputStream os = null;
+        try {
+            os = new ByteArrayOutputStream();
+            os.write(bytes);
+            return os;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Input stream to string.
+     */
+    public static String inputStream2String(final InputStream is, final String charsetName) {
+        if (is == null) return "";
+        try {
+            ByteArrayOutputStream baos = input2OutputStream(is);
+            if (baos == null) return "";
+            return baos.toString(getSafeCharset(charsetName));
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * String to input stream.
+     */
+    public static InputStream string2InputStream(final String string, final String charsetName) {
+        if (string == null) return null;
+        try {
+            return new ByteArrayInputStream(string.getBytes(getSafeCharset(charsetName)));
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Output stream to string.
+     */
+    public static String outputStream2String(final OutputStream out, final String charsetName) {
+        if (out == null) return "";
+        try {
+            return new String(outputStream2Bytes(out), getSafeCharset(charsetName));
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * String to output stream.
+     */
+    public static OutputStream string2OutputStream(final String string, final String charsetName) {
+        if (string == null) return null;
+        try {
+            return bytes2OutputStream(string.getBytes(getSafeCharset(charsetName)));
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static List<String> inputStream2Lines(final InputStream is) {
+        return inputStream2Lines(is, "");
+    }
+
+    public static List<String> inputStream2Lines(final InputStream is,
+                                                 final String charsetName) {
+        BufferedReader reader = null;
+        try {
+            List<String> list = new ArrayList<>();
+            reader = new BufferedReader(new InputStreamReader(is, getSafeCharset(charsetName)));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                list.add(line);
+            }
+            return list;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Drawable to bitmap.
+     */
+    public static Bitmap drawable2Bitmap(final Drawable drawable) {
+        return UtilsBridge.drawable2Bitmap(drawable);
+    }
+
+    /**
+     * Bitmap to drawable.
+     */
+    public static Drawable bitmap2Drawable(final Bitmap bitmap) {
+        return UtilsBridge.bitmap2Drawable(bitmap);
+    }
+
+    /**
+     * View to bitmap.
+     */
+    public static Bitmap view2Bitmap(final View view) {
+        return UtilsBridge.view2Bitmap(view);
+    }
+
+    /**
+     * Value of dp to value of px.
+     */
+    public static int dp2px(final float dpValue) {
+        return UtilsBridge.dp2px(dpValue);
+    }
+
+    /**
+     * Value of px to value of dp.
+     */
+    public static int px2dp(final float pxValue) {
+        return UtilsBridge.px2dp(pxValue);
+    }
+
+    /**
+     * Value of sp to value of px.
+     */
+    public static int sp2px(final float spValue) {
+        return UtilsBridge.sp2px(spValue);
+    }
+
+    /**
+     * Value of px to value of sp.
+     */
+    public static int px2sp(final float pxValue) {
+        return UtilsBridge.px2sp(pxValue);
+    }
+
+    private static String getSafeCharset(String charsetName) {
+        String cn = charsetName;
+        if (UtilsBridge.isSpace(charsetName) || !Charset.isSupported(charsetName)) {
+            cn = "UTF-8";
+        }
+        return cn;
+    }
+}

+ 150 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/CrashUtils.java

@@ -0,0 +1,150 @@
+package com.cooleshow.base.utils;
+
+import androidx.annotation.NonNull;
+import java.io.File;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/27
+ *     desc  : utils about crash
+ * </pre>
+ */
+public final class CrashUtils {
+
+    private static final String FILE_SEP = System.getProperty("file.separator");
+
+    private static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER =
+        Thread.getDefaultUncaughtExceptionHandler();
+
+    private CrashUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Initialization.
+     */
+    public static void init() {
+        init("");
+    }
+
+    /**
+     * Initialization
+     *
+     * @param crashDir The directory of saving crash information.
+     */
+    public static void init(@NonNull final File crashDir) {
+        init(crashDir.getAbsolutePath(), null);
+    }
+
+    /**
+     * Initialization
+     *
+     * @param crashDirPath The directory's path of saving crash information.
+     */
+    public static void init(final String crashDirPath) {
+        init(crashDirPath, null);
+    }
+
+    /**
+     * Initialization
+     *
+     * @param onCrashListener The crash listener.
+     */
+    public static void init(final OnCrashListener onCrashListener) {
+        init("", onCrashListener);
+    }
+
+    /**
+     * Initialization
+     *
+     * @param crashDir The directory of saving crash information.
+     * @param onCrashListener The crash listener.
+     */
+    public static void init(@NonNull final File crashDir, final OnCrashListener onCrashListener) {
+        init(crashDir.getAbsolutePath(), onCrashListener);
+    }
+
+    /**
+     * Initialization
+     *
+     * @param crashDirPath The directory's path of saving crash information.
+     * @param onCrashListener The crash listener.
+     */
+    public static void init(final String crashDirPath, final OnCrashListener onCrashListener) {
+        String dirPath;
+        if (UtilsBridge.isSpace(crashDirPath)) {
+            if (UtilsBridge.isSDCardEnableByEnvironment()
+                && Utils.getApp().getExternalFilesDir(null) != null) {
+                dirPath = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + "crash" + FILE_SEP;
+            } else {
+                dirPath = Utils.getApp().getFilesDir() + FILE_SEP + "crash" + FILE_SEP;
+            }
+        } else {
+            dirPath = crashDirPath.endsWith(FILE_SEP) ? crashDirPath : crashDirPath + FILE_SEP;
+        }
+        Thread.setDefaultUncaughtExceptionHandler(
+            getUncaughtExceptionHandler(dirPath, onCrashListener));
+    }
+
+    private static UncaughtExceptionHandler getUncaughtExceptionHandler(final String dirPath,
+                                                                        final OnCrashListener onCrashListener) {
+        return new UncaughtExceptionHandler() {
+            @Override
+            public void uncaughtException(@NonNull final Thread t, @NonNull final Throwable e) {
+                final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss").format(new Date());
+                CrashInfo info = new CrashInfo(time, e);
+                final String crashFile = dirPath + time + ".txt";
+                UtilsBridge.writeFileFromString(crashFile, info.toString(), true);
+
+                if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
+                    DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(t, e);
+                }
+                if (onCrashListener != null) {
+                    onCrashListener.onCrash(info);
+                }
+            }
+        };
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // interface
+    ///////////////////////////////////////////////////////////////////////////
+
+    public interface OnCrashListener {
+        void onCrash(CrashInfo crashInfo);
+    }
+
+    public static final class CrashInfo {
+        private UtilsBridge.FileHead mFileHeadProvider;
+        private Throwable mThrowable;
+
+        private CrashInfo(String time, Throwable throwable) {
+            mThrowable = throwable;
+            mFileHeadProvider = new UtilsBridge.FileHead("Crash");
+            mFileHeadProvider.addFirst("Time Of Crash", time);
+        }
+
+        public final void addExtraHead(Map<String, String> extraHead) {
+            mFileHeadProvider.append(extraHead);
+        }
+
+        public final void addExtraHead(String key, String value) {
+            mFileHeadProvider.append(key, value);
+        }
+
+        public final Throwable getThrowable() {
+            return mThrowable;
+        }
+
+        @Override
+        public String toString() {
+            return mFileHeadProvider.toString() + UtilsBridge.getFullStackTrace(mThrowable);
+        }
+    }
+}

+ 86 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/DebouncingUtils.java

@@ -0,0 +1,86 @@
+package com.cooleshow.base.utils;
+
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.view.View;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2020/09/01
+ *     desc  : utils about debouncing
+ * </pre>
+ */
+public class DebouncingUtils {
+
+    private static final int               CACHE_SIZE               = 64;
+    private static final Map<String, Long> KEY_MILLIS_MAP           = new ConcurrentHashMap<>(CACHE_SIZE);
+    private static final long              DEBOUNCING_DEFAULT_VALUE = 1000;
+
+    private DebouncingUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether the view is not in a jitter state.
+     *
+     * @param view The view.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isValid(@NonNull final View view) {
+        return isValid(view, DEBOUNCING_DEFAULT_VALUE);
+    }
+
+    /**
+     * Return whether the view is not in a jitter state.
+     *
+     * @param view     The view.
+     * @param duration The duration.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isValid(@NonNull final View view, final long duration) {
+        return isValid(String.valueOf(view.hashCode()), duration);
+    }
+
+    /**
+     * Return whether the key is not in a jitter state.
+     *
+     * @param key      The key.
+     * @param duration The duration.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isValid(@NonNull String key, final long duration) {
+        if (TextUtils.isEmpty(key)) {
+            throw new IllegalArgumentException("The key is null.");
+        }
+        if (duration < 0) {
+            throw new IllegalArgumentException("The duration is less than 0.");
+        }
+        long curTime = SystemClock.elapsedRealtime();
+        clearIfNecessary(curTime);
+        Long validTime = KEY_MILLIS_MAP.get(key);
+        if (validTime == null || curTime >= validTime) {
+            KEY_MILLIS_MAP.put(key, curTime + duration);
+            return true;
+        }
+        return false;
+    }
+
+    private static void clearIfNecessary(long curTime) {
+        if (KEY_MILLIS_MAP.size() < CACHE_SIZE) return;
+        for (Iterator<Map.Entry<String, Long>> it = KEY_MILLIS_MAP.entrySet().iterator(); it.hasNext(); ) {
+            Map.Entry<String, Long> entry = it.next();
+            Long validTime = entry.getValue();
+            if (curTime >= validTime) {
+                it.remove();
+            }
+        }
+    }
+}

+ 524 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/DeviceUtils.java

@@ -0,0 +1,524 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.UUID;
+
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+import static android.Manifest.permission.INTERNET;
+import static android.content.Context.WIFI_SERVICE;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/8/1
+ *     desc  : utils about device
+ * </pre>
+ */
+public final class DeviceUtils {
+
+    private DeviceUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether device is rooted.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isDeviceRooted() {
+        String su = "su";
+        String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/",
+                "/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/",
+                "/system/sbin/", "/usr/bin/", "/vendor/bin/"};
+        for (String location : locations) {
+            if (new File(location + su).exists()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return whether ADB is enabled.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static boolean isAdbEnabled() {
+        return Settings.Secure.getInt(
+                Utils.getApp().getContentResolver(),
+                Settings.Global.ADB_ENABLED, 0
+        ) > 0;
+    }
+
+    /**
+     * Return the version name of device's system.
+     *
+     * @return the version name of device's system
+     */
+    public static String getSDKVersionName() {
+        return android.os.Build.VERSION.RELEASE;
+    }
+
+    /**
+     * Return version code of device's system.
+     *
+     * @return version code of device's system
+     */
+    public static int getSDKVersionCode() {
+        return android.os.Build.VERSION.SDK_INT;
+    }
+
+    /**
+     * Return the android id of device.
+     *
+     * @return the android id of device
+     */
+    @SuppressLint("HardwareIds")
+    public static String getAndroidID() {
+        String id = Settings.Secure.getString(
+                Utils.getApp().getContentResolver(),
+                Settings.Secure.ANDROID_ID
+        );
+        if ("9774d56d682e549c".equals(id)) return "";
+        return id == null ? "" : id;
+    }
+
+    /**
+     * Return the MAC address.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />},
+     * {@code <uses-permission android:name="android.permission.INTERNET" />},
+     * {@code <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
+     *
+     * @return the MAC address
+     */
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE})
+    public static String getMacAddress() {
+        String macAddress = getMacAddress((String[]) null);
+        if (!TextUtils.isEmpty(macAddress) || getWifiEnabled()) return macAddress;
+        setWifiEnabled(true);
+        setWifiEnabled(false);
+        return getMacAddress((String[]) null);
+    }
+
+    private static boolean getWifiEnabled() {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
+        if (manager == null) return false;
+        return manager.isWifiEnabled();
+    }
+
+    /**
+     * Enable or disable wifi.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
+     *
+     * @param enabled True to enabled, false otherwise.
+     */
+    @RequiresPermission(CHANGE_WIFI_STATE)
+    private static void setWifiEnabled(final boolean enabled) {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
+        if (manager == null) return;
+        if (enabled == manager.isWifiEnabled()) return;
+        manager.setWifiEnabled(enabled);
+    }
+
+    /**
+     * Return the MAC address.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />},
+     * {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @return the MAC address
+     */
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE})
+    public static String getMacAddress(final String... excepts) {
+        String macAddress = getMacAddressByNetworkInterface();
+        if (isAddressNotInExcepts(macAddress, excepts)) {
+            return macAddress;
+        }
+        macAddress = getMacAddressByInetAddress();
+        if (isAddressNotInExcepts(macAddress, excepts)) {
+            return macAddress;
+        }
+        macAddress = getMacAddressByWifiInfo();
+        if (isAddressNotInExcepts(macAddress, excepts)) {
+            return macAddress;
+        }
+        macAddress = getMacAddressByFile();
+        if (isAddressNotInExcepts(macAddress, excepts)) {
+            return macAddress;
+        }
+        return "";
+    }
+
+    private static boolean isAddressNotInExcepts(final String address, final String... excepts) {
+        if (TextUtils.isEmpty(address)) {
+            return false;
+        }
+        if ("02:00:00:00:00:00".equals(address)) {
+            return false;
+        }
+        if (excepts == null || excepts.length == 0) {
+            return true;
+        }
+        for (String filter : excepts) {
+            if (filter != null && filter.equals(address)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    private static String getMacAddressByWifiInfo() {
+        try {
+            final WifiManager wifi = (WifiManager) Utils.getApp()
+                    .getApplicationContext().getSystemService(WIFI_SERVICE);
+            if (wifi != null) {
+                final WifiInfo info = wifi.getConnectionInfo();
+                if (info != null) {
+                    @SuppressLint("HardwareIds")
+                    String macAddress = info.getMacAddress();
+                    if (!TextUtils.isEmpty(macAddress)) {
+                        return macAddress;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "02:00:00:00:00:00";
+    }
+
+    private static String getMacAddressByNetworkInterface() {
+        try {
+            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
+            while (nis.hasMoreElements()) {
+                NetworkInterface ni = nis.nextElement();
+                if (ni == null || !ni.getName().equalsIgnoreCase("wlan0")) continue;
+                byte[] macBytes = ni.getHardwareAddress();
+                if (macBytes != null && macBytes.length > 0) {
+                    StringBuilder sb = new StringBuilder();
+                    for (byte b : macBytes) {
+                        sb.append(String.format("%02x:", b));
+                    }
+                    return sb.substring(0, sb.length() - 1);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "02:00:00:00:00:00";
+    }
+
+    private static String getMacAddressByInetAddress() {
+        try {
+            InetAddress inetAddress = getInetAddress();
+            if (inetAddress != null) {
+                NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress);
+                if (ni != null) {
+                    byte[] macBytes = ni.getHardwareAddress();
+                    if (macBytes != null && macBytes.length > 0) {
+                        StringBuilder sb = new StringBuilder();
+                        for (byte b : macBytes) {
+                            sb.append(String.format("%02x:", b));
+                        }
+                        return sb.substring(0, sb.length() - 1);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "02:00:00:00:00:00";
+    }
+
+    private static InetAddress getInetAddress() {
+        try {
+            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
+            while (nis.hasMoreElements()) {
+                NetworkInterface ni = nis.nextElement();
+                // To prevent phone of xiaomi return "10.0.2.15"
+                if (!ni.isUp()) continue;
+                Enumeration<InetAddress> addresses = ni.getInetAddresses();
+                while (addresses.hasMoreElements()) {
+                    InetAddress inetAddress = addresses.nextElement();
+                    if (!inetAddress.isLoopbackAddress()) {
+                        String hostAddress = inetAddress.getHostAddress();
+                        if (hostAddress.indexOf(':') < 0) return inetAddress;
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private static String getMacAddressByFile() {
+        ShellUtils.CommandResult result = UtilsBridge.execCmd("getprop wifi.interface", false);
+        if (result.result == 0) {
+            String name = result.successMsg;
+            if (name != null) {
+                result = UtilsBridge.execCmd("cat /sys/class/net/" + name + "/address", false);
+                if (result.result == 0) {
+                    String address = result.successMsg;
+                    if (address != null && address.length() > 0) {
+                        return address;
+                    }
+                }
+            }
+        }
+        return "02:00:00:00:00:00";
+    }
+
+    /**
+     * Return the manufacturer of the product/hardware.
+     * <p>e.g. Xiaomi</p>
+     *
+     * @return the manufacturer of the product/hardware
+     */
+    public static String getManufacturer() {
+        return Build.MANUFACTURER;
+    }
+
+    /**
+     * Return the model of device.
+     * <p>e.g. MI2SC</p>
+     *
+     * @return the model of device
+     */
+    public static String getModel() {
+        String model = Build.MODEL;
+        if (model != null) {
+            model = model.trim().replaceAll("\\s*", "");
+        } else {
+            model = "";
+        }
+        return model;
+    }
+
+    /**
+     * Return an ordered list of ABIs supported by this device. The most preferred ABI is the first
+     * element in the list.
+     *
+     * @return an ordered list of ABIs supported by this device
+     */
+    public static String[] getABIs() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return Build.SUPPORTED_ABIS;
+        } else {
+            if (!TextUtils.isEmpty(Build.CPU_ABI2)) {
+                return new String[]{Build.CPU_ABI, Build.CPU_ABI2};
+            }
+            return new String[]{Build.CPU_ABI};
+        }
+    }
+
+    /**
+     * Return whether device is tablet.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isTablet() {
+        return (Resources.getSystem().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK)
+                >= Configuration.SCREENLAYOUT_SIZE_LARGE;
+    }
+
+    /**
+     * Return whether device is emulator.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isEmulator() {
+        boolean checkProperty = Build.FINGERPRINT.startsWith("generic")
+                || Build.FINGERPRINT.toLowerCase().contains("vbox")
+                || Build.FINGERPRINT.toLowerCase().contains("test-keys")
+                || Build.MODEL.contains("google_sdk")
+                || Build.MODEL.contains("Emulator")
+                || Build.MODEL.contains("Android SDK built for x86")
+                || Build.MANUFACTURER.contains("Genymotion")
+                || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
+                || "google_sdk".equals(Build.PRODUCT);
+        if (checkProperty) return true;
+
+        String operatorName = "";
+        TelephonyManager tm = (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm != null) {
+            String name = tm.getNetworkOperatorName();
+            if (name != null) {
+                operatorName = name;
+            }
+        }
+        boolean checkOperatorName = operatorName.toLowerCase().equals("android");
+        if (checkOperatorName) return true;
+
+        String url = "tel:" + "123456";
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(url));
+        intent.setAction(Intent.ACTION_DIAL);
+        boolean checkDial = intent.resolveActivity(Utils.getApp().getPackageManager()) == null;
+        if (checkDial) return true;
+
+//        boolean checkDebuggerConnected = Debug.isDebuggerConnected();
+//        if (checkDebuggerConnected) return true;
+
+        return false;
+    }
+
+    /**
+     * Whether user has enabled development settings.
+     *
+     * @return whether user has enabled development settings.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static boolean isDevelopmentSettingsEnabled() {
+        return Settings.Global.getInt(
+                Utils.getApp().getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0
+        ) > 0;
+    }
+
+
+    private static final    String KEY_UDID = "KEY_UDID";
+    private volatile static String udid;
+
+    /**
+     * Return the unique device id.
+     * <pre>{1}{UUID(macAddress)}</pre>
+     * <pre>{2}{UUID(androidId )}</pre>
+     * <pre>{9}{UUID(random    )}</pre>
+     *
+     * @return the unique device id
+     */
+    public static String getUniqueDeviceId() {
+        return getUniqueDeviceId("", true);
+    }
+
+    /**
+     * Return the unique device id.
+     * <pre>android 10 deprecated {prefix}{1}{UUID(macAddress)}</pre>
+     * <pre>{prefix}{2}{UUID(androidId )}</pre>
+     * <pre>{prefix}{9}{UUID(random    )}</pre>
+     *
+     * @param prefix The prefix of the unique device id.
+     * @return the unique device id
+     */
+    public static String getUniqueDeviceId(String prefix) {
+        return getUniqueDeviceId(prefix, true);
+    }
+
+    /**
+     * Return the unique device id.
+     * <pre>{1}{UUID(macAddress)}</pre>
+     * <pre>{2}{UUID(androidId )}</pre>
+     * <pre>{9}{UUID(random    )}</pre>
+     *
+     * @param useCache True to use cache, false otherwise.
+     * @return the unique device id
+     */
+    public static String getUniqueDeviceId(boolean useCache) {
+        return getUniqueDeviceId("", useCache);
+    }
+
+    /**
+     * Return the unique device id.
+     * <pre>android 10 deprecated {prefix}{1}{UUID(macAddress)}</pre>
+     * <pre>{prefix}{2}{UUID(androidId )}</pre>
+     * <pre>{prefix}{9}{UUID(random    )}</pre>
+     *
+     * @param prefix   The prefix of the unique device id.
+     * @param useCache True to use cache, false otherwise.
+     * @return the unique device id
+     */
+    public static String getUniqueDeviceId(String prefix, boolean useCache) {
+        if (!useCache) {
+            return getUniqueDeviceIdReal(prefix);
+        }
+        if (udid == null) {
+            synchronized (DeviceUtils.class) {
+                if (udid == null) {
+                    final String id = UtilsBridge.getSpUtils4Utils().getString(KEY_UDID, null);
+                    if (id != null) {
+                        udid = id;
+                        return udid;
+                    }
+                    return getUniqueDeviceIdReal(prefix);
+                }
+            }
+        }
+        return udid;
+    }
+
+    private static String getUniqueDeviceIdReal(String prefix) {
+        try {
+            final String androidId = getAndroidID();
+            if (!TextUtils.isEmpty(androidId)) {
+                return saveUdid(prefix + 2, androidId);
+            }
+
+        } catch (Exception ignore) {/**/}
+        return saveUdid(prefix + 9, "");
+    }
+
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET, CHANGE_WIFI_STATE})
+    public static boolean isSameDevice(final String uniqueDeviceId) {
+        // {prefix}{type}{32id}
+        if (TextUtils.isEmpty(uniqueDeviceId) && uniqueDeviceId.length() < 33) return false;
+        if (uniqueDeviceId.equals(udid)) return true;
+        final String cachedId = UtilsBridge.getSpUtils4Utils().getString(KEY_UDID, null);
+        if (uniqueDeviceId.equals(cachedId)) return true;
+        int st = uniqueDeviceId.length() - 33;
+        String type = uniqueDeviceId.substring(st, st + 1);
+        if (type.startsWith("1")) {
+            String macAddress = getMacAddress();
+            if (macAddress.equals("")) {
+                return false;
+            }
+            return uniqueDeviceId.substring(st + 1).equals(getUdid("", macAddress));
+        } else if (type.startsWith("2")) {
+            final String androidId = getAndroidID();
+            if (TextUtils.isEmpty(androidId)) {
+                return false;
+            }
+            return uniqueDeviceId.substring(st + 1).equals(getUdid("", androidId));
+        }
+        return false;
+    }
+
+    private static String saveUdid(String prefix, String id) {
+        udid = getUdid(prefix, id);
+        UtilsBridge.getSpUtils4Utils().put(KEY_UDID, udid);
+        return udid;
+    }
+
+    private static String getUdid(String prefix, String id) {
+        if (id.equals("")) {
+            return prefix + UUID.randomUUID().toString().replace("-", "");
+        }
+        return prefix + UUID.nameUUIDFromBytes(id.getBytes()).toString().replace("-", "");
+    }
+}

+ 130 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/DialogUtils.java

@@ -0,0 +1,130 @@
+//package com.cooleshow.base.utils;
+//
+//import android.app.Activity;
+//import android.app.Dialog;
+//import android.content.Context;
+//import android.content.ContextWrapper;
+//import android.graphics.drawable.ColorDrawable;
+//import android.os.Build;
+//import android.support.annotation.LayoutRes;
+//import android.support.annotation.NonNull;
+//import android.util.Log;
+//import android.view.LayoutInflater;
+//import android.view.View;
+//import android.view.Window;
+//
+//import java.util.HashMap;
+//import java.util.TreeSet;
+//
+///**
+// * <pre>
+// *     author: blankj
+// *     blog  : http://blankj.com
+// *     time  : 2019/08/26
+// *     desc  : utils about dialog
+// * </pre>
+// */
+//public class DialogUtils {
+//
+//    private DialogUtils() {
+//        throw new UnsupportedOperationException("u can't instantiate me...");
+//    }
+//
+//    public static void show(final Dialog dialog) {
+//        if (dialog == null) return;
+//        Utils.runOnUiThread(new Runnable() {
+//            @Override
+//            public void run() {
+//                Activity activity = getActivityByContext(dialog.getContext());
+//                if (!isActivityAlive(activity)) return;
+//                dialog.show();
+//            }
+//        });
+//    }
+//
+//    public static void dismiss(final Dialog dialog) {
+//        if (dialog == null) return;
+//        Utils.runOnUiThread(new Runnable() {
+//            @Override
+//            public void run() {
+//                dialog.dismiss();
+//            }
+//        });
+//    }
+//
+//    public static void show(final Utils.TransActivityDelegate delegate) {
+//        Utils.TransActivity.start(null, delegate);
+//    }
+//
+//    public static Dialog create(Activity activity, @LayoutRes int layoutId) {
+//        Dialog dialog = new Dialog(activity);
+//        View dialogContent = LayoutInflater.from(activity).inflate(layoutId, null);
+//
+//        dialog.setContentView(dialogContent);
+//        Window window = dialog.getWindow();
+//        if (window != null) {
+//            window.setBackgroundDrawable(new ColorDrawable(0));
+//        }
+//
+//        return dialog;
+//    }
+//
+//    private static boolean isActivityAlive(final Activity activity) {
+//        return activity != null && !activity.isFinishing()
+//                && (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed());
+//    }
+//
+//    private static Activity getActivityByContext(Context context) {
+//        if (context instanceof Activity) return (Activity) context;
+//        while (context instanceof ContextWrapper) {
+//            if (context instanceof Activity) {
+//                return (Activity) context;
+//            }
+//            context = ((ContextWrapper) context).getBaseContext();
+//        }
+//        return null;
+//    }
+//
+//    public static final class UtilsDialog extends Dialog {
+//
+//        private int mPriority = 5;
+//
+//        public UtilsDialog(@NonNull Context context) {
+//            this(context, 0);
+//        }
+//
+//        public UtilsDialog(@NonNull Context context, int themeResId) {
+//            super(context, themeResId);
+//        }
+//
+//        @Override
+//        public void show() {
+//            Utils.runOnUiThread(new Runnable() {
+//                @Override
+//                public void run() {
+//                    Activity activity = getActivityByContext(getContext());
+//                    if (!isActivityAlive(activity)) {
+//                        Log.w("DialogUtils", "Activity is not alive.");
+//                        return;
+//                    }
+//                    UtilsDialog.super.show();
+//                }
+//            });
+//        }
+//
+//        @Override
+//        public void dismiss() {
+//            Utils.runOnUiThread(new Runnable() {
+//                @Override
+//                public void run() {
+//                    UtilsDialog.super.dismiss();
+//                }
+//            });
+//        }
+//
+//        public void show(int priority) {
+//            mPriority = priority;
+//            show();
+//        }
+//    }
+//}

+ 217 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/EncodeUtils.java

@@ -0,0 +1,217 @@
+package com.cooleshow.base.utils;
+
+import android.os.Build;
+import android.text.Html;
+import android.util.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/07
+ *     desc  : utils about encode
+ * </pre>
+ */
+public final class EncodeUtils {
+
+    private EncodeUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the urlencoded string.
+     *
+     * @param input The input.
+     * @return the urlencoded string
+     */
+    public static String urlEncode(final String input) {
+        return urlEncode(input, "UTF-8");
+    }
+
+    /**
+     * Return the urlencoded string.
+     *
+     * @param input       The input.
+     * @param charsetName The name of charset.
+     * @return the urlencoded string
+     */
+    public static String urlEncode(final String input, final String charsetName) {
+        if (input == null || input.length() == 0) return "";
+        try {
+            return URLEncoder.encode(input, charsetName);
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Return the string of decode urlencoded string.
+     *
+     * @param input The input.
+     * @return the string of decode urlencoded string
+     */
+    public static String urlDecode(final String input) {
+        return urlDecode(input, "UTF-8");
+    }
+
+    /**
+     * Return the string of decode urlencoded string.
+     *
+     * @param input       The input.
+     * @param charsetName The name of charset.
+     * @return the string of decode urlencoded string
+     */
+    public static String urlDecode(final String input, final String charsetName) {
+        if (input == null || input.length() == 0) return "";
+        try {
+            String safeInput = input.replaceAll("%(?![0-9a-fA-F]{2})", "%25").replaceAll("\\+", "%2B");
+            return URLDecoder.decode(safeInput, charsetName);
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Return Base64-encode bytes.
+     *
+     * @param input The input.
+     * @return Base64-encode bytes
+     */
+    public static byte[] base64Encode(final String input) {
+        return base64Encode(input.getBytes());
+    }
+
+    /**
+     * Return Base64-encode bytes.
+     *
+     * @param input The input.
+     * @return Base64-encode bytes
+     */
+    public static byte[] base64Encode(final byte[] input) {
+        if (input == null || input.length == 0) return new byte[0];
+        return Base64.encode(input, Base64.NO_WRAP);
+    }
+
+    /**
+     * Return Base64-encode string.
+     *
+     * @param input The input.
+     * @return Base64-encode string
+     */
+    public static String base64Encode2String(final byte[] input) {
+        if (input == null || input.length == 0) return "";
+        return Base64.encodeToString(input, Base64.NO_WRAP);
+    }
+
+    /**
+     * Return the bytes of decode Base64-encode string.
+     *
+     * @param input The input.
+     * @return the string of decode Base64-encode string
+     */
+    public static byte[] base64Decode(final String input) {
+        if (input == null || input.length() == 0) return new byte[0];
+        return Base64.decode(input, Base64.NO_WRAP);
+    }
+
+    /**
+     * Return the bytes of decode Base64-encode bytes.
+     *
+     * @param input The input.
+     * @return the bytes of decode Base64-encode bytes
+     */
+    public static byte[] base64Decode(final byte[] input) {
+        if (input == null || input.length == 0) return new byte[0];
+        return Base64.decode(input, Base64.NO_WRAP);
+    }
+
+    /**
+     * Return html-encode string.
+     *
+     * @param input The input.
+     * @return html-encode string
+     */
+    public static String htmlEncode(final CharSequence input) {
+        if (input == null || input.length() == 0) return "";
+        StringBuilder sb = new StringBuilder();
+        char c;
+        for (int i = 0, len = input.length(); i < len; i++) {
+            c = input.charAt(i);
+            switch (c) {
+                case '<':
+                    sb.append("&lt;"); //$NON-NLS-1$
+                    break;
+                case '>':
+                    sb.append("&gt;"); //$NON-NLS-1$
+                    break;
+                case '&':
+                    sb.append("&amp;"); //$NON-NLS-1$
+                    break;
+                case '\'':
+                    //http://www.w3.org/TR/xhtml1
+                    // The named character reference &apos; (the apostrophe, U+0027) was
+                    // introduced in XML 1.0 but does not appear in HTML. Authors should
+                    // therefore use &#39; instead of &apos; to work as expected in HTML 4
+                    // user agents.
+                    sb.append("&#39;"); //$NON-NLS-1$
+                    break;
+                case '"':
+                    sb.append("&quot;"); //$NON-NLS-1$
+                    break;
+                default:
+                    sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Return the string of decode html-encode string.
+     *
+     * @param input The input.
+     * @return the string of decode html-encode string
+     */
+    public static CharSequence htmlDecode(final String input) {
+        if (input == null || input.length() == 0) return "";
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            return Html.fromHtml(input, Html.FROM_HTML_MODE_LEGACY);
+        } else {
+            return Html.fromHtml(input);
+        }
+    }
+
+    /**
+     * Return the binary encoded string padded with one space
+     *
+     * @param input The input.
+     * @return binary string
+     */
+    public static String binaryEncode(final String input) {
+        if (input == null || input.length() == 0) return "";
+        StringBuilder sb = new StringBuilder();
+        for (char i : input.toCharArray()) {
+            sb.append(Integer.toBinaryString(i)).append(" ");
+        }
+        return sb.deleteCharAt(sb.length() - 1).toString();
+    }
+
+    /**
+     * Return UTF-8 String from binary
+     *
+     * @param input binary string
+     * @return UTF-8 String
+     */
+    public static String binaryDecode(final String input) {
+        if (input == null || input.length() == 0) return "";
+        String[] splits = input.split(" ");
+        StringBuilder sb = new StringBuilder();
+        for (String split : splits) {
+            sb.append(((char) Integer.parseInt(split, 2)));
+        }
+        return sb.toString();
+    }
+}

+ 1184 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/EncryptUtils.java

@@ -0,0 +1,1184 @@
+package com.cooleshow.base.utils;
+
+import android.os.Build;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.DigestInputStream;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about encrypt
+ * </pre>
+ */
+public final class EncryptUtils {
+
+    private EncryptUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // hash encryption
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the hex string of MD2 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of MD2 encryption
+     */
+    public static String encryptMD2ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptMD2ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of MD2 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of MD2 encryption
+     */
+    public static String encryptMD2ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptMD2(data));
+    }
+
+    /**
+     * Return the bytes of MD2 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of MD2 encryption
+     */
+    public static byte[] encryptMD2(final byte[] data) {
+        return hashTemplate(data, "MD2");
+    }
+
+    /**
+     * Return the hex string of MD5 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of MD5 encryption
+     */
+    public static String encryptMD5ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptMD5ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of MD5 encryption.
+     *
+     * @param data The data.
+     * @param salt The salt.
+     * @return the hex string of MD5 encryption
+     */
+    public static String encryptMD5ToString(final String data, final String salt) {
+        if (data == null && salt == null) return "";
+        if (salt == null) return UtilsBridge.bytes2HexString(encryptMD5(data.getBytes()));
+        if (data == null) return UtilsBridge.bytes2HexString(encryptMD5(salt.getBytes()));
+        return UtilsBridge.bytes2HexString(encryptMD5((data + salt).getBytes()));
+    }
+
+    /**
+     * Return the hex string of MD5 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of MD5 encryption
+     */
+    public static String encryptMD5ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptMD5(data));
+    }
+
+    /**
+     * Return the hex string of MD5 encryption.
+     *
+     * @param data The data.
+     * @param salt The salt.
+     * @return the hex string of MD5 encryption
+     */
+    public static String encryptMD5ToString(final byte[] data, final byte[] salt) {
+        if (data == null && salt == null) return "";
+        if (salt == null) return UtilsBridge.bytes2HexString(encryptMD5(data));
+        if (data == null) return UtilsBridge.bytes2HexString(encryptMD5(salt));
+        byte[] dataSalt = new byte[data.length + salt.length];
+        System.arraycopy(data, 0, dataSalt, 0, data.length);
+        System.arraycopy(salt, 0, dataSalt, data.length, salt.length);
+        return UtilsBridge.bytes2HexString(encryptMD5(dataSalt));
+    }
+
+    /**
+     * Return the bytes of MD5 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of MD5 encryption
+     */
+    public static byte[] encryptMD5(final byte[] data) {
+        return hashTemplate(data, "MD5");
+    }
+
+    /**
+     * Return the hex string of file's MD5 encryption.
+     *
+     * @param filePath The path of file.
+     * @return the hex string of file's MD5 encryption
+     */
+    public static String encryptMD5File2String(final String filePath) {
+        File file = UtilsBridge.isSpace(filePath) ? null : new File(filePath);
+        return encryptMD5File2String(file);
+    }
+
+    /**
+     * Return the bytes of file's MD5 encryption.
+     *
+     * @param filePath The path of file.
+     * @return the bytes of file's MD5 encryption
+     */
+    public static byte[] encryptMD5File(final String filePath) {
+        File file = UtilsBridge.isSpace(filePath) ? null : new File(filePath);
+        return encryptMD5File(file);
+    }
+
+    /**
+     * Return the hex string of file's MD5 encryption.
+     *
+     * @param file The file.
+     * @return the hex string of file's MD5 encryption
+     */
+    public static String encryptMD5File2String(final File file) {
+        return UtilsBridge.bytes2HexString(encryptMD5File(file));
+    }
+
+    /**
+     * Return the bytes of file's MD5 encryption.
+     *
+     * @param file The file.
+     * @return the bytes of file's MD5 encryption
+     */
+    public static byte[] encryptMD5File(final File file) {
+        if (file == null) return null;
+        FileInputStream fis = null;
+        DigestInputStream digestInputStream;
+        try {
+            fis = new FileInputStream(file);
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            digestInputStream = new DigestInputStream(fis, md);
+            byte[] buffer = new byte[256 * 1024];
+            while (true) {
+                if (!(digestInputStream.read(buffer) > 0)) break;
+            }
+            md = digestInputStream.getMessageDigest();
+            return md.digest();
+        } catch (NoSuchAlgorithmException | IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (fis != null) {
+                    fis.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Return the hex string of SHA1 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA1 encryption
+     */
+    public static String encryptSHA1ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptSHA1ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of SHA1 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA1 encryption
+     */
+    public static String encryptSHA1ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptSHA1(data));
+    }
+
+    /**
+     * Return the bytes of SHA1 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of SHA1 encryption
+     */
+    public static byte[] encryptSHA1(final byte[] data) {
+        return hashTemplate(data, "SHA-1");
+    }
+
+    /**
+     * Return the hex string of SHA224 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA224 encryption
+     */
+    public static String encryptSHA224ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptSHA224ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of SHA224 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA224 encryption
+     */
+    public static String encryptSHA224ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptSHA224(data));
+    }
+
+    /**
+     * Return the bytes of SHA224 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of SHA224 encryption
+     */
+    public static byte[] encryptSHA224(final byte[] data) {
+        return hashTemplate(data, "SHA224");
+    }
+
+    /**
+     * Return the hex string of SHA256 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA256 encryption
+     */
+    public static String encryptSHA256ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptSHA256ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of SHA256 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA256 encryption
+     */
+    public static String encryptSHA256ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptSHA256(data));
+    }
+
+    /**
+     * Return the bytes of SHA256 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of SHA256 encryption
+     */
+    public static byte[] encryptSHA256(final byte[] data) {
+        return hashTemplate(data, "SHA-256");
+    }
+
+    /**
+     * Return the hex string of SHA384 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA384 encryption
+     */
+    public static String encryptSHA384ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptSHA384ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of SHA384 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA384 encryption
+     */
+    public static String encryptSHA384ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptSHA384(data));
+    }
+
+    /**
+     * Return the bytes of SHA384 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of SHA384 encryption
+     */
+    public static byte[] encryptSHA384(final byte[] data) {
+        return hashTemplate(data, "SHA-384");
+    }
+
+    /**
+     * Return the hex string of SHA512 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA512 encryption
+     */
+    public static String encryptSHA512ToString(final String data) {
+        if (data == null || data.length() == 0) return "";
+        return encryptSHA512ToString(data.getBytes());
+    }
+
+    /**
+     * Return the hex string of SHA512 encryption.
+     *
+     * @param data The data.
+     * @return the hex string of SHA512 encryption
+     */
+    public static String encryptSHA512ToString(final byte[] data) {
+        return UtilsBridge.bytes2HexString(encryptSHA512(data));
+    }
+
+    /**
+     * Return the bytes of SHA512 encryption.
+     *
+     * @param data The data.
+     * @return the bytes of SHA512 encryption
+     */
+    public static byte[] encryptSHA512(final byte[] data) {
+        return hashTemplate(data, "SHA-512");
+    }
+
+    /**
+     * Return the bytes of hash encryption.
+     *
+     * @param data      The data.
+     * @param algorithm The name of hash encryption.
+     * @return the bytes of hash encryption
+     */
+    static byte[] hashTemplate(final byte[] data, final String algorithm) {
+        if (data == null || data.length <= 0) return null;
+        try {
+            MessageDigest md = MessageDigest.getInstance(algorithm);
+            md.update(data);
+            return md.digest();
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // hmac encryption
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the hex string of HmacMD5 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacMD5 encryption
+     */
+    public static String encryptHmacMD5ToString(final String data, final String key) {
+        if (data == null || data.length() == 0 || key == null || key.length() == 0) return "";
+        return encryptHmacMD5ToString(data.getBytes(), key.getBytes());
+    }
+
+    /**
+     * Return the hex string of HmacMD5 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacMD5 encryption
+     */
+    public static String encryptHmacMD5ToString(final byte[] data, final byte[] key) {
+        return UtilsBridge.bytes2HexString(encryptHmacMD5(data, key));
+    }
+
+    /**
+     * Return the bytes of HmacMD5 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the bytes of HmacMD5 encryption
+     */
+    public static byte[] encryptHmacMD5(final byte[] data, final byte[] key) {
+        return hmacTemplate(data, key, "HmacMD5");
+    }
+
+    /**
+     * Return the hex string of HmacSHA1 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA1 encryption
+     */
+    public static String encryptHmacSHA1ToString(final String data, final String key) {
+        if (data == null || data.length() == 0 || key == null || key.length() == 0) return "";
+        return encryptHmacSHA1ToString(data.getBytes(), key.getBytes());
+    }
+
+    /**
+     * Return the hex string of HmacSHA1 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA1 encryption
+     */
+    public static String encryptHmacSHA1ToString(final byte[] data, final byte[] key) {
+        return UtilsBridge.bytes2HexString(encryptHmacSHA1(data, key));
+    }
+
+    /**
+     * Return the bytes of HmacSHA1 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the bytes of HmacSHA1 encryption
+     */
+    public static byte[] encryptHmacSHA1(final byte[] data, final byte[] key) {
+        return hmacTemplate(data, key, "HmacSHA1");
+    }
+
+    /**
+     * Return the hex string of HmacSHA224 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA224 encryption
+     */
+    public static String encryptHmacSHA224ToString(final String data, final String key) {
+        if (data == null || data.length() == 0 || key == null || key.length() == 0) return "";
+        return encryptHmacSHA224ToString(data.getBytes(), key.getBytes());
+    }
+
+    /**
+     * Return the hex string of HmacSHA224 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA224 encryption
+     */
+    public static String encryptHmacSHA224ToString(final byte[] data, final byte[] key) {
+        return UtilsBridge.bytes2HexString(encryptHmacSHA224(data, key));
+    }
+
+    /**
+     * Return the bytes of HmacSHA224 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the bytes of HmacSHA224 encryption
+     */
+    public static byte[] encryptHmacSHA224(final byte[] data, final byte[] key) {
+        return hmacTemplate(data, key, "HmacSHA224");
+    }
+
+    /**
+     * Return the hex string of HmacSHA256 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA256 encryption
+     */
+    public static String encryptHmacSHA256ToString(final String data, final String key) {
+        if (data == null || data.length() == 0 || key == null || key.length() == 0) return "";
+        return encryptHmacSHA256ToString(data.getBytes(), key.getBytes());
+    }
+
+    /**
+     * Return the hex string of HmacSHA256 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA256 encryption
+     */
+    public static String encryptHmacSHA256ToString(final byte[] data, final byte[] key) {
+        return UtilsBridge.bytes2HexString(encryptHmacSHA256(data, key));
+    }
+
+    /**
+     * Return the bytes of HmacSHA256 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the bytes of HmacSHA256 encryption
+     */
+    public static byte[] encryptHmacSHA256(final byte[] data, final byte[] key) {
+        return hmacTemplate(data, key, "HmacSHA256");
+    }
+
+    /**
+     * Return the hex string of HmacSHA384 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA384 encryption
+     */
+    public static String encryptHmacSHA384ToString(final String data, final String key) {
+        if (data == null || data.length() == 0 || key == null || key.length() == 0) return "";
+        return encryptHmacSHA384ToString(data.getBytes(), key.getBytes());
+    }
+
+    /**
+     * Return the hex string of HmacSHA384 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA384 encryption
+     */
+    public static String encryptHmacSHA384ToString(final byte[] data, final byte[] key) {
+        return UtilsBridge.bytes2HexString(encryptHmacSHA384(data, key));
+    }
+
+    /**
+     * Return the bytes of HmacSHA384 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the bytes of HmacSHA384 encryption
+     */
+    public static byte[] encryptHmacSHA384(final byte[] data, final byte[] key) {
+        return hmacTemplate(data, key, "HmacSHA384");
+    }
+
+    /**
+     * Return the hex string of HmacSHA512 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA512 encryption
+     */
+    public static String encryptHmacSHA512ToString(final String data, final String key) {
+        if (data == null || data.length() == 0 || key == null || key.length() == 0) return "";
+        return encryptHmacSHA512ToString(data.getBytes(), key.getBytes());
+    }
+
+    /**
+     * Return the hex string of HmacSHA512 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the hex string of HmacSHA512 encryption
+     */
+    public static String encryptHmacSHA512ToString(final byte[] data, final byte[] key) {
+        return UtilsBridge.bytes2HexString(encryptHmacSHA512(data, key));
+    }
+
+    /**
+     * Return the bytes of HmacSHA512 encryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     * @return the bytes of HmacSHA512 encryption
+     */
+    public static byte[] encryptHmacSHA512(final byte[] data, final byte[] key) {
+        return hmacTemplate(data, key, "HmacSHA512");
+    }
+
+    /**
+     * Return the bytes of hmac encryption.
+     *
+     * @param data      The data.
+     * @param key       The key.
+     * @param algorithm The name of hmac encryption.
+     * @return the bytes of hmac encryption
+     */
+    private static byte[] hmacTemplate(final byte[] data,
+                                       final byte[] key,
+                                       final String algorithm) {
+        if (data == null || data.length == 0 || key == null || key.length == 0) return null;
+        try {
+            SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
+            Mac mac = Mac.getInstance(algorithm);
+            mac.init(secretKey);
+            return mac.doFinal(data);
+        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // DES encryption
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the Base64-encode bytes of DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the Base64-encode bytes of DES encryption
+     */
+    public static byte[] encryptDES2Base64(final byte[] data,
+                                           final byte[] key,
+                                           final String transformation,
+                                           final byte[] iv) {
+        return UtilsBridge.base64Encode(encryptDES(data, key, transformation, iv));
+    }
+
+    /**
+     * Return the hex string of DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the hex string of DES encryption
+     */
+    public static String encryptDES2HexString(final byte[] data,
+                                              final byte[] key,
+                                              final String transformation,
+                                              final byte[] iv) {
+        return UtilsBridge.bytes2HexString(encryptDES(data, key, transformation, iv));
+    }
+
+    /**
+     * Return the bytes of DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of DES encryption
+     */
+    public static byte[] encryptDES(final byte[] data,
+                                    final byte[] key,
+                                    final String transformation,
+                                    final byte[] iv) {
+        return symmetricTemplate(data, key, "DES", transformation, iv, true);
+    }
+
+    /**
+     * Return the bytes of DES decryption for Base64-encode bytes.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of DES decryption for Base64-encode bytes
+     */
+    public static byte[] decryptBase64DES(final byte[] data,
+                                          final byte[] key,
+                                          final String transformation,
+                                          final byte[] iv) {
+        return decryptDES(UtilsBridge.base64Decode(data), key, transformation, iv);
+    }
+
+    /**
+     * Return the bytes of DES decryption for hex string.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of DES decryption for hex string
+     */
+    public static byte[] decryptHexStringDES(final String data,
+                                             final byte[] key,
+                                             final String transformation,
+                                             final byte[] iv) {
+        return decryptDES(UtilsBridge.hexString2Bytes(data), key, transformation, iv);
+    }
+
+    /**
+     * Return the bytes of DES decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of DES decryption
+     */
+    public static byte[] decryptDES(final byte[] data,
+                                    final byte[] key,
+                                    final String transformation,
+                                    final byte[] iv) {
+        return symmetricTemplate(data, key, "DES", transformation, iv, false);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // 3DES encryption
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the Base64-encode bytes of 3DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the Base64-encode bytes of 3DES encryption
+     */
+    public static byte[] encrypt3DES2Base64(final byte[] data,
+                                            final byte[] key,
+                                            final String transformation,
+                                            final byte[] iv) {
+        return UtilsBridge.base64Encode(encrypt3DES(data, key, transformation, iv));
+    }
+
+    /**
+     * Return the hex string of 3DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the hex string of 3DES encryption
+     */
+    public static String encrypt3DES2HexString(final byte[] data,
+                                               final byte[] key,
+                                               final String transformation,
+                                               final byte[] iv) {
+        return UtilsBridge.bytes2HexString(encrypt3DES(data, key, transformation, iv));
+    }
+
+    /**
+     * Return the bytes of 3DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of 3DES encryption
+     */
+    public static byte[] encrypt3DES(final byte[] data,
+                                     final byte[] key,
+                                     final String transformation,
+                                     final byte[] iv) {
+        return symmetricTemplate(data, key, "DESede", transformation, iv, true);
+    }
+
+    /**
+     * Return the bytes of 3DES decryption for Base64-encode bytes.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of 3DES decryption for Base64-encode bytes
+     */
+    public static byte[] decryptBase64_3DES(final byte[] data,
+                                            final byte[] key,
+                                            final String transformation,
+                                            final byte[] iv) {
+        return decrypt3DES(UtilsBridge.base64Decode(data), key, transformation, iv);
+    }
+
+    /**
+     * Return the bytes of 3DES decryption for hex string.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of 3DES decryption for hex string
+     */
+    public static byte[] decryptHexString3DES(final String data,
+                                              final byte[] key,
+                                              final String transformation,
+                                              final byte[] iv) {
+        return decrypt3DES(UtilsBridge.hexString2Bytes(data), key, transformation, iv);
+    }
+
+    /**
+     * Return the bytes of 3DES decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of 3DES decryption
+     */
+    public static byte[] decrypt3DES(final byte[] data,
+                                     final byte[] key,
+                                     final String transformation,
+                                     final byte[] iv) {
+        return symmetricTemplate(data, key, "DESede", transformation, iv, false);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AES encryption
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the Base64-encode bytes of AES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the Base64-encode bytes of AES encryption
+     */
+    public static byte[] encryptAES2Base64(final byte[] data,
+                                           final byte[] key,
+                                           final String transformation,
+                                           final byte[] iv) {
+        return UtilsBridge.base64Encode(encryptAES(data, key, transformation, iv));
+    }
+
+    /**
+     * Return the hex string of AES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the hex string of AES encryption
+     */
+    public static String encryptAES2HexString(final byte[] data,
+                                              final byte[] key,
+                                              final String transformation,
+                                              final byte[] iv) {
+        return UtilsBridge.bytes2HexString(encryptAES(data, key, transformation, iv));
+    }
+
+    /**
+     * Return the bytes of AES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES encryption
+     */
+    public static byte[] encryptAES(final byte[] data,
+                                    final byte[] key,
+                                    final String transformation,
+                                    final byte[] iv) {
+        return symmetricTemplate(data, key, "AES", transformation, iv, true);
+    }
+
+    /**
+     * Return the bytes of AES decryption for Base64-encode bytes.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption for Base64-encode bytes
+     */
+    public static byte[] decryptBase64AES(final byte[] data,
+                                          final byte[] key,
+                                          final String transformation,
+                                          final byte[] iv) {
+        return decryptAES(UtilsBridge.base64Decode(data), key, transformation, iv);
+    }
+
+    /**
+     * Return the bytes of AES decryption for hex string.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption for hex string
+     */
+    public static byte[] decryptHexStringAES(final String data,
+                                             final byte[] key,
+                                             final String transformation,
+                                             final byte[] iv) {
+        return decryptAES(UtilsBridge.hexString2Bytes(data), key, transformation, iv);
+    }
+
+    /**
+     * Return the bytes of AES decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param iv             The buffer with the IV. The contents of the
+     *                       buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption
+     */
+    public static byte[] decryptAES(final byte[] data,
+                                    final byte[] key,
+                                    final String transformation,
+                                    final byte[] iv) {
+        return symmetricTemplate(data, key, "AES", transformation, iv, false);
+    }
+
+    /**
+     * Return the bytes of symmetric encryption or decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param algorithm      The name of algorithm.
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.
+     * @param isEncrypt      True to encrypt, false otherwise.
+     * @return the bytes of symmetric encryption or decryption
+     */
+    private static byte[] symmetricTemplate(final byte[] data,
+                                            final byte[] key,
+                                            final String algorithm,
+                                            final String transformation,
+                                            final byte[] iv,
+                                            final boolean isEncrypt) {
+        if (data == null || data.length == 0 || key == null || key.length == 0) return null;
+        try {
+            SecretKey secretKey;
+            if ("DES".equals(algorithm)) {
+                DESKeySpec desKey = new DESKeySpec(key);
+                SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
+                secretKey = keyFactory.generateSecret(desKey);
+            } else {
+                secretKey = new SecretKeySpec(key, algorithm);
+            }
+            Cipher cipher = Cipher.getInstance(transformation);
+            if (iv == null || iv.length == 0) {
+                cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey);
+            } else {
+                AlgorithmParameterSpec params = new IvParameterSpec(iv);
+                cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey, params);
+            }
+            return cipher.doFinal(data);
+        } catch (Throwable e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // RSA encryption
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the Base64-encode bytes of RSA encryption.
+     *
+     * @param data           The data.
+     * @param publicKey      The public key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.
+     * @return the Base64-encode bytes of RSA encryption
+     */
+    public static byte[] encryptRSA2Base64(final byte[] data,
+                                           final byte[] publicKey,
+                                           final int keySize,
+                                           final String transformation) {
+        return UtilsBridge.base64Encode(encryptRSA(data, publicKey, keySize, transformation));
+    }
+
+    /**
+     * Return the hex string of RSA encryption.
+     *
+     * @param data           The data.
+     * @param publicKey      The public key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.
+     * @return the hex string of RSA encryption
+     */
+    public static String encryptRSA2HexString(final byte[] data,
+                                              final byte[] publicKey,
+                                              final int keySize,
+                                              final String transformation) {
+        return UtilsBridge.bytes2HexString(encryptRSA(data, publicKey, keySize, transformation));
+    }
+
+    /**
+     * Return the bytes of RSA encryption.
+     *
+     * @param data           The data.
+     * @param publicKey      The public key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.
+     * @return the bytes of RSA encryption
+     */
+    public static byte[] encryptRSA(final byte[] data,
+                                    final byte[] publicKey,
+                                    final int keySize,
+                                    final String transformation) {
+        return rsaTemplate(data, publicKey, keySize, transformation, true);
+    }
+
+    /**
+     * Return the bytes of RSA decryption for Base64-encode bytes.
+     *
+     * @param data           The data.
+     * @param privateKey     The private key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.
+     * @return the bytes of RSA decryption for Base64-encode bytes
+     */
+    public static byte[] decryptBase64RSA(final byte[] data,
+                                          final byte[] privateKey,
+                                          final int keySize,
+                                          final String transformation) {
+        return decryptRSA(UtilsBridge.base64Decode(data), privateKey, keySize, transformation);
+    }
+
+    /**
+     * Return the bytes of RSA decryption for hex string.
+     *
+     * @param data           The data.
+     * @param privateKey     The private key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.
+     * @return the bytes of RSA decryption for hex string
+     */
+    public static byte[] decryptHexStringRSA(final String data,
+                                             final byte[] privateKey,
+                                             final int keySize,
+                                             final String transformation) {
+        return decryptRSA(UtilsBridge.hexString2Bytes(data), privateKey, keySize, transformation);
+    }
+
+    /**
+     * Return the bytes of RSA decryption.
+     *
+     * @param data           The data.
+     * @param privateKey     The private key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.
+     * @return the bytes of RSA decryption
+     */
+    public static byte[] decryptRSA(final byte[] data,
+                                    final byte[] privateKey,
+                                    final int keySize,
+                                    final String transformation) {
+        return rsaTemplate(data, privateKey, keySize, transformation, false);
+    }
+
+    /**
+     * Return the bytes of RSA encryption or decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param keySize        The size of key, e.g. 1024, 2048...
+     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS1Padding</i>.
+     * @param isEncrypt      True to encrypt, false otherwise.
+     * @return the bytes of RSA encryption or decryption
+     */
+    private static byte[] rsaTemplate(final byte[] data,
+                                      final byte[] key,
+                                      final int keySize,
+                                      final String transformation,
+                                      final boolean isEncrypt) {
+        if (data == null || data.length == 0 || key == null || key.length == 0) {
+            return null;
+        }
+        try {
+            Key rsaKey;
+            KeyFactory keyFactory;
+            if (Build.VERSION.SDK_INT < 28) {
+                keyFactory = KeyFactory.getInstance("RSA", "BC");
+            } else {
+                keyFactory = KeyFactory.getInstance("RSA");
+            }
+            if (isEncrypt) {
+                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
+                rsaKey = keyFactory.generatePublic(keySpec);
+            } else {
+                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
+                rsaKey = keyFactory.generatePrivate(keySpec);
+            }
+            if (rsaKey == null) return null;
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey);
+            int len = data.length;
+            int maxLen = keySize / 8;
+            if (isEncrypt) {
+                String lowerTrans = transformation.toLowerCase();
+                if (lowerTrans.endsWith("pkcs1padding")) {
+                    maxLen -= 11;
+                }
+            }
+            int count = len / maxLen;
+            if (count > 0) {
+                byte[] ret = new byte[0];
+                byte[] buff = new byte[maxLen];
+                int index = 0;
+                for (int i = 0; i < count; i++) {
+                    System.arraycopy(data, index, buff, 0, maxLen);
+                    ret = joins(ret, cipher.doFinal(buff));
+                    index += maxLen;
+                }
+                if (index != len) {
+                    int restLen = len - index;
+                    buff = new byte[restLen];
+                    System.arraycopy(data, index, buff, 0, restLen);
+                    ret = joins(ret, cipher.doFinal(buff));
+                }
+                return ret;
+            } else {
+                return cipher.doFinal(data);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Return the bytes of RC4 encryption/decryption.
+     *
+     * @param data The data.
+     * @param key  The key.
+     */
+    public static byte[] rc4(byte[] data, byte[] key) {
+        if (data == null || data.length == 0 || key == null) return null;
+        if (key.length < 1 || key.length > 256) {
+            throw new IllegalArgumentException("key must be between 1 and 256 bytes");
+        }
+        final byte[] iS = new byte[256];
+        final byte[] iK = new byte[256];
+        int keyLen = key.length;
+        for (int i = 0; i < 256; i++) {
+            iS[i] = (byte) i;
+            iK[i] = key[i % keyLen];
+        }
+        int j = 0;
+        byte tmp;
+        for (int i = 0; i < 256; i++) {
+            j = (j + iS[i] + iK[i]) & 0xFF;
+            tmp = iS[j];
+            iS[j] = iS[i];
+            iS[i] = tmp;
+        }
+
+        final byte[] ret = new byte[data.length];
+        int i = 0, k, t;
+        for (int counter = 0; counter < data.length; counter++) {
+            i = (i + 1) & 0xFF;
+            j = (j + iS[i]) & 0xFF;
+            tmp = iS[j];
+            iS[j] = iS[i];
+            iS[i] = tmp;
+            t = (iS[i] + iS[j]) & 0xFF;
+            k = iS[t];
+            ret[counter] = (byte) (data[counter] ^ k);
+        }
+        return ret;
+    }
+
+    private static byte[] joins(final byte[] prefix, final byte[] suffix) {
+        byte[] ret = new byte[prefix.length + suffix.length];
+        System.arraycopy(prefix, 0, ret, 0, prefix.length);
+        System.arraycopy(suffix, 0, ret, prefix.length, suffix.length);
+        return ret;
+    }
+}

+ 951 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/FileIOUtils.java

@@ -0,0 +1,951 @@
+package com.cooleshow.base.utils;
+
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/06/22
+ *     desc  : utils about file io
+ * </pre>
+ */
+public final class FileIOUtils {
+
+    private static int sBufferSize = 524288;
+
+    private FileIOUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // writeFileFromIS without progress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Write file from input stream.
+     *
+     * @param filePath The path of file.
+     * @param is       The input stream.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final String filePath, final InputStream is) {
+        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, false, null);
+    }
+
+    /**
+     * Write file from input stream.
+     *
+     * @param filePath The path of file.
+     * @param is       The input stream.
+     * @param append   True to append, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final String filePath,
+                                          final InputStream is,
+                                          final boolean append) {
+        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, append, null);
+    }
+
+    /**
+     * Write file from input stream.
+     *
+     * @param file The file.
+     * @param is   The input stream.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final File file, final InputStream is) {
+        return writeFileFromIS(file, is, false, null);
+    }
+
+    /**
+     * Write file from input stream.
+     *
+     * @param file   The file.
+     * @param is     The input stream.
+     * @param append True to append, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final File file,
+                                          final InputStream is,
+                                          final boolean append) {
+        return writeFileFromIS(file, is, append, null);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // writeFileFromIS with progress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Write file from input stream.
+     *
+     * @param filePath The path of file.
+     * @param is       The input stream.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final String filePath,
+                                          final InputStream is,
+                                          final OnProgressUpdateListener listener) {
+        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, false, listener);
+    }
+
+    /**
+     * Write file from input stream.
+     *
+     * @param filePath The path of file.
+     * @param is       The input stream.
+     * @param append   True to append, false otherwise.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final String filePath,
+                                          final InputStream is,
+                                          final boolean append,
+                                          final OnProgressUpdateListener listener) {
+        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, append, listener);
+    }
+
+    /**
+     * Write file from input stream.
+     *
+     * @param file     The file.
+     * @param is       The input stream.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final File file,
+                                          final InputStream is,
+                                          final OnProgressUpdateListener listener) {
+        return writeFileFromIS(file, is, false, listener);
+    }
+
+    /**
+     * Write file from input stream.
+     *
+     * @param file     The file.
+     * @param is       The input stream.
+     * @param append   True to append, false otherwise.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromIS(final File file,
+                                          final InputStream is,
+                                          final boolean append,
+                                          final OnProgressUpdateListener listener) {
+        if (is == null || !UtilsBridge.createOrExistsFile(file)) {
+            Log.e("FileIOUtils", "create file <" + file + "> failed.");
+            return false;
+        }
+        OutputStream os = null;
+        try {
+            os = new BufferedOutputStream(new FileOutputStream(file, append), sBufferSize);
+            if (listener == null) {
+                byte[] data = new byte[sBufferSize];
+                for (int len; (len = is.read(data)) != -1; ) {
+                    os.write(data, 0, len);
+                }
+            } else {
+                double totalSize = is.available();
+                int curSize = 0;
+                listener.onProgressUpdate(0);
+                byte[] data = new byte[sBufferSize];
+                for (int len; (len = is.read(data)) != -1; ) {
+                    os.write(data, 0, len);
+                    curSize += len;
+                    listener.onProgressUpdate(curSize / totalSize);
+                }
+            }
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            try {
+                if (os != null) {
+                    os.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // writeFileFromBytesByStream without progress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes) {
+        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, false, null);
+    }
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param append   True to append, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final String filePath,
+                                                     final byte[] bytes,
+                                                     final boolean append) {
+        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, append, null);
+    }
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param file  The file.
+     * @param bytes The bytes.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes) {
+        return writeFileFromBytesByStream(file, bytes, false, null);
+    }
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param file   The file.
+     * @param bytes  The bytes.
+     * @param append True to append, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final File file,
+                                                     final byte[] bytes,
+                                                     final boolean append) {
+        return writeFileFromBytesByStream(file, bytes, append, null);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // writeFileFromBytesByStream with progress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final String filePath,
+                                                     final byte[] bytes,
+                                                     final OnProgressUpdateListener listener) {
+        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, false, listener);
+    }
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param append   True to append, false otherwise.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final String filePath,
+                                                     final byte[] bytes,
+                                                     final boolean append,
+                                                     final OnProgressUpdateListener listener) {
+        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, append, listener);
+    }
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param file     The file.
+     * @param bytes    The bytes.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final File file,
+                                                     final byte[] bytes,
+                                                     final OnProgressUpdateListener listener) {
+        return writeFileFromBytesByStream(file, bytes, false, listener);
+    }
+
+    /**
+     * Write file from bytes by stream.
+     *
+     * @param file     The file.
+     * @param bytes    The bytes.
+     * @param append   True to append, false otherwise.
+     * @param listener The progress update listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByStream(final File file,
+                                                     final byte[] bytes,
+                                                     final boolean append,
+                                                     final OnProgressUpdateListener listener) {
+        if (bytes == null) return false;
+        return writeFileFromIS(file, new ByteArrayInputStream(bytes), append, listener);
+    }
+
+    /**
+     * Write file from bytes by channel.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param isForce  是否写入文件
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByChannel(final String filePath,
+                                                      final byte[] bytes,
+                                                      final boolean isForce) {
+        return writeFileFromBytesByChannel(UtilsBridge.getFileByPath(filePath), bytes, false, isForce);
+    }
+
+    /**
+     * Write file from bytes by channel.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param append   True to append, false otherwise.
+     * @param isForce  True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByChannel(final String filePath,
+                                                      final byte[] bytes,
+                                                      final boolean append,
+                                                      final boolean isForce) {
+        return writeFileFromBytesByChannel(UtilsBridge.getFileByPath(filePath), bytes, append, isForce);
+    }
+
+    /**
+     * Write file from bytes by channel.
+     *
+     * @param file    The file.
+     * @param bytes   The bytes.
+     * @param isForce True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByChannel(final File file,
+                                                      final byte[] bytes,
+                                                      final boolean isForce) {
+        return writeFileFromBytesByChannel(file, bytes, false, isForce);
+    }
+
+    /**
+     * Write file from bytes by channel.
+     *
+     * @param file    The file.
+     * @param bytes   The bytes.
+     * @param append  True to append, false otherwise.
+     * @param isForce True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByChannel(final File file,
+                                                      final byte[] bytes,
+                                                      final boolean append,
+                                                      final boolean isForce) {
+        if (bytes == null) {
+            Log.e("FileIOUtils", "bytes is null.");
+            return false;
+        }
+        if (!UtilsBridge.createOrExistsFile(file)) {
+            Log.e("FileIOUtils", "create file <" + file + "> failed.");
+            return false;
+        }
+        FileChannel fc = null;
+        try {
+            fc = new FileOutputStream(file, append).getChannel();
+            if (fc == null) {
+                Log.e("FileIOUtils", "fc is null.");
+                return false;
+            }
+            fc.position(fc.size());
+            fc.write(ByteBuffer.wrap(bytes));
+            if (isForce) fc.force(true);
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        } finally {
+            try {
+                if (fc != null) {
+                    fc.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Write file from bytes by map.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param isForce  True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByMap(final String filePath,
+                                                  final byte[] bytes,
+                                                  final boolean isForce) {
+        return writeFileFromBytesByMap(filePath, bytes, false, isForce);
+    }
+
+    /**
+     * Write file from bytes by map.
+     *
+     * @param filePath The path of file.
+     * @param bytes    The bytes.
+     * @param append   True to append, false otherwise.
+     * @param isForce  True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByMap(final String filePath,
+                                                  final byte[] bytes,
+                                                  final boolean append,
+                                                  final boolean isForce) {
+        return writeFileFromBytesByMap(UtilsBridge.getFileByPath(filePath), bytes, append, isForce);
+    }
+
+    /**
+     * Write file from bytes by map.
+     *
+     * @param file    The file.
+     * @param bytes   The bytes.
+     * @param isForce True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByMap(final File file,
+                                                  final byte[] bytes,
+                                                  final boolean isForce) {
+        return writeFileFromBytesByMap(file, bytes, false, isForce);
+    }
+
+    /**
+     * Write file from bytes by map.
+     *
+     * @param file    The file.
+     * @param bytes   The bytes.
+     * @param append  True to append, false otherwise.
+     * @param isForce True to force write file, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromBytesByMap(final File file,
+                                                  final byte[] bytes,
+                                                  final boolean append,
+                                                  final boolean isForce) {
+        if (bytes == null || !UtilsBridge.createOrExistsFile(file)) {
+            Log.e("FileIOUtils", "create file <" + file + "> failed.");
+            return false;
+        }
+        FileChannel fc = null;
+        try {
+            fc = new FileOutputStream(file, append).getChannel();
+            if (fc == null) {
+                Log.e("FileIOUtils", "fc is null.");
+                return false;
+            }
+            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length);
+            mbb.put(bytes);
+            if (isForce) mbb.force();
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        } finally {
+            try {
+                if (fc != null) {
+                    fc.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Write file from string.
+     *
+     * @param filePath The path of file.
+     * @param content  The string of content.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromString(final String filePath, final String content) {
+        return writeFileFromString(UtilsBridge.getFileByPath(filePath), content, false);
+    }
+
+    /**
+     * Write file from string.
+     *
+     * @param filePath The path of file.
+     * @param content  The string of content.
+     * @param append   True to append, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromString(final String filePath,
+                                              final String content,
+                                              final boolean append) {
+        return writeFileFromString(UtilsBridge.getFileByPath(filePath), content, append);
+    }
+
+    /**
+     * Write file from string.
+     *
+     * @param file    The file.
+     * @param content The string of content.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromString(final File file, final String content) {
+        return writeFileFromString(file, content, false);
+    }
+
+    /**
+     * Write file from string.
+     *
+     * @param file    The file.
+     * @param content The string of content.
+     * @param append  True to append, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean writeFileFromString(final File file,
+                                              final String content,
+                                              final boolean append) {
+        if (file == null || content == null) return false;
+        if (!UtilsBridge.createOrExistsFile(file)) {
+            Log.e("FileIOUtils", "create file <" + file + "> failed.");
+            return false;
+        }
+        BufferedWriter bw = null;
+        try {
+            bw = new BufferedWriter(new FileWriter(file, append));
+            bw.write(content);
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        } finally {
+            try {
+                if (bw != null) {
+                    bw.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // the divide line of write and read
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the lines in file.
+     *
+     * @param filePath The path of file.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final String filePath) {
+        return readFile2List(UtilsBridge.getFileByPath(filePath), null);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param filePath    The path of file.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final String filePath, final String charsetName) {
+        return readFile2List(UtilsBridge.getFileByPath(filePath), charsetName);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param file The file.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final File file) {
+        return readFile2List(file, 0, 0x7FFFFFFF, null);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param file        The file.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final File file, final String charsetName) {
+        return readFile2List(file, 0, 0x7FFFFFFF, charsetName);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param filePath The path of file.
+     * @param st       The line's index of start.
+     * @param end      The line's index of end.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final String filePath, final int st, final int end) {
+        return readFile2List(UtilsBridge.getFileByPath(filePath), st, end, null);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param filePath    The path of file.
+     * @param st          The line's index of start.
+     * @param end         The line's index of end.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final String filePath,
+                                             final int st,
+                                             final int end,
+                                             final String charsetName) {
+        return readFile2List(UtilsBridge.getFileByPath(filePath), st, end, charsetName);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param file The file.
+     * @param st   The line's index of start.
+     * @param end  The line's index of end.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final File file, final int st, final int end) {
+        return readFile2List(file, st, end, null);
+    }
+
+    /**
+     * Return the lines in file.
+     *
+     * @param file        The file.
+     * @param st          The line's index of start.
+     * @param end         The line's index of end.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    public static List<String> readFile2List(final File file,
+                                             final int st,
+                                             final int end,
+                                             final String charsetName) {
+        if (!UtilsBridge.isFileExists(file)) return null;
+        if (st > end) return null;
+        BufferedReader reader = null;
+        try {
+            String line;
+            int curLine = 1;
+            List<String> list = new ArrayList<>();
+            if (UtilsBridge.isSpace(charsetName)) {
+                reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+            } else {
+                reader = new BufferedReader(
+                        new InputStreamReader(new FileInputStream(file), charsetName)
+                );
+            }
+            while ((line = reader.readLine()) != null) {
+                if (curLine > end) break;
+                if (st <= curLine && curLine <= end) list.add(line);
+                ++curLine;
+            }
+            return list;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Return the string in file.
+     *
+     * @param filePath The path of file.
+     * @return the string in file
+     */
+    public static String readFile2String(final String filePath) {
+        return readFile2String(UtilsBridge.getFileByPath(filePath), null);
+    }
+
+    /**
+     * Return the string in file.
+     *
+     * @param filePath    The path of file.
+     * @param charsetName The name of charset.
+     * @return the string in file
+     */
+    public static String readFile2String(final String filePath, final String charsetName) {
+        return readFile2String(UtilsBridge.getFileByPath(filePath), charsetName);
+    }
+
+    /**
+     * Return the string in file.
+     *
+     * @param file The file.
+     * @return the string in file
+     */
+    public static String readFile2String(final File file) {
+        return readFile2String(file, null);
+    }
+
+    /**
+     * Return the string in file.
+     *
+     * @param file        The file.
+     * @param charsetName The name of charset.
+     * @return the string in file
+     */
+    public static String readFile2String(final File file, final String charsetName) {
+        byte[] bytes = readFile2BytesByStream(file);
+        if (bytes == null) return null;
+        if (UtilsBridge.isSpace(charsetName)) {
+            return new String(bytes);
+        } else {
+            try {
+                return new String(bytes, charsetName);
+            } catch (UnsupportedEncodingException e) {
+                e.printStackTrace();
+                return "";
+            }
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // readFile2BytesByStream without progress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the bytes in file by stream.
+     *
+     * @param filePath The path of file.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByStream(final String filePath) {
+        return readFile2BytesByStream(UtilsBridge.getFileByPath(filePath), null);
+    }
+
+    /**
+     * Return the bytes in file by stream.
+     *
+     * @param file The file.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByStream(final File file) {
+        return readFile2BytesByStream(file, null);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // readFile2BytesByStream with progress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the bytes in file by stream.
+     *
+     * @param filePath The path of file.
+     * @param listener The progress update listener.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByStream(final String filePath,
+                                                final OnProgressUpdateListener listener) {
+        return readFile2BytesByStream(UtilsBridge.getFileByPath(filePath), listener);
+    }
+
+    /**
+     * Return the bytes in file by stream.
+     *
+     * @param file     The file.
+     * @param listener The progress update listener.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByStream(final File file,
+                                                final OnProgressUpdateListener listener) {
+        if (!UtilsBridge.isFileExists(file)) return null;
+        try {
+            ByteArrayOutputStream os = null;
+            InputStream is = new BufferedInputStream(new FileInputStream(file), sBufferSize);
+            try {
+                os = new ByteArrayOutputStream();
+                byte[] b = new byte[sBufferSize];
+                int len;
+                if (listener == null) {
+                    while ((len = is.read(b, 0, sBufferSize)) != -1) {
+                        os.write(b, 0, len);
+                    }
+                } else {
+                    double totalSize = is.available();
+                    int curSize = 0;
+                    listener.onProgressUpdate(0);
+                    while ((len = is.read(b, 0, sBufferSize)) != -1) {
+                        os.write(b, 0, len);
+                        curSize += len;
+                        listener.onProgressUpdate(curSize / totalSize);
+                    }
+                }
+                return os.toByteArray();
+            } catch (IOException e) {
+                e.printStackTrace();
+                return null;
+            } finally {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+                try {
+                    if (os != null) {
+                        os.close();
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Return the bytes in file by channel.
+     *
+     * @param filePath The path of file.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByChannel(final String filePath) {
+        return readFile2BytesByChannel(UtilsBridge.getFileByPath(filePath));
+    }
+
+    /**
+     * Return the bytes in file by channel.
+     *
+     * @param file The file.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByChannel(final File file) {
+        if (!UtilsBridge.isFileExists(file)) return null;
+        FileChannel fc = null;
+        try {
+            fc = new RandomAccessFile(file, "r").getChannel();
+            if (fc == null) {
+                Log.e("FileIOUtils", "fc is null.");
+                return new byte[0];
+            }
+            ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size());
+            while (true) {
+                if (!((fc.read(byteBuffer)) > 0)) break;
+            }
+            return byteBuffer.array();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (fc != null) {
+                    fc.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Return the bytes in file by map.
+     *
+     * @param filePath The path of file.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByMap(final String filePath) {
+        return readFile2BytesByMap(UtilsBridge.getFileByPath(filePath));
+    }
+
+    /**
+     * Return the bytes in file by map.
+     *
+     * @param file The file.
+     * @return the bytes in file
+     */
+    public static byte[] readFile2BytesByMap(final File file) {
+        if (!UtilsBridge.isFileExists(file)) return null;
+        FileChannel fc = null;
+        try {
+            fc = new RandomAccessFile(file, "r").getChannel();
+            if (fc == null) {
+                Log.e("FileIOUtils", "fc is null.");
+                return new byte[0];
+            }
+            int size = (int) fc.size();
+            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load();
+            byte[] result = new byte[size];
+            mbb.get(result, 0, size);
+            return result;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            try {
+                if (fc != null) {
+                    fc.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Set the buffer's size.
+     * <p>Default size equals 8192 bytes.</p>
+     *
+     * @param bufferSize The buffer's size.
+     */
+    public static void setBufferSize(final int bufferSize) {
+        sBufferSize = bufferSize;
+    }
+
+    public interface OnProgressUpdateListener {
+        void onProgressUpdate(double progress);
+    }
+}

+ 1456 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/FileUtils.java

@@ -0,0 +1,1456 @@
+package com.cooleshow.base.utils;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.StatFs;
+import android.text.TextUtils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.net.ssl.HttpsURLConnection;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/05/03
+ *     desc  : utils about file
+ * </pre>
+ */
+public final class FileUtils {
+
+    private static final String LINE_SEP = System.getProperty("line.separator");
+
+    private FileUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the file by path.
+     *
+     * @param filePath The path of file.
+     * @return the file
+     */
+    public static File getFileByPath(final String filePath) {
+        return UtilsBridge.isSpace(filePath) ? null : new File(filePath);
+    }
+
+    /**
+     * Return whether the file exists.
+     *
+     * @param file The file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFileExists(final File file) {
+        if (file == null) return false;
+        if (file.exists()) {
+            return true;
+        }
+        return isFileExists(file.getAbsolutePath());
+    }
+
+    /**
+     * Return whether the file exists.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFileExists(final String filePath) {
+        File file = getFileByPath(filePath);
+        if (file == null) return false;
+        if (file.exists()) {
+            return true;
+        }
+        return isFileExistsApi29(filePath);
+    }
+
+    private static boolean isFileExistsApi29(String filePath) {
+        if (Build.VERSION.SDK_INT >= 29) {
+            try {
+                Uri uri = Uri.parse(filePath);
+                ContentResolver cr = Utils.getApp().getContentResolver();
+                AssetFileDescriptor afd = cr.openAssetFileDescriptor(uri, "r");
+                if (afd == null) return false;
+                try {
+                    afd.close();
+                } catch (IOException ignore) {
+                }
+            } catch (FileNotFoundException e) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Rename the file.
+     *
+     * @param filePath The path of file.
+     * @param newName  The new name of file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean rename(final String filePath, final String newName) {
+        return rename(getFileByPath(filePath), newName);
+    }
+
+    /**
+     * Rename the file.
+     *
+     * @param file    The file.
+     * @param newName The new name of file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean rename(final File file, final String newName) {
+        // file is null then return false
+        if (file == null) return false;
+        // file doesn't exist then return false
+        if (!file.exists()) return false;
+        // the new name is space then return false
+        if (UtilsBridge.isSpace(newName)) return false;
+        // the new name equals old name then return true
+        if (newName.equals(file.getName())) return true;
+        File newFile = new File(file.getParent() + File.separator + newName);
+        // the new name of file exists then return false
+        return !newFile.exists()
+                && file.renameTo(newFile);
+    }
+
+    /**
+     * Return whether it is a directory.
+     *
+     * @param dirPath The path of directory.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isDir(final String dirPath) {
+        return isDir(getFileByPath(dirPath));
+    }
+
+    /**
+     * Return whether it is a directory.
+     *
+     * @param file The file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isDir(final File file) {
+        return file != null && file.exists() && file.isDirectory();
+    }
+
+    /**
+     * Return whether it is a file.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFile(final String filePath) {
+        return isFile(getFileByPath(filePath));
+    }
+
+    /**
+     * Return whether it is a file.
+     *
+     * @param file The file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFile(final File file) {
+        return file != null && file.exists() && file.isFile();
+    }
+
+    /**
+     * Create a directory if it doesn't exist, otherwise do nothing.
+     *
+     * @param dirPath The path of directory.
+     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise
+     */
+    public static boolean createOrExistsDir(final String dirPath) {
+        return createOrExistsDir(getFileByPath(dirPath));
+    }
+
+    /**
+     * Create a directory if it doesn't exist, otherwise do nothing.
+     *
+     * @param file The file.
+     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise
+     */
+    public static boolean createOrExistsDir(final File file) {
+        return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
+    }
+
+    /**
+     * Create a file if it doesn't exist, otherwise do nothing.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise
+     */
+    public static boolean createOrExistsFile(final String filePath) {
+        return createOrExistsFile(getFileByPath(filePath));
+    }
+
+    /**
+     * Create a file if it doesn't exist, otherwise do nothing.
+     *
+     * @param file The file.
+     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise
+     */
+    public static boolean createOrExistsFile(final File file) {
+        if (file == null) return false;
+        if (file.exists()) return file.isFile();
+        if (!createOrExistsDir(file.getParentFile())) return false;
+        try {
+            return file.createNewFile();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Create a file if it doesn't exist, otherwise delete old file before creating.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean createFileByDeleteOldFile(final String filePath) {
+        return createFileByDeleteOldFile(getFileByPath(filePath));
+    }
+
+    /**
+     * Create a file if it doesn't exist, otherwise delete old file before creating.
+     *
+     * @param file The file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean createFileByDeleteOldFile(final File file) {
+        if (file == null) return false;
+        // file exists and unsuccessfully delete then return false
+        if (file.exists() && !file.delete()) return false;
+        if (!createOrExistsDir(file.getParentFile())) return false;
+        try {
+            return file.createNewFile();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Copy the directory or file.
+     *
+     * @param srcPath  The path of source.
+     * @param destPath The path of destination.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean copy(final String srcPath,
+                               final String destPath) {
+        return copy(getFileByPath(srcPath), getFileByPath(destPath), null);
+    }
+
+    /**
+     * Copy the directory or file.
+     *
+     * @param srcPath  The path of source.
+     * @param destPath The path of destination.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean copy(final String srcPath,
+                               final String destPath,
+                               final OnReplaceListener listener) {
+        return copy(getFileByPath(srcPath), getFileByPath(destPath), listener);
+    }
+
+    /**
+     * Copy the directory or file.
+     *
+     * @param src  The source.
+     * @param dest The destination.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean copy(final File src,
+                               final File dest) {
+        return copy(src, dest, null);
+    }
+
+    /**
+     * Copy the directory or file.
+     *
+     * @param src      The source.
+     * @param dest     The destination.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean copy(final File src,
+                               final File dest,
+                               final OnReplaceListener listener) {
+        if (src == null) return false;
+        if (src.isDirectory()) {
+            return copyDir(src, dest, listener);
+        }
+        return copyFile(src, dest, listener);
+    }
+
+    /**
+     * Copy the directory.
+     *
+     * @param srcDir   The source directory.
+     * @param destDir  The destination directory.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    private static boolean copyDir(final File srcDir,
+                                   final File destDir,
+                                   final OnReplaceListener listener) {
+        return copyOrMoveDir(srcDir, destDir, listener, false);
+    }
+
+    /**
+     * Copy the file.
+     *
+     * @param srcFile  The source file.
+     * @param destFile The destination file.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    private static boolean copyFile(final File srcFile,
+                                    final File destFile,
+                                    final OnReplaceListener listener) {
+        return copyOrMoveFile(srcFile, destFile, listener, false);
+    }
+
+    /**
+     * Move the directory or file.
+     *
+     * @param srcPath  The path of source.
+     * @param destPath The path of destination.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean move(final String srcPath,
+                               final String destPath) {
+        return move(getFileByPath(srcPath), getFileByPath(destPath), null);
+    }
+
+    /**
+     * Move the directory or file.
+     *
+     * @param srcPath  The path of source.
+     * @param destPath The path of destination.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean move(final String srcPath,
+                               final String destPath,
+                               final OnReplaceListener listener) {
+        return move(getFileByPath(srcPath), getFileByPath(destPath), listener);
+    }
+
+    /**
+     * Move the directory or file.
+     *
+     * @param src  The source.
+     * @param dest The destination.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean move(final File src,
+                               final File dest) {
+        return move(src, dest, null);
+    }
+
+    /**
+     * Move the directory or file.
+     *
+     * @param src      The source.
+     * @param dest     The destination.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean move(final File src,
+                               final File dest,
+                               final OnReplaceListener listener) {
+        if (src == null) return false;
+        if (src.isDirectory()) {
+            return moveDir(src, dest, listener);
+        }
+        return moveFile(src, dest, listener);
+    }
+
+    /**
+     * Move the directory.
+     *
+     * @param srcDir   The source directory.
+     * @param destDir  The destination directory.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean moveDir(final File srcDir,
+                                  final File destDir,
+                                  final OnReplaceListener listener) {
+        return copyOrMoveDir(srcDir, destDir, listener, true);
+    }
+
+    /**
+     * Move the file.
+     *
+     * @param srcFile  The source file.
+     * @param destFile The destination file.
+     * @param listener The replace listener.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean moveFile(final File srcFile,
+                                   final File destFile,
+                                   final OnReplaceListener listener) {
+        return copyOrMoveFile(srcFile, destFile, listener, true);
+    }
+
+    private static boolean copyOrMoveDir(final File srcDir,
+                                         final File destDir,
+                                         final OnReplaceListener listener,
+                                         final boolean isMove) {
+        if (srcDir == null || destDir == null) return false;
+        // destDir's path locate in srcDir's path then return false
+        String srcPath = srcDir.getPath() + File.separator;
+        String destPath = destDir.getPath() + File.separator;
+        if (destPath.contains(srcPath)) return false;
+        if (!srcDir.exists() || !srcDir.isDirectory()) return false;
+        if (!createOrExistsDir(destDir)) return false;
+        File[] files = srcDir.listFiles();
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                File oneDestFile = new File(destPath + file.getName());
+                if (file.isFile()) {
+                    if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false;
+                } else if (file.isDirectory()) {
+                    if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false;
+                }
+            }
+        }
+        return !isMove || deleteDir(srcDir);
+    }
+
+    private static boolean copyOrMoveFile(final File srcFile,
+                                          final File destFile,
+                                          final OnReplaceListener listener,
+                                          final boolean isMove) {
+        if (srcFile == null || destFile == null) return false;
+        // srcFile equals destFile then return false
+        if (srcFile.equals(destFile)) return false;
+        // srcFile doesn't exist or isn't a file then return false
+        if (!srcFile.exists() || !srcFile.isFile()) return false;
+        if (destFile.exists()) {
+            if (listener == null || listener.onReplace(srcFile, destFile)) {// require delete the old file
+                if (!destFile.delete()) {// unsuccessfully delete then return false
+                    return false;
+                }
+            } else {
+                return true;
+            }
+        }
+        if (!createOrExistsDir(destFile.getParentFile())) return false;
+        try {
+            return UtilsBridge.writeFileFromIS(destFile.getAbsolutePath(), new FileInputStream(srcFile))
+                    && !(isMove && !deleteFile(srcFile));
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Delete the directory.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean delete(final String filePath) {
+        return delete(getFileByPath(filePath));
+    }
+
+    /**
+     * Delete the directory.
+     *
+     * @param file The file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean delete(final File file) {
+        if (file == null) return false;
+        if (file.isDirectory()) {
+            return deleteDir(file);
+        }
+        return deleteFile(file);
+    }
+
+    /**
+     * Delete the directory.
+     *
+     * @param dir The directory.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    private static boolean deleteDir(final File dir) {
+        if (dir == null) return false;
+        // dir doesn't exist then return true
+        if (!dir.exists()) return true;
+        // dir isn't a directory then return false
+        if (!dir.isDirectory()) return false;
+        File[] files = dir.listFiles();
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                if (file.isFile()) {
+                    if (!file.delete()) return false;
+                } else if (file.isDirectory()) {
+                    if (!deleteDir(file)) return false;
+                }
+            }
+        }
+        return dir.delete();
+    }
+
+    /**
+     * Delete the file.
+     *
+     * @param file The file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    private static boolean deleteFile(final File file) {
+        return file != null && (!file.exists() || file.isFile() && file.delete());
+    }
+
+    /**
+     * Delete the all in directory.
+     *
+     * @param dirPath The path of directory.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean deleteAllInDir(final String dirPath) {
+        return deleteAllInDir(getFileByPath(dirPath));
+    }
+
+    /**
+     * Delete the all in directory.
+     *
+     * @param dir The directory.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean deleteAllInDir(final File dir) {
+        return deleteFilesInDirWithFilter(dir, new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Delete all files in directory.
+     *
+     * @param dirPath The path of directory.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean deleteFilesInDir(final String dirPath) {
+        return deleteFilesInDir(getFileByPath(dirPath));
+    }
+
+    /**
+     * Delete all files in directory.
+     *
+     * @param dir The directory.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean deleteFilesInDir(final File dir) {
+        return deleteFilesInDirWithFilter(dir, new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                return pathname.isFile();
+            }
+        });
+    }
+
+    /**
+     * Delete all files that satisfy the filter in directory.
+     *
+     * @param dirPath The path of directory.
+     * @param filter  The filter.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean deleteFilesInDirWithFilter(final String dirPath,
+                                                     final FileFilter filter) {
+        return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter);
+    }
+
+    /**
+     * Delete all files that satisfy the filter in directory.
+     *
+     * @param dir    The directory.
+     * @param filter The filter.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean deleteFilesInDirWithFilter(final File dir, final FileFilter filter) {
+        if (dir == null || filter == null) return false;
+        // dir doesn't exist then return true
+        if (!dir.exists()) return true;
+        // dir isn't a directory then return false
+        if (!dir.isDirectory()) return false;
+        File[] files = dir.listFiles();
+        if (files != null && files.length != 0) {
+            for (File file : files) {
+                if (filter.accept(file)) {
+                    if (file.isFile()) {
+                        if (!file.delete()) return false;
+                    } else if (file.isDirectory()) {
+                        if (!deleteDir(file)) return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return the files in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dirPath The path of directory.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final String dirPath) {
+        return listFilesInDir(dirPath, null);
+    }
+
+    /**
+     * Return the files in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dir The directory.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final File dir) {
+        return listFilesInDir(dir, null);
+    }
+
+    /**
+     * Return the files in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dirPath    The path of directory.
+     * @param comparator The comparator to determine the order of the list.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final String dirPath, Comparator<File> comparator) {
+        return listFilesInDir(getFileByPath(dirPath), false, comparator);
+    }
+
+    /**
+     * Return the files in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dir        The directory.
+     * @param comparator The comparator to determine the order of the list.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final File dir, Comparator<File> comparator) {
+        return listFilesInDir(dir, false, comparator);
+    }
+
+    /**
+     * Return the files in directory.
+     *
+     * @param dirPath     The path of directory.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final String dirPath, final boolean isRecursive) {
+        return listFilesInDir(getFileByPath(dirPath), isRecursive);
+    }
+
+    /**
+     * Return the files in directory.
+     *
+     * @param dir         The directory.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final File dir, final boolean isRecursive) {
+        return listFilesInDir(dir, isRecursive, null);
+    }
+
+    /**
+     * Return the files in directory.
+     *
+     * @param dirPath     The path of directory.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @param comparator  The comparator to determine the order of the list.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final String dirPath,
+                                            final boolean isRecursive,
+                                            final Comparator<File> comparator) {
+        return listFilesInDir(getFileByPath(dirPath), isRecursive, comparator);
+    }
+
+    /**
+     * Return the files in directory.
+     *
+     * @param dir         The directory.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @param comparator  The comparator to determine the order of the list.
+     * @return the files in directory
+     */
+    public static List<File> listFilesInDir(final File dir,
+                                            final boolean isRecursive,
+                                            final Comparator<File> comparator) {
+        return listFilesInDirWithFilter(dir, new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                return true;
+            }
+        }, isRecursive, comparator);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dirPath The path of directory.
+     * @param filter  The filter.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final String dirPath,
+                                                      final FileFilter filter) {
+        return listFilesInDirWithFilter(getFileByPath(dirPath), filter);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dir    The directory.
+     * @param filter The filter.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final File dir,
+                                                      final FileFilter filter) {
+        return listFilesInDirWithFilter(dir, filter, false, null);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dirPath    The path of directory.
+     * @param filter     The filter.
+     * @param comparator The comparator to determine the order of the list.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final String dirPath,
+                                                      final FileFilter filter,
+                                                      final Comparator<File> comparator) {
+        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, comparator);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     * <p>Doesn't traverse subdirectories</p>
+     *
+     * @param dir        The directory.
+     * @param filter     The filter.
+     * @param comparator The comparator to determine the order of the list.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final File dir,
+                                                      final FileFilter filter,
+                                                      final Comparator<File> comparator) {
+        return listFilesInDirWithFilter(dir, filter, false, comparator);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     *
+     * @param dirPath     The path of directory.
+     * @param filter      The filter.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final String dirPath,
+                                                      final FileFilter filter,
+                                                      final boolean isRecursive) {
+        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     *
+     * @param dir         The directory.
+     * @param filter      The filter.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final File dir,
+                                                      final FileFilter filter,
+                                                      final boolean isRecursive) {
+        return listFilesInDirWithFilter(dir, filter, isRecursive, null);
+    }
+
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     *
+     * @param dirPath     The path of directory.
+     * @param filter      The filter.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @param comparator  The comparator to determine the order of the list.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final String dirPath,
+                                                      final FileFilter filter,
+                                                      final boolean isRecursive,
+                                                      final Comparator<File> comparator) {
+        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive, comparator);
+    }
+
+    /**
+     * Return the files that satisfy the filter in directory.
+     *
+     * @param dir         The directory.
+     * @param filter      The filter.
+     * @param isRecursive True to traverse subdirectories, false otherwise.
+     * @param comparator  The comparator to determine the order of the list.
+     * @return the files that satisfy the filter in directory
+     */
+    public static List<File> listFilesInDirWithFilter(final File dir,
+                                                      final FileFilter filter,
+                                                      final boolean isRecursive,
+                                                      final Comparator<File> comparator) {
+        List<File> files = listFilesInDirWithFilterInner(dir, filter, isRecursive);
+        if (comparator != null) {
+            Collections.sort(files, comparator);
+        }
+        return files;
+    }
+
+    private static List<File> listFilesInDirWithFilterInner(final File dir,
+                                                            final FileFilter filter,
+                                                            final boolean isRecursive) {
+        List<File> list = new ArrayList<>();
+        if (!isDir(dir)) return list;
+        File[] files = dir.listFiles();
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                if (filter.accept(file)) {
+                    list.add(file);
+                }
+                if (isRecursive && file.isDirectory()) {
+                    list.addAll(listFilesInDirWithFilterInner(file, filter, true));
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Return the time that the file was last modified.
+     *
+     * @param filePath The path of file.
+     * @return the time that the file was last modified
+     */
+
+    public static long getFileLastModified(final String filePath) {
+        return getFileLastModified(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the time that the file was last modified.
+     *
+     * @param file The file.
+     * @return the time that the file was last modified
+     */
+    public static long getFileLastModified(final File file) {
+        if (file == null) return -1;
+        return file.lastModified();
+    }
+
+    /**
+     * Return the charset of file simply.
+     *
+     * @param filePath The path of file.
+     * @return the charset of file simply
+     */
+    public static String getFileCharsetSimple(final String filePath) {
+        return getFileCharsetSimple(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the charset of file simply.
+     *
+     * @param file The file.
+     * @return the charset of file simply
+     */
+    public static String getFileCharsetSimple(final File file) {
+        if (file == null) return "";
+        if (isUtf8(file)) return "UTF-8";
+        int p = 0;
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(new FileInputStream(file));
+            p = (is.read() << 8) + is.read();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        switch (p) {
+            case 0xfffe:
+                return "Unicode";
+            case 0xfeff:
+                return "UTF-16BE";
+            default:
+                return "GBK";
+        }
+    }
+
+    /**
+     * Return whether the charset of file is utf8.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isUtf8(final String filePath) {
+        return isUtf8(getFileByPath(filePath));
+    }
+
+    /**
+     * Return whether the charset of file is utf8.
+     *
+     * @param file The file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isUtf8(final File file) {
+        if (file == null) return false;
+        InputStream is = null;
+        try {
+            byte[] bytes = new byte[24];
+            is = new BufferedInputStream(new FileInputStream(file));
+            int read = is.read(bytes);
+            if (read != -1) {
+                byte[] readArr = new byte[read];
+                System.arraycopy(bytes, 0, readArr, 0, read);
+                return isUtf8(readArr) == 100;
+            } else {
+                return false;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * UTF-8编码方式
+     * ----------------------------------------------
+     * 0xxxxxxx
+     * 110xxxxx 10xxxxxx
+     * 1110xxxx 10xxxxxx 10xxxxxx
+     * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+     */
+    private static int isUtf8(byte[] raw) {
+        int i, len;
+        int utf8 = 0, ascii = 0;
+        if (raw.length > 3) {
+            if ((raw[0] == (byte) 0xEF) && (raw[1] == (byte) 0xBB) && (raw[2] == (byte) 0xBF)) {
+                return 100;
+            }
+        }
+        len = raw.length;
+        int child = 0;
+        for (i = 0; i < len; ) {
+            // UTF-8 byte shouldn't be FF and FE
+            if ((raw[i] & (byte) 0xFF) == (byte) 0xFF || (raw[i] & (byte) 0xFE) == (byte) 0xFE) {
+                return 0;
+            }
+            if (child == 0) {
+                // ASCII format is 0x0*******
+                if ((raw[i] & (byte) 0x7F) == raw[i] && raw[i] != 0) {
+                    ascii++;
+                } else if ((raw[i] & (byte) 0xC0) == (byte) 0xC0) {
+                    // 0x11****** maybe is UTF-8
+                    for (int bit = 0; bit < 8; bit++) {
+                        if ((((byte) (0x80 >> bit)) & raw[i]) == ((byte) (0x80 >> bit))) {
+                            child = bit;
+                        } else {
+                            break;
+                        }
+                    }
+                    utf8++;
+                }
+                i++;
+            } else {
+                child = (raw.length - i > child) ? child : (raw.length - i);
+                boolean currentNotUtf8 = false;
+                for (int children = 0; children < child; children++) {
+                    // format must is 0x10******
+                    if ((raw[i + children] & ((byte) 0x80)) != ((byte) 0x80)) {
+                        if ((raw[i + children] & (byte) 0x7F) == raw[i + children] && raw[i] != 0) {
+                            // ASCII format is 0x0*******
+                            ascii++;
+                        }
+                        currentNotUtf8 = true;
+                    }
+                }
+                if (currentNotUtf8) {
+                    utf8--;
+                    i++;
+                } else {
+                    utf8 += child;
+                    i += child;
+                }
+                child = 0;
+            }
+        }
+        // UTF-8 contains ASCII
+        if (ascii == len) {
+            return 100;
+        }
+        return (int) (100 * ((float) (utf8 + ascii) / (float) len));
+    }
+
+    /**
+     * Return the number of lines of file.
+     *
+     * @param filePath The path of file.
+     * @return the number of lines of file
+     */
+    public static int getFileLines(final String filePath) {
+        return getFileLines(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the number of lines of file.
+     *
+     * @param file The file.
+     * @return the number of lines of file
+     */
+    public static int getFileLines(final File file) {
+        int count = 1;
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(new FileInputStream(file));
+            byte[] buffer = new byte[1024];
+            int readChars;
+            if (LINE_SEP.endsWith("\n")) {
+                while ((readChars = is.read(buffer, 0, 1024)) != -1) {
+                    for (int i = 0; i < readChars; ++i) {
+                        if (buffer[i] == '\n') ++count;
+                    }
+                }
+            } else {
+                while ((readChars = is.read(buffer, 0, 1024)) != -1) {
+                    for (int i = 0; i < readChars; ++i) {
+                        if (buffer[i] == '\r') ++count;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Return the size.
+     *
+     * @param filePath The path of file.
+     * @return the size
+     */
+    public static String getSize(final String filePath) {
+        return getSize(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the size.
+     *
+     * @param file The directory.
+     * @return the size
+     */
+    public static String getSize(final File file) {
+        if (file == null) return "";
+        if (file.isDirectory()) {
+            return getDirSize(file);
+        }
+        return getFileSize(file);
+    }
+
+    /**
+     * Return the size of directory.
+     *
+     * @param dir The directory.
+     * @return the size of directory
+     */
+    private static String getDirSize(final File dir) {
+        long len = getDirLength(dir);
+        return len == -1 ? "" : UtilsBridge.byte2FitMemorySize(len);
+    }
+
+    /**
+     * Return the size of file.
+     *
+     * @param file The file.
+     * @return the length of file
+     */
+    private static String getFileSize(final File file) {
+        long len = getFileLength(file);
+        return len == -1 ? "" : UtilsBridge.byte2FitMemorySize(len);
+    }
+
+    /**
+     * Return the length.
+     *
+     * @param filePath The path of file.
+     * @return the length
+     */
+    public static long getLength(final String filePath) {
+        return getLength(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the length.
+     *
+     * @param file The file.
+     * @return the length
+     */
+    public static long getLength(final File file) {
+        if (file == null) return 0;
+        if (file.isDirectory()) {
+            return getDirLength(file);
+        }
+        return getFileLength(file);
+    }
+
+    /**
+     * Return the length of directory.
+     *
+     * @param dir The directory.
+     * @return the length of directory
+     */
+    private static long getDirLength(final File dir) {
+        if (!isDir(dir)) return 0;
+        long len = 0;
+        File[] files = dir.listFiles();
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    len += getDirLength(file);
+                } else {
+                    len += file.length();
+                }
+            }
+        }
+        return len;
+    }
+
+    /**
+     * Return the length of file.
+     *
+     * @param filePath The path of file.
+     * @return the length of file
+     */
+    public static long getFileLength(final String filePath) {
+        boolean isURL = filePath.matches("[a-zA-z]+://[^\\s]*");
+        if (isURL) {
+            try {
+                HttpsURLConnection conn = (HttpsURLConnection) new URL(filePath).openConnection();
+                conn.setRequestProperty("Accept-Encoding", "identity");
+                conn.connect();
+                if (conn.getResponseCode() == 200) {
+                    return conn.getContentLength();
+                }
+                return -1;
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return getFileLength(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the length of file.
+     *
+     * @param file The file.
+     * @return the length of file
+     */
+    private static long getFileLength(final File file) {
+        if (!isFile(file)) return -1;
+        return file.length();
+    }
+
+    /**
+     * Return the MD5 of file.
+     *
+     * @param filePath The path of file.
+     * @return the md5 of file
+     */
+    public static String getFileMD5ToString(final String filePath) {
+        File file = UtilsBridge.isSpace(filePath) ? null : new File(filePath);
+        return getFileMD5ToString(file);
+    }
+
+    /**
+     * Return the MD5 of file.
+     *
+     * @param file The file.
+     * @return the md5 of file
+     */
+    public static String getFileMD5ToString(final File file) {
+        return UtilsBridge.bytes2HexString(getFileMD5(file));
+    }
+
+    /**
+     * Return the MD5 of file.
+     *
+     * @param filePath The path of file.
+     * @return the md5 of file
+     */
+    public static byte[] getFileMD5(final String filePath) {
+        return getFileMD5(getFileByPath(filePath));
+    }
+
+    /**
+     * Return the MD5 of file.
+     *
+     * @param file The file.
+     * @return the md5 of file
+     */
+    public static byte[] getFileMD5(final File file) {
+        if (file == null) return null;
+        DigestInputStream dis = null;
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            dis = new DigestInputStream(fis, md);
+            byte[] buffer = new byte[1024 * 256];
+            while (true) {
+                if (!(dis.read(buffer) > 0)) break;
+            }
+            md = dis.getMessageDigest();
+            return md.digest();
+        } catch (NoSuchAlgorithmException | IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (dis != null) {
+                    dis.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the file's path of directory.
+     *
+     * @param file The file.
+     * @return the file's path of directory
+     */
+    public static String getDirName(final File file) {
+        if (file == null) return "";
+        return getDirName(file.getAbsolutePath());
+    }
+
+    /**
+     * Return the file's path of directory.
+     *
+     * @param filePath The path of file.
+     * @return the file's path of directory
+     */
+    public static String getDirName(final String filePath) {
+        if (UtilsBridge.isSpace(filePath)) return "";
+        int lastSep = filePath.lastIndexOf(File.separator);
+        return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1);
+    }
+
+    /**
+     * Return the name of file.
+     *
+     * @param file The file.
+     * @return the name of file
+     */
+    public static String getFileName(final File file) {
+        if (file == null) return "";
+        return getFileName(file.getAbsolutePath());
+    }
+
+    /**
+     * Return the name of file.
+     *
+     * @param filePath The path of file.
+     * @return the name of file
+     */
+    public static String getFileName(final String filePath) {
+        if (UtilsBridge.isSpace(filePath)) return "";
+        int lastSep = filePath.lastIndexOf(File.separator);
+        return lastSep == -1 ? filePath : filePath.substring(lastSep + 1);
+    }
+
+    /**
+     * Return the name of file without extension.
+     *
+     * @param file The file.
+     * @return the name of file without extension
+     */
+    public static String getFileNameNoExtension(final File file) {
+        if (file == null) return "";
+        return getFileNameNoExtension(file.getPath());
+    }
+
+    /**
+     * Return the name of file without extension.
+     *
+     * @param filePath The path of file.
+     * @return the name of file without extension
+     */
+    public static String getFileNameNoExtension(final String filePath) {
+        if (UtilsBridge.isSpace(filePath)) return "";
+        int lastPoi = filePath.lastIndexOf('.');
+        int lastSep = filePath.lastIndexOf(File.separator);
+        if (lastSep == -1) {
+            return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi));
+        }
+        if (lastPoi == -1 || lastSep > lastPoi) {
+            return filePath.substring(lastSep + 1);
+        }
+        return filePath.substring(lastSep + 1, lastPoi);
+    }
+
+    /**
+     * Return the extension of file.
+     *
+     * @param file The file.
+     * @return the extension of file
+     */
+    public static String getFileExtension(final File file) {
+        if (file == null) return "";
+        return getFileExtension(file.getPath());
+    }
+
+    /**
+     * Return the extension of file.
+     *
+     * @param filePath The path of file.
+     * @return the extension of file
+     */
+    public static String getFileExtension(final String filePath) {
+        if (UtilsBridge.isSpace(filePath)) return "";
+        int lastPoi = filePath.lastIndexOf('.');
+        int lastSep = filePath.lastIndexOf(File.separator);
+        if (lastPoi == -1 || lastSep >= lastPoi) return "";
+        return filePath.substring(lastPoi + 1);
+    }
+
+    /**
+     * Notify system to scan the file.
+     *
+     * @param filePath The path of file.
+     */
+    public static void notifySystemToScan(final String filePath) {
+        notifySystemToScan(getFileByPath(filePath));
+    }
+
+    /**
+     * Notify system to scan the file.
+     *
+     * @param file The file.
+     */
+    public static void notifySystemToScan(final File file) {
+        if (file == null || !file.exists()) return;
+        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+        intent.setData(Uri.parse("file://" + file.getAbsolutePath()));
+        Utils.getApp().sendBroadcast(intent);
+    }
+
+    /**
+     * Return the total size of file system.
+     *
+     * @param anyPathInFs Any path in file system.
+     * @return the total size of file system
+     */
+    public static long getFsTotalSize(String anyPathInFs) {
+        if (TextUtils.isEmpty(anyPathInFs)) return 0;
+        StatFs statFs = new StatFs(anyPathInFs);
+        long blockSize;
+        long totalSize;
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            blockSize = statFs.getBlockSizeLong();
+            totalSize = statFs.getBlockCountLong();
+        } else {
+            blockSize = statFs.getBlockSize();
+            totalSize = statFs.getBlockCount();
+        }
+        return blockSize * totalSize;
+    }
+
+    /**
+     * Return the available size of file system.
+     *
+     * @param anyPathInFs Any path in file system.
+     * @return the available size of file system
+     */
+    public static long getFsAvailableSize(final String anyPathInFs) {
+        if (TextUtils.isEmpty(anyPathInFs)) return 0;
+        StatFs statFs = new StatFs(anyPathInFs);
+        long blockSize;
+        long availableSize;
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            blockSize = statFs.getBlockSizeLong();
+            availableSize = statFs.getAvailableBlocksLong();
+        } else {
+            blockSize = statFs.getBlockSize();
+            availableSize = statFs.getAvailableBlocks();
+        }
+        return blockSize * availableSize;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // interface
+    ///////////////////////////////////////////////////////////////////////////
+
+    public interface OnReplaceListener {
+        boolean onReplace(File srcFile, File destFile);
+    }
+}

+ 105 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/FlashlightUtils.java

@@ -0,0 +1,105 @@
+package com.cooleshow.base.utils;
+
+import android.content.pm.PackageManager;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.util.Log;
+
+import java.io.IOException;
+
+import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
+import static android.hardware.Camera.Parameters.FLASH_MODE_TORCH;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/04/27
+ *     desc  : utils about flashlight
+ * </pre>
+ */
+public final class FlashlightUtils {
+
+    private static Camera         mCamera;
+    private static SurfaceTexture mSurfaceTexture;
+
+    private FlashlightUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether the device supports flashlight.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFlashlightEnable() {
+        return Utils.getApp()
+                .getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+    }
+
+    /**
+     * Return whether the flashlight is working.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFlashlightOn() {
+        if (!init()) return false;
+        Camera.Parameters parameters = mCamera.getParameters();
+        return FLASH_MODE_TORCH.equals(parameters.getFlashMode());
+    }
+
+    /**
+     * Turn on or turn off the flashlight.
+     *
+     * @param isOn True to turn on the flashlight, false otherwise.
+     */
+    public static void setFlashlightStatus(final boolean isOn) {
+        if (!init()) return;
+        final Camera.Parameters parameters = mCamera.getParameters();
+        if (isOn) {
+            if (!FLASH_MODE_TORCH.equals(parameters.getFlashMode())) {
+                try {
+                    mCamera.setPreviewTexture(mSurfaceTexture);
+                    mCamera.startPreview();
+                    parameters.setFlashMode(FLASH_MODE_TORCH);
+                    mCamera.setParameters(parameters);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        } else {
+            if (!FLASH_MODE_OFF.equals(parameters.getFlashMode())) {
+                parameters.setFlashMode(FLASH_MODE_OFF);
+                mCamera.setParameters(parameters);
+            }
+        }
+    }
+
+    /**
+     * Destroy the flashlight.
+     */
+    public static void destroy() {
+        if (mCamera == null) return;
+        mCamera.release();
+        mSurfaceTexture = null;
+        mCamera = null;
+    }
+
+    private static boolean init() {
+        if (mCamera == null) {
+            try {
+                mCamera = Camera.open(0);
+                mSurfaceTexture = new SurfaceTexture(0);
+            } catch (Throwable t) {
+                Log.e("FlashlightUtils", "init failed: ", t);
+                return false;
+            }
+        }
+        if (mCamera == null) {
+            Log.e("FlashlightUtils", "init failed.");
+            return false;
+        }
+        return true;
+    }
+}

+ 1881 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/FragmentUtils.java

@@ -0,0 +1,1881 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.AnimRes;
+import androidx.annotation.AnimatorRes;
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/01/17
+ *     desc  : utils about fragment
+ * </pre>
+ */
+public final class FragmentUtils {
+
+    private static final int TYPE_ADD_FRAGMENT       = 0x01;
+    private static final int TYPE_SHOW_FRAGMENT      = 0x01 << 1;
+    private static final int TYPE_HIDE_FRAGMENT      = 0x01 << 2;
+    private static final int TYPE_SHOW_HIDE_FRAGMENT = 0x01 << 3;
+    private static final int TYPE_REPLACE_FRAGMENT   = 0x01 << 4;
+    private static final int TYPE_REMOVE_FRAGMENT    = 0x01 << 5;
+    private static final int TYPE_REMOVE_TO_FRAGMENT = 0x01 << 6;
+
+    private static final String ARGS_ID           = "args_id";
+    private static final String ARGS_IS_HIDE      = "args_is_hide";
+    private static final String ARGS_IS_ADD_STACK = "args_is_add_stack";
+    private static final String ARGS_TAG          = "args_tag";
+
+    private FragmentUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId) {
+        add(fm, add, containerId, null, false, false);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param isHide      True to hide, false otherwise.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final boolean isHide) {
+        add(fm, add, containerId, null, isHide, false);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param isHide      True to hide, false otherwise.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final boolean isHide,
+                           final boolean isAddStack) {
+        add(fm, add, containerId, null, isHide, isAddStack);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim) {
+        add(fm, add, containerId, null, false, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param containerId The id of container.
+     * @param add         The fragment will be add.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final boolean isAddStack,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim) {
+        add(fm, add, containerId, null, isAddStack, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param containerId  The id of container.
+     * @param add          The fragment will be add.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim,
+                           @AnimatorRes @AnimRes final int popEnterAnim,
+                           @AnimatorRes @AnimRes final int popExitAnim) {
+        add(fm, add, containerId, null, false, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param containerId  The id of container.
+     * @param add          The fragment will be add.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final boolean isAddStack,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim,
+                           @AnimatorRes @AnimRes final int popEnterAnim,
+                           @AnimatorRes @AnimRes final int popExitAnim) {
+        add(fm, add, containerId, null, isAddStack, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param add            The fragment will be add.
+     * @param containerId    The id of container.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           @NonNull final View... sharedElements) {
+        add(fm, add, containerId, null, false, sharedElements);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param add            The fragment will be add.
+     * @param containerId    The id of container.
+     * @param isAddStack     True to add fragment in stack, false otherwise.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final boolean isAddStack,
+                           @NonNull final View... sharedElements) {
+        add(fm, add, containerId, null, isAddStack, sharedElements);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param adds        The fragments will be add.
+     * @param containerId The id of container.
+     * @param showIndex   The index of fragment will be shown.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final List<Fragment> adds,
+                           @IdRes final int containerId,
+                           final int showIndex) {
+        add(fm, adds.toArray(new Fragment[0]), containerId, null, showIndex);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param adds        The fragments will be add.
+     * @param containerId The id of container.
+     * @param showIndex   The index of fragment will be shown.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment[] adds,
+                           @IdRes final int containerId,
+                           final int showIndex) {
+        add(fm, adds, containerId, null, showIndex);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param tag         The tag of fragment.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag) {
+        add(fm, add, containerId, tag, false, false);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param tag         The tag of fragment.
+     * @param isHide      True to hide, false otherwise.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           final boolean isHide) {
+        add(fm, add, containerId, tag, isHide, false);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param tag         The tag of fragment.
+     * @param isHide      True to hide, false otherwise.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           final boolean isHide,
+                           final boolean isAddStack) {
+        putArgs(add, new Args(containerId, tag, isHide, isAddStack));
+        operateNoAnim(TYPE_ADD_FRAGMENT, fm, null, add);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param tag         The tag of fragment.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim) {
+        add(fm, add, containerId, tag, false, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param add         The fragment will be add.
+     * @param containerId The id of container.
+     * @param tag         The tag of fragment.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           final boolean isAddStack,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim) {
+        add(fm, add, containerId, tag, isAddStack, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param add          The fragment will be add.
+     * @param containerId  The id of container.
+     * @param tag          The tag of fragment.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim,
+                           @AnimatorRes @AnimRes final int popEnterAnim,
+                           @AnimatorRes @AnimRes final int popExitAnim) {
+        add(fm, add, containerId, tag, false, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param add          The fragment will be add.
+     * @param containerId  The id of container.
+     * @param tag          The tag of fragment.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           final boolean isAddStack,
+                           @AnimatorRes @AnimRes final int enterAnim,
+                           @AnimatorRes @AnimRes final int exitAnim,
+                           @AnimatorRes @AnimRes final int popEnterAnim,
+                           @AnimatorRes @AnimRes final int popExitAnim) {
+        FragmentTransaction ft = fm.beginTransaction();
+        putArgs(add, new Args(containerId, tag, false, isAddStack));
+        addAnim(ft, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+        operate(TYPE_ADD_FRAGMENT, fm, ft, null, add);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param add            The fragment will be add.
+     * @param tag            The tag of fragment.
+     * @param containerId    The id of container.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           @NonNull final View... sharedElements) {
+        add(fm, add, containerId, tag, false, sharedElements);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param add            The fragment will be add.
+     * @param containerId    The id of container.
+     * @param isAddStack     True to add fragment in stack, false otherwise.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment add,
+                           @IdRes final int containerId,
+                           final String tag,
+                           final boolean isAddStack,
+                           @NonNull final View... sharedElements) {
+        FragmentTransaction ft = fm.beginTransaction();
+        putArgs(add, new Args(containerId, tag, false, isAddStack));
+        addSharedElement(ft, sharedElements);
+        operate(TYPE_ADD_FRAGMENT, fm, ft, null, add);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param adds        The fragments will be add.
+     * @param containerId The id of container.
+     * @param showIndex   The index of fragment will be shown.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final List<Fragment> adds,
+                           @IdRes final int containerId,
+                           final String[] tags,
+                           final int showIndex) {
+        add(fm, adds.toArray(new Fragment[0]), containerId, tags, showIndex);
+    }
+
+    /**
+     * Add fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param adds        The fragments will be add.
+     * @param containerId The id of container.
+     * @param showIndex   The index of fragment will be shown.
+     */
+    public static void add(@NonNull final FragmentManager fm,
+                           @NonNull final Fragment[] adds,
+                           @IdRes final int containerId,
+                           final String[] tags,
+                           final int showIndex) {
+        if (tags == null) {
+            for (int i = 0, len = adds.length; i < len; ++i) {
+                putArgs(adds[i], new Args(containerId, null, showIndex != i, false));
+            }
+        } else {
+            for (int i = 0, len = adds.length; i < len; ++i) {
+                putArgs(adds[i], new Args(containerId, tags[i], showIndex != i, false));
+            }
+        }
+        operateNoAnim(TYPE_ADD_FRAGMENT, fm, null, adds);
+    }
+
+    /**
+     * Show fragment.
+     *
+     * @param show The fragment will be show.
+     */
+    public static void show(@NonNull final Fragment show) {
+        putArgs(show, false);
+        operateNoAnim(TYPE_SHOW_FRAGMENT, show.getFragmentManager(), null, show);
+    }
+
+    /**
+     * Show fragment.
+     *
+     * @param fm The manager of fragment.
+     */
+    public static void show(@NonNull final FragmentManager fm) {
+        List<Fragment> fragments = getFragments(fm);
+        for (Fragment show : fragments) {
+            putArgs(show, false);
+        }
+        operateNoAnim(TYPE_SHOW_FRAGMENT, fm, null, fragments.toArray(new Fragment[0]));
+    }
+
+    /**
+     * Hide fragment.
+     *
+     * @param hide The fragment will be hide.
+     */
+    public static void hide(@NonNull final Fragment hide) {
+        putArgs(hide, true);
+        operateNoAnim(TYPE_HIDE_FRAGMENT, hide.getFragmentManager(), null, hide);
+    }
+
+    /**
+     * Hide fragment.
+     *
+     * @param fm The manager of fragment.
+     */
+    public static void hide(@NonNull final FragmentManager fm) {
+        List<Fragment> fragments = getFragments(fm);
+        for (Fragment hide : fragments) {
+            putArgs(hide, true);
+        }
+        operateNoAnim(TYPE_HIDE_FRAGMENT, fm, null, fragments.toArray(new Fragment[0]));
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param show The fragment will be show.
+     * @param hide The fragment will be hide.
+     */
+    public static void showHide(@NonNull final Fragment show,
+                                @NonNull final Fragment hide) {
+        showHide(show, Collections.singletonList(hide));
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param showIndex The index of fragment will be shown.
+     * @param fragments The fragment will be hide.
+     */
+    public static void showHide(final int showIndex, @NonNull final Fragment... fragments) {
+        showHide(fragments[showIndex], fragments);
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param show The fragment will be show.
+     * @param hide The fragment will be hide.
+     */
+    public static void showHide(@NonNull final Fragment show, @NonNull final Fragment... hide) {
+        showHide(show, Arrays.asList(hide));
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param showIndex The index of fragment will be shown.
+     * @param fragments The fragments will be hide.
+     */
+    public static void showHide(final int showIndex, @NonNull final List<Fragment> fragments) {
+        showHide(fragments.get(showIndex), fragments);
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param show The fragment will be show.
+     * @param hide The fragment will be hide.
+     */
+    public static void showHide(@NonNull final Fragment show, @NonNull final List<Fragment> hide) {
+        for (Fragment fragment : hide) {
+            putArgs(fragment, fragment != show);
+        }
+        operateNoAnim(TYPE_SHOW_HIDE_FRAGMENT, show.getFragmentManager(), show, hide.toArray(new Fragment[0]));
+    }
+
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param show The fragment will be show.
+     * @param hide The fragment will be hide.
+     */
+    public static void showHide(@NonNull final Fragment show,
+                                @NonNull final Fragment hide, @AnimatorRes @AnimRes final int enterAnim,
+                                @AnimatorRes @AnimRes final int exitAnim,
+                                @AnimatorRes @AnimRes final int popEnterAnim,
+                                @AnimatorRes @AnimRes final int popExitAnim) {
+        showHide(show, Collections.singletonList(hide), enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param showIndex The index of fragment will be shown.
+     * @param fragments The fragments will be hide.
+     */
+    public static void showHide(final int showIndex, @NonNull final List<Fragment> fragments,
+                                @AnimatorRes @AnimRes final int enterAnim,
+                                @AnimatorRes @AnimRes final int exitAnim,
+                                @AnimatorRes @AnimRes final int popEnterAnim,
+                                @AnimatorRes @AnimRes final int popExitAnim) {
+        showHide(fragments.get(showIndex), fragments, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Show fragment then hide other fragment.
+     *
+     * @param show The fragment will be show.
+     * @param hide The fragment will be hide.
+     */
+    public static void showHide(@NonNull final Fragment show, @NonNull final List<Fragment> hide,
+                                @AnimatorRes @AnimRes final int enterAnim,
+                                @AnimatorRes @AnimRes final int exitAnim,
+                                @AnimatorRes @AnimRes final int popEnterAnim,
+                                @AnimatorRes @AnimRes final int popExitAnim) {
+        for (Fragment fragment : hide) {
+            putArgs(fragment, fragment != show);
+        }
+        FragmentManager fm = show.getFragmentManager();
+        if (fm != null) {
+            FragmentTransaction ft = fm.beginTransaction();
+            addAnim(ft, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+            operate(TYPE_SHOW_HIDE_FRAGMENT, fm, ft, show, hide.toArray(new Fragment[0]));
+        }
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment) {
+        replace(srcFragment, destFragment, null, false);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final boolean isAddStack) {
+        replace(srcFragment, destFragment, null, isAddStack);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(srcFragment, destFragment, null, false, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(srcFragment, destFragment, null, isAddStack, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        replace(srcFragment, destFragment, null, false,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        replace(srcFragment, destFragment, null, isAddStack,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment    The source of fragment.
+     * @param destFragment   The destination of fragment.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final View... sharedElements) {
+        replace(srcFragment, destFragment, null, false, sharedElements);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment    The source of fragment.
+     * @param destFragment   The destination of fragment.
+     * @param isAddStack     True to add fragment in stack, false otherwise.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final boolean isAddStack,
+                               final View... sharedElements) {
+        replace(srcFragment, destFragment, null, isAddStack, sharedElements);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param fragment    The new fragment to place in the container.
+     * @param containerId The id of container.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId) {
+        replace(fm, fragment, containerId, null, false);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param containerId The id of container.
+     * @param fragment    The new fragment to place in the container.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final boolean isAddStack) {
+        replace(fm, fragment, containerId, null, isAddStack);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param containerId The id of container.
+     * @param fragment    The new fragment to place in the container.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(fm, fragment, containerId, null, false, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param containerId The id of container.
+     * @param fragment    The new fragment to place in the container.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(fm, fragment, containerId, null, isAddStack, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param containerId  The id of container.
+     * @param fragment     The new fragment to place in the container.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        replace(fm, fragment, containerId, null, false,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param containerId  The id of container.
+     * @param fragment     The new fragment to place in the container.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        replace(fm, fragment, containerId, null, isAddStack,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param containerId    The id of container.
+     * @param fragment       The new fragment to place in the container.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final View... sharedElements) {
+        replace(fm, fragment, containerId, null, false, sharedElements);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param containerId    The id of container.
+     * @param fragment       The new fragment to place in the container.
+     * @param isAddStack     True to add fragment in stack, false otherwise.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final boolean isAddStack,
+                               final View... sharedElements) {
+        replace(fm, fragment, containerId, null, isAddStack, sharedElements);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param destTag      The destination of fragment's tag.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag) {
+        replace(srcFragment, destFragment, destTag, false);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param destTag      The destination of fragment's tag.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               final boolean isAddStack) {
+        FragmentManager fm = srcFragment.getFragmentManager();
+        if (fm == null) return;
+        Args args = getArgs(srcFragment);
+        replace(fm, destFragment, args.id, destTag, isAddStack);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param destTag      The destination of fragment's tag.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(srcFragment, destFragment, destTag, false, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param destTag      The destination of fragment's tag.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(srcFragment, destFragment, destTag, isAddStack, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param destTag      The destination of fragment's tag.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        replace(srcFragment, destFragment, destTag, false,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment  The source of fragment.
+     * @param destFragment The destination of fragment.
+     * @param destTag      The destination of fragment's tag.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        FragmentManager fm = srcFragment.getFragmentManager();
+        if (fm == null) return;
+        Args args = getArgs(srcFragment);
+        replace(fm, destFragment, args.id, destTag, isAddStack,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment    The source of fragment.
+     * @param destFragment   The destination of fragment.
+     * @param destTag        The destination of fragment's tag.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               final View... sharedElements) {
+        replace(srcFragment, destFragment, destTag, false, sharedElements);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param srcFragment    The source of fragment.
+     * @param destFragment   The destination of fragment.
+     * @param destTag        The destination of fragment's tag.
+     * @param isAddStack     True to add fragment in stack, false otherwise.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final Fragment srcFragment,
+                               @NonNull final Fragment destFragment,
+                               final String destTag,
+                               final boolean isAddStack,
+                               final View... sharedElements) {
+        FragmentManager fm = srcFragment.getFragmentManager();
+        if (fm == null) return;
+        Args args = getArgs(srcFragment);
+        replace(fm,
+                destFragment,
+                args.id,
+                destTag,
+                isAddStack,
+                sharedElements
+        );
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param fragment    The new fragment to place in the container.
+     * @param containerId The id of container.
+     * @param destTag     The destination of fragment's tag.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag) {
+        replace(fm, fragment, containerId, destTag, false);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param fragment    The new fragment to place in the container.
+     * @param containerId The id of container.
+     * @param destTag     The destination of fragment's tag.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               final boolean isAddStack) {
+        FragmentTransaction ft = fm.beginTransaction();
+        putArgs(fragment, new Args(containerId, destTag, false, isAddStack));
+        operate(TYPE_REPLACE_FRAGMENT, fm, ft, null, fragment);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param fragment    The new fragment to place in the container.
+     * @param containerId The id of container.
+     * @param destTag     The destination of fragment's tag.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(fm, fragment, containerId, destTag, false, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param fragment    The new fragment to place in the container.
+     * @param containerId The id of container.
+     * @param destTag     The destination of fragment's tag.
+     * @param isAddStack  True to add fragment in stack, false otherwise.
+     * @param enterAnim   An animation or animator resource ID used for the enter animation on the
+     *                    view of the fragment being added or attached.
+     * @param exitAnim    An animation or animator resource ID used for the exit animation on the
+     *                    view of the fragment being removed or detached.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim) {
+        replace(fm, fragment, containerId, destTag, isAddStack, enterAnim, exitAnim, 0, 0);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param fragment     The new fragment to place in the container.
+     * @param containerId  The id of container.
+     * @param destTag      The destination of fragment's tag.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        replace(fm, fragment, containerId, destTag, false,
+                enterAnim, exitAnim, popEnterAnim, popExitAnim);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm           The manager of fragment.
+     * @param fragment     The new fragment to place in the container.
+     * @param containerId  The id of container.
+     * @param destTag      The destination of fragment's tag.
+     * @param isAddStack   True to add fragment in stack, false otherwise.
+     * @param enterAnim    An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being added or attached.
+     * @param exitAnim     An animation or animator resource ID used for the exit animation on the
+     *                     view of the fragment being removed or detached.
+     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being readded or reattached caused by
+     *                     popBackStack() or similar methods.
+     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the
+     *                     view of the fragment being removed or detached caused by
+     *                     popBackStack() or similar methods.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               final boolean isAddStack,
+                               @AnimatorRes @AnimRes final int enterAnim,
+                               @AnimatorRes @AnimRes final int exitAnim,
+                               @AnimatorRes @AnimRes final int popEnterAnim,
+                               @AnimatorRes @AnimRes final int popExitAnim) {
+        FragmentTransaction ft = fm.beginTransaction();
+        putArgs(fragment, new Args(containerId, destTag, false, isAddStack));
+        addAnim(ft, enterAnim, exitAnim, popEnterAnim, popExitAnim);
+        operate(TYPE_REPLACE_FRAGMENT, fm, ft, null, fragment);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param fragment       The new fragment to place in the container.
+     * @param containerId    The id of container.
+     * @param destTag        The destination of fragment's tag.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               final View... sharedElements) {
+        replace(fm, fragment, containerId, destTag, false, sharedElements);
+    }
+
+    /**
+     * Replace fragment.
+     *
+     * @param fm             The manager of fragment.
+     * @param fragment       The new fragment to place in the container.
+     * @param containerId    The id of container.
+     * @param destTag        The destination of fragment's tag.
+     * @param isAddStack     True to add fragment in stack, false otherwise.
+     * @param sharedElements A View in a disappearing Fragment to match with a View in an
+     *                       appearing Fragment.
+     */
+    public static void replace(@NonNull final FragmentManager fm,
+                               @NonNull final Fragment fragment,
+                               @IdRes final int containerId,
+                               final String destTag,
+                               final boolean isAddStack,
+                               final View... sharedElements) {
+        FragmentTransaction ft = fm.beginTransaction();
+        putArgs(fragment, new Args(containerId, destTag, false, isAddStack));
+        addSharedElement(ft, sharedElements);
+        operate(TYPE_REPLACE_FRAGMENT, fm, ft, null, fragment);
+    }
+
+    /**
+     * Pop fragment.
+     *
+     * @param fm The manager of fragment.
+     */
+    public static void pop(@NonNull final FragmentManager fm) {
+        pop(fm, true);
+    }
+
+    /**
+     * Pop fragment.
+     *
+     * @param fm          The manager of fragment.
+     * @param isImmediate True to pop immediately, false otherwise.
+     */
+    public static void pop(@NonNull final FragmentManager fm,
+                           final boolean isImmediate) {
+        if (isImmediate) {
+            fm.popBackStackImmediate();
+        } else {
+            fm.popBackStack();
+        }
+    }
+
+    /**
+     * Pop to fragment.
+     *
+     * @param fm            The manager of fragment.
+     * @param popClz        The class of fragment will be popped to.
+     * @param isIncludeSelf True to include the fragment, false otherwise.
+     */
+    public static void popTo(@NonNull final FragmentManager fm,
+                             final Class<? extends Fragment> popClz,
+                             final boolean isIncludeSelf) {
+        popTo(fm, popClz, isIncludeSelf, true);
+    }
+
+    /**
+     * Pop to fragment.
+     *
+     * @param fm            The manager of fragment.
+     * @param popClz        The class of fragment will be popped to.
+     * @param isIncludeSelf True to include the fragment, false otherwise.
+     * @param isImmediate   True to pop immediately, false otherwise.
+     */
+    public static void popTo(@NonNull final FragmentManager fm,
+                             final Class<? extends Fragment> popClz,
+                             final boolean isIncludeSelf,
+                             final boolean isImmediate) {
+        if (isImmediate) {
+            fm.popBackStackImmediate(popClz.getName(),
+                    isIncludeSelf ? FragmentManager.POP_BACK_STACK_INCLUSIVE : 0);
+        } else {
+            fm.popBackStack(popClz.getName(),
+                    isIncludeSelf ? FragmentManager.POP_BACK_STACK_INCLUSIVE : 0);
+        }
+    }
+
+    /**
+     * Pop all fragments.
+     *
+     * @param fm The manager of fragment.
+     */
+    public static void popAll(@NonNull final FragmentManager fm) {
+        popAll(fm, true);
+    }
+
+    /**
+     * Pop all fragments.
+     *
+     * @param fm The manager of fragment.
+     */
+    public static void popAll(@NonNull final FragmentManager fm, final boolean isImmediate) {
+        if (fm.getBackStackEntryCount() > 0) {
+            FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(0);
+            if (isImmediate) {
+                fm.popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
+            } else {
+                fm.popBackStack(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
+            }
+        }
+    }
+
+    /**
+     * Remove fragment.
+     *
+     * @param remove The fragment will be removed.
+     */
+    public static void remove(@NonNull final Fragment remove) {
+        operateNoAnim(TYPE_REMOVE_FRAGMENT, remove.getFragmentManager(), null, remove);
+    }
+
+    /**
+     * Remove to fragment.
+     *
+     * @param removeTo      The fragment will be removed to.
+     * @param isIncludeSelf True to include the fragment, false otherwise.
+     */
+    public static void removeTo(@NonNull final Fragment removeTo, final boolean isIncludeSelf) {
+        operateNoAnim(TYPE_REMOVE_TO_FRAGMENT, removeTo.getFragmentManager(), isIncludeSelf ? removeTo : null, removeTo);
+    }
+
+    /**
+     * Remove all fragments.
+     *
+     * @param fm The manager of fragment.
+     */
+    public static void removeAll(@NonNull final FragmentManager fm) {
+        List<Fragment> fragments = getFragments(fm);
+        operateNoAnim(TYPE_REMOVE_FRAGMENT, fm, null, fragments.toArray(new Fragment[0]));
+    }
+
+    private static void putArgs(final Fragment fragment, final Args args) {
+        Bundle bundle = fragment.getArguments();
+        if (bundle == null) {
+            bundle = new Bundle();
+            fragment.setArguments(bundle);
+        }
+        bundle.putInt(ARGS_ID, args.id);
+        bundle.putBoolean(ARGS_IS_HIDE, args.isHide);
+        bundle.putBoolean(ARGS_IS_ADD_STACK, args.isAddStack);
+        bundle.putString(ARGS_TAG, args.tag);
+    }
+
+    private static void putArgs(final Fragment fragment, final boolean isHide) {
+        Bundle bundle = fragment.getArguments();
+        if (bundle == null) {
+            bundle = new Bundle();
+            fragment.setArguments(bundle);
+        }
+        bundle.putBoolean(ARGS_IS_HIDE, isHide);
+    }
+
+    private static Args getArgs(final Fragment fragment) {
+        Bundle bundle = fragment.getArguments();
+        if (bundle == null) bundle = Bundle.EMPTY;
+        return new Args(bundle.getInt(ARGS_ID, fragment.getId()),
+                bundle.getBoolean(ARGS_IS_HIDE),
+                bundle.getBoolean(ARGS_IS_ADD_STACK));
+    }
+
+    private static void operateNoAnim(final int type, @Nullable final FragmentManager fm,
+                                      final Fragment src,
+                                      Fragment... dest) {
+        if (fm == null) return;
+        FragmentTransaction ft = fm.beginTransaction();
+        operate(type, fm, ft, src, dest);
+    }
+
+    private static void operate(final int type,
+                                @NonNull final FragmentManager fm,
+                                final FragmentTransaction ft,
+                                final Fragment src,
+                                final Fragment... dest) {
+        if (src != null && src.isRemoving()) {
+            Log.e("FragmentUtils", src.getClass().getName() + " is isRemoving");
+            return;
+        }
+        String name;
+        Bundle args;
+        switch (type) {
+            case TYPE_ADD_FRAGMENT:
+                for (Fragment fragment : dest) {
+                    args = fragment.getArguments();
+                    if (args == null) return;
+                    name = args.getString(ARGS_TAG, fragment.getClass().getName());
+                    Fragment fragmentByTag = fm.findFragmentByTag(name);
+                    if (fragmentByTag != null && fragmentByTag.isAdded()) {
+                        ft.remove(fragmentByTag);
+                    }
+                    ft.add(args.getInt(ARGS_ID), fragment, name);
+                    if (args.getBoolean(ARGS_IS_HIDE)) ft.hide(fragment);
+                    if (args.getBoolean(ARGS_IS_ADD_STACK)) ft.addToBackStack(name);
+                }
+                break;
+            case TYPE_HIDE_FRAGMENT:
+                for (Fragment fragment : dest) {
+                    ft.hide(fragment);
+                }
+                break;
+            case TYPE_SHOW_FRAGMENT:
+                for (Fragment fragment : dest) {
+                    ft.show(fragment);
+                }
+                break;
+            case TYPE_SHOW_HIDE_FRAGMENT:
+                ft.show(src);
+                for (Fragment fragment : dest) {
+                    if (fragment != src) {
+                        ft.hide(fragment);
+                    }
+                }
+                break;
+            case TYPE_REPLACE_FRAGMENT:
+                args = dest[0].getArguments();
+                if (args == null) return;
+                name = args.getString(ARGS_TAG, dest[0].getClass().getName());
+                ft.replace(args.getInt(ARGS_ID), dest[0], name);
+                if (args.getBoolean(ARGS_IS_ADD_STACK)) ft.addToBackStack(name);
+                break;
+            case TYPE_REMOVE_FRAGMENT:
+                for (Fragment fragment : dest) {
+                    if (fragment != src) {
+                        ft.remove(fragment);
+                    }
+                }
+                break;
+            case TYPE_REMOVE_TO_FRAGMENT:
+                for (int i = dest.length - 1; i >= 0; --i) {
+                    Fragment fragment = dest[i];
+                    if (fragment == dest[0]) {
+                        if (src != null) ft.remove(fragment);
+                        break;
+                    }
+                    ft.remove(fragment);
+                }
+                break;
+        }
+        ft.commitAllowingStateLoss();
+        fm.executePendingTransactions();
+    }
+
+    private static void addAnim(final FragmentTransaction ft,
+                                final int enter,
+                                final int exit,
+                                final int popEnter,
+                                final int popExit) {
+        ft.setCustomAnimations(enter, exit, popEnter, popExit);
+    }
+
+    private static void addSharedElement(final FragmentTransaction ft,
+                                         final View... views) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            for (View view : views) {
+                ft.addSharedElement(view, view.getTransitionName());
+            }
+        }
+    }
+
+    /**
+     * Return the top fragment.
+     *
+     * @param fm The manager of fragment.
+     * @return the top fragment
+     */
+    public static Fragment getTop(@NonNull final FragmentManager fm) {
+        return getTopIsInStack(fm, null, false);
+    }
+
+    /**
+     * Return the top fragment in stack.
+     *
+     * @param fm The manager of fragment.
+     * @return the top fragment in stack
+     */
+    public static Fragment getTopInStack(@NonNull final FragmentManager fm) {
+        return getTopIsInStack(fm, null, true);
+    }
+
+    private static Fragment getTopIsInStack(@NonNull final FragmentManager fm,
+                                            Fragment parentFragment,
+                                            final boolean isInStack) {
+        List<Fragment> fragments = getFragments(fm);
+        for (int i = fragments.size() - 1; i >= 0; --i) {
+            Fragment fragment = fragments.get(i);
+            if (fragment != null) {
+                if (isInStack) {
+                    Bundle args = fragment.getArguments();
+                    if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {
+                        return getTopIsInStack(fragment.getChildFragmentManager(), fragment, true);
+                    }
+                } else {
+                    return getTopIsInStack(fragment.getChildFragmentManager(), fragment, false);
+                }
+            }
+        }
+        return parentFragment;
+    }
+
+    /**
+     * Return the top fragment which is shown.
+     *
+     * @param fm The manager of fragment.
+     * @return the top fragment which is shown
+     */
+    public static Fragment getTopShow(@NonNull final FragmentManager fm) {
+        return getTopShowIsInStack(fm, null, false);
+    }
+
+    /**
+     * Return the top fragment which is shown in stack.
+     *
+     * @param fm The manager of fragment.
+     * @return the top fragment which is shown in stack
+     */
+    public static Fragment getTopShowInStack(@NonNull final FragmentManager fm) {
+        return getTopShowIsInStack(fm, null, true);
+    }
+
+    private static Fragment getTopShowIsInStack(@NonNull final FragmentManager fm,
+                                                Fragment parentFragment,
+                                                final boolean isInStack) {
+        List<Fragment> fragments = getFragments(fm);
+        for (int i = fragments.size() - 1; i >= 0; --i) {
+            Fragment fragment = fragments.get(i);
+            if (fragment != null
+                    && fragment.isResumed()
+                    && fragment.isVisible()
+                    && fragment.getUserVisibleHint()) {
+                if (isInStack) {
+                    Bundle args = fragment.getArguments();
+                    if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {
+                        return getTopShowIsInStack(fragment.getChildFragmentManager(), fragment, true);
+                    }
+                } else {
+                    return getTopShowIsInStack(fragment.getChildFragmentManager(), fragment, false);
+                }
+            }
+        }
+        return parentFragment;
+    }
+
+    /**
+     * Return the fragments in manager.
+     *
+     * @param fm The manager of fragment.
+     * @return the fragments in manager
+     */
+    public static List<Fragment> getFragments(@NonNull final FragmentManager fm) {
+        List<Fragment> fragments = fm.getFragments();
+        if (fragments == null || fragments.isEmpty()) return Collections.emptyList();
+        return fragments;
+    }
+
+    /**
+     * Return the fragments in stack in manager.
+     *
+     * @param fm The manager of fragment.
+     * @return the fragments in stack in manager
+     */
+    public static List<Fragment> getFragmentsInStack(@NonNull final FragmentManager fm) {
+        List<Fragment> fragments = getFragments(fm);
+        List<Fragment> result = new ArrayList<>();
+        for (Fragment fragment : fragments) {
+            if (fragment != null) {
+                Bundle args = fragment.getArguments();
+                if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {
+                    result.add(fragment);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Return all fragments in manager.
+     *
+     * @param fm The manager of fragment.
+     * @return all fragments in manager
+     */
+    public static List<FragmentNode> getAllFragments(@NonNull final FragmentManager fm) {
+        return getAllFragments(fm, new ArrayList<FragmentNode>());
+    }
+
+    private static List<FragmentNode> getAllFragments(@NonNull final FragmentManager fm,
+                                                      final List<FragmentNode> result) {
+        List<Fragment> fragments = getFragments(fm);
+        for (int i = fragments.size() - 1; i >= 0; --i) {
+            Fragment fragment = fragments.get(i);
+            if (fragment != null) {
+                result.add(new FragmentNode(fragment,
+                        getAllFragments(fragment.getChildFragmentManager(),
+                                new ArrayList<FragmentNode>())));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Return all fragments in stack in manager.
+     *
+     * @param fm The manager of fragment.
+     * @return all fragments in stack in manager
+     */
+    public static List<FragmentNode> getAllFragmentsInStack(@NonNull final FragmentManager fm) {
+        return getAllFragmentsInStack(fm, new ArrayList<FragmentNode>());
+    }
+
+    private static List<FragmentNode> getAllFragmentsInStack(@NonNull final FragmentManager fm,
+                                                             final List<FragmentNode> result) {
+        List<Fragment> fragments = getFragments(fm);
+        for (int i = fragments.size() - 1; i >= 0; --i) {
+            Fragment fragment = fragments.get(i);
+            if (fragment != null) {
+                Bundle args = fragment.getArguments();
+                if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {
+                    result.add(new FragmentNode(fragment,
+                            getAllFragmentsInStack(fragment.getChildFragmentManager(),
+                                    new ArrayList<FragmentNode>())));
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Find fragment.
+     *
+     * @param fm      The manager of fragment.
+     * @param findClz The class of fragment will be found.
+     * @return the fragment matches class
+     */
+    public static Fragment findFragment(@NonNull final FragmentManager fm,
+                                        final Class<? extends Fragment> findClz) {
+        return fm.findFragmentByTag(findClz.getName());
+    }
+
+    /**
+     * Find fragment.
+     *
+     * @param fm  The manager of fragment.
+     * @param tag The tag of fragment will be found.
+     * @return the fragment matches class
+     */
+    public static Fragment findFragment(@NonNull final FragmentManager fm,
+                                        @NonNull final String tag) {
+        return fm.findFragmentByTag(tag);
+    }
+
+    /**
+     * Dispatch the back press for fragment.
+     *
+     * @param fragment The fragment.
+     * @return {@code true}: the fragment consumes the back press<br>{@code false}: otherwise
+     */
+    public static boolean dispatchBackPress(@NonNull final Fragment fragment) {
+        return fragment.isResumed()
+                && fragment.isVisible()
+                && fragment.getUserVisibleHint()
+                && fragment instanceof OnBackClickListener
+                && ((OnBackClickListener) fragment).onBackClick();
+    }
+
+    /**
+     * Dispatch the back press for fragment.
+     *
+     * @param fm The manager of fragment.
+     * @return {@code true}: the fragment consumes the back press<br>{@code false}: otherwise
+     */
+    public static boolean dispatchBackPress(@NonNull final FragmentManager fm) {
+        List<Fragment> fragments = getFragments(fm);
+        if (fragments == null || fragments.isEmpty()) return false;
+        for (int i = fragments.size() - 1; i >= 0; --i) {
+            Fragment fragment = fragments.get(i);
+            if (fragment != null
+                    && fragment.isResumed()
+                    && fragment.isVisible()
+                    && fragment.getUserVisibleHint()
+                    && fragment instanceof OnBackClickListener
+                    && ((OnBackClickListener) fragment).onBackClick()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Set background color for fragment.
+     *
+     * @param fragment The fragment.
+     * @param color    The background color.
+     */
+    public static void setBackgroundColor(@NonNull final Fragment fragment,
+                                          @ColorInt final int color) {
+        View view = fragment.getView();
+        if (view != null) {
+            view.setBackgroundColor(color);
+        }
+    }
+
+    /**
+     * Set background resource for fragment.
+     *
+     * @param fragment The fragment.
+     * @param resId    The resource id.
+     */
+    public static void setBackgroundResource(@NonNull final Fragment fragment,
+                                             @DrawableRes final int resId) {
+        View view = fragment.getView();
+        if (view != null) {
+            view.setBackgroundResource(resId);
+        }
+    }
+
+    /**
+     * Set background color for fragment.
+     *
+     * @param fragment   The fragment.
+     * @param background The background.
+     */
+    public static void setBackground(@NonNull final Fragment fragment, final Drawable background) {
+        View view = fragment.getView();
+        if (view == null) return;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            view.setBackground(background);
+        } else {
+            view.setBackgroundDrawable(background);
+        }
+    }
+
+    /**
+     * Return the simple name of fragment.
+     *
+     * @param fragment The fragment.
+     * @return the simple name of fragment
+     */
+    public static String getSimpleName(final Fragment fragment) {
+        return fragment == null ? "null" : fragment.getClass().getSimpleName();
+    }
+
+    private static class Args {
+        final int     id;
+        final boolean isHide;
+        final boolean isAddStack;
+        final String  tag;
+
+        Args(final int id, final boolean isHide, final boolean isAddStack) {
+            this(id, null, isHide, isAddStack);
+        }
+
+        Args(final int id, final String tag,
+             final boolean isHide, final boolean isAddStack) {
+            this.id = id;
+            this.tag = tag;
+            this.isHide = isHide;
+            this.isAddStack = isAddStack;
+        }
+    }
+
+    public static class FragmentNode {
+        final Fragment           fragment;
+        final List<FragmentNode> next;
+
+        public FragmentNode(final Fragment fragment, final List<FragmentNode> next) {
+            this.fragment = fragment;
+            this.next = next;
+        }
+
+        public Fragment getFragment() {
+            return fragment;
+        }
+
+        public List<FragmentNode> getNext() {
+            return next;
+        }
+
+        @Override
+        public String toString() {
+            return fragment.getClass().getSimpleName()
+                    + "->"
+                    + ((next == null || next.isEmpty()) ? "no child" : next.toString());
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // interface
+    ///////////////////////////////////////////////////////////////////////////
+
+    public interface OnBackClickListener {
+        boolean onBackClick();
+    }
+}

+ 283 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/GsonUtils.java

@@ -0,0 +1,283 @@
+package com.cooleshow.base.utils;
+
+import android.text.TextUtils;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import androidx.annotation.NonNull;
+
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/04/05
+ *     desc  : utils about gson
+ * </pre>
+ */
+public final class GsonUtils {
+
+    private static final String KEY_DEFAULT   = "defaultGson";
+    private static final String KEY_DELEGATE  = "delegateGson";
+    private static final String KEY_LOG_UTILS = "logUtilsGson";
+
+    private static final Map<String, Gson> GSONS = new ConcurrentHashMap<>();
+
+    private GsonUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Set the delegate of {@link Gson}.
+     *
+     * @param delegate The delegate of {@link Gson}.
+     */
+    public static void setGsonDelegate(Gson delegate) {
+        if (delegate == null) return;
+        GSONS.put(KEY_DELEGATE, delegate);
+    }
+
+    /**
+     * Set the {@link Gson} with key.
+     *
+     * @param key  The key.
+     * @param gson The {@link Gson}.
+     */
+    public static void setGson(final String key, final Gson gson) {
+        if (TextUtils.isEmpty(key) || gson == null) return;
+        GSONS.put(key, gson);
+    }
+
+    /**
+     * Return the {@link Gson} with key.
+     *
+     * @param key The key.
+     * @return the {@link Gson} with key
+     */
+    public static Gson getGson(final String key) {
+        return GSONS.get(key);
+    }
+
+    public static Gson getGson() {
+        Gson gsonDelegate = GSONS.get(KEY_DELEGATE);
+        if (gsonDelegate != null) {
+            return gsonDelegate;
+        }
+        Gson gsonDefault = GSONS.get(KEY_DEFAULT);
+        if (gsonDefault == null) {
+            gsonDefault = createGson();
+            GSONS.put(KEY_DEFAULT, gsonDefault);
+        }
+        return gsonDefault;
+    }
+
+    /**
+     * Serializes an object into json.
+     *
+     * @param object The object to serialize.
+     * @return object serialized into json.
+     */
+    public static String toJson(final Object object) {
+        return toJson(getGson(), object);
+    }
+
+    /**
+     * Serializes an object into json.
+     *
+     * @param src       The object to serialize.
+     * @param typeOfSrc The specific genericized type of src.
+     * @return object serialized into json.
+     */
+    public static String toJson(final Object src, @NonNull final Type typeOfSrc) {
+        return toJson(getGson(), src, typeOfSrc);
+    }
+
+    /**
+     * Serializes an object into json.
+     *
+     * @param gson   The gson.
+     * @param object The object to serialize.
+     * @return object serialized into json.
+     */
+    public static String toJson(@NonNull final Gson gson, final Object object) {
+        return gson.toJson(object);
+    }
+
+    /**
+     * Serializes an object into json.
+     *
+     * @param gson      The gson.
+     * @param src       The object to serialize.
+     * @param typeOfSrc The specific genericized type of src.
+     * @return object serialized into json.
+     */
+    public static String toJson(@NonNull final Gson gson, final Object src, @NonNull final Type typeOfSrc) {
+        return gson.toJson(src, typeOfSrc);
+    }
+
+    /**
+     * Converts {@link String} to given type.
+     *
+     * @param json The json to convert.
+     * @param type Type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(final String json, @NonNull final Class<T> type) {
+        return fromJson(getGson(), json, type);
+    }
+
+    /**
+     * Converts {@link String} to given type.
+     *
+     * @param json the json to convert.
+     * @param type type type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(final String json, @NonNull final Type type) {
+        return fromJson(getGson(), json, type);
+    }
+
+    /**
+     * Converts {@link Reader} to given type.
+     *
+     * @param reader the reader to convert.
+     * @param type   type type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(@NonNull final Reader reader, @NonNull final Class<T> type) {
+        return fromJson(getGson(), reader, type);
+    }
+
+    /**
+     * Converts {@link Reader} to given type.
+     *
+     * @param reader the reader to convert.
+     * @param type   type type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(@NonNull final Reader reader, @NonNull final Type type) {
+        return fromJson(getGson(), reader, type);
+    }
+
+    /**
+     * Converts {@link String} to given type.
+     *
+     * @param gson The gson.
+     * @param json The json to convert.
+     * @param type Type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(@NonNull final Gson gson, final String json, @NonNull final Class<T> type) {
+        return gson.fromJson(json, type);
+    }
+
+    /**
+     * Converts {@link String} to given type.
+     *
+     * @param gson The gson.
+     * @param json the json to convert.
+     * @param type type type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(@NonNull final Gson gson, final String json, @NonNull final Type type) {
+        return gson.fromJson(json, type);
+    }
+
+    /**
+     * Converts {@link Reader} to given type.
+     *
+     * @param gson   The gson.
+     * @param reader the reader to convert.
+     * @param type   type type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(@NonNull final Gson gson, final Reader reader, @NonNull final Class<T> type) {
+        return gson.fromJson(reader, type);
+    }
+
+    /**
+     * Converts {@link Reader} to given type.
+     *
+     * @param gson   The gson.
+     * @param reader the reader to convert.
+     * @param type   type type json will be converted to.
+     * @return instance of type
+     */
+    public static <T> T fromJson(@NonNull final Gson gson, final Reader reader, @NonNull final Type type) {
+        return gson.fromJson(reader, type);
+    }
+
+//    /**
+//     * Return the type of {@link List} with the {@code type}.
+//     *
+//     * @param type The type.
+//     * @return the type of {@link List} with the {@code type}
+//     */
+//    public static Type getListType(@NonNull final Type type) {
+//        return TypeToken.get(type).getType();
+//    }
+//
+//    /**
+//     * Return the type of {@link Set} with the {@code type}.
+//     *
+//     * @param type The type.
+//     * @return the type of {@link Set} with the {@code type}
+//     */
+//    public static Type getSetType(@NonNull final Type type) {
+//        return TypeToken.getParameterized(Set.class, type).getType();
+//    }
+//
+//    /**
+//     * Return the type of map with the {@code keyType} and {@code valueType}.
+//     *
+//     * @param keyType   The type of key.
+//     * @param valueType The type of value.
+//     * @return the type of map with the {@code keyType} and {@code valueType}
+//     */
+//    public static Type getMapType(@NonNull final Type keyType, @NonNull final Type valueType) {
+//        return TypeToken.getParameterized(Map.class, keyType, valueType).getType();
+//    }
+//
+//    /**
+//     * Return the type of array with the {@code type}.
+//     *
+//     * @param type The type.
+//     * @return the type of map with the {@code type}
+//     */
+//    public static Type getArrayType(@NonNull final Type type) {
+//        return TypeToken.getArray(type).getType();
+//    }
+//
+//    /**
+//     * Return the type of {@code rawType} with the {@code typeArguments}.
+//     *
+//     * @param rawType       The raw type.
+//     * @param typeArguments The type of arguments.
+//     * @return the type of map with the {@code type}
+//     */
+//    public static Type getType(@NonNull final Type rawType, @NonNull final Type... typeArguments) {
+//        return TypeToken.getParameterized(rawType, typeArguments).getType();
+//    }
+
+    static Gson getGson4LogUtils() {
+        Gson gson4LogUtils = GSONS.get(KEY_LOG_UTILS);
+        if (gson4LogUtils == null) {
+            gson4LogUtils = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
+            GSONS.put(KEY_LOG_UTILS, gson4LogUtils);
+        }
+        return gson4LogUtils;
+    }
+
+    private static Gson createGson() {
+        return new GsonBuilder().serializeNulls().disableHtmlEscaping().create();
+    }
+}

+ 2374 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ImageUtils.java

@@ -0,0 +1,2374 @@
+package com.cooleshow.base.utils;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/12
+ *     desc  : utils about image
+ * </pre>
+ */
+public final class ImageUtils {
+
+    private ImageUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Bitmap to bytes.
+     *
+     * @param bitmap The bitmap.
+     * @return bytes
+     */
+    public static byte[] bitmap2Bytes(final Bitmap bitmap) {
+        return bitmap2Bytes(bitmap, CompressFormat.PNG, 100);
+    }
+
+    /**
+     * Bitmap to bytes.
+     *
+     * @param bitmap  The bitmap.
+     * @param format  The format of bitmap.
+     * @param quality The quality.
+     * @return bytes
+     */
+    public static byte[] bitmap2Bytes(@Nullable final Bitmap bitmap, @NonNull final CompressFormat format, int quality) {
+        if (bitmap == null) return null;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        bitmap.compress(format, quality, baos);
+        return baos.toByteArray();
+    }
+
+    /**
+     * Bytes to bitmap.
+     *
+     * @param bytes The bytes.
+     * @return bitmap
+     */
+    public static Bitmap bytes2Bitmap(@Nullable final byte[] bytes) {
+        return (bytes == null || bytes.length == 0)
+                ? null
+                : BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+    }
+
+    /**
+     * Drawable to bitmap.
+     *
+     * @param drawable The drawable.
+     * @return bitmap
+     */
+    public static Bitmap drawable2Bitmap(@Nullable final Drawable drawable) {
+        if (drawable == null) return null;
+        if (drawable instanceof BitmapDrawable) {
+            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+            if (bitmapDrawable.getBitmap() != null) {
+                return bitmapDrawable.getBitmap();
+            }
+        }
+        Bitmap bitmap;
+        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+            bitmap = Bitmap.createBitmap(1, 1,
+                    drawable.getOpacity() != PixelFormat.OPAQUE
+                            ? Bitmap.Config.ARGB_8888
+                            : Bitmap.Config.RGB_565);
+        } else {
+            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                    drawable.getIntrinsicHeight(),
+                    drawable.getOpacity() != PixelFormat.OPAQUE
+                            ? Bitmap.Config.ARGB_8888
+                            : Bitmap.Config.RGB_565);
+        }
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /**
+     * Bitmap to drawable.
+     *
+     * @param bitmap The bitmap.
+     * @return drawable
+     */
+    public static Drawable bitmap2Drawable(@Nullable final Bitmap bitmap) {
+        return bitmap == null ? null : new BitmapDrawable(Utils.getApp().getResources(), bitmap);
+    }
+
+    /**
+     * Drawable to bytes.
+     *
+     * @param drawable The drawable.
+     * @return bytes
+     */
+    public static byte[] drawable2Bytes(@Nullable final Drawable drawable) {
+        return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable));
+    }
+
+    /**
+     * Drawable to bytes.
+     *
+     * @param drawable The drawable.
+     * @param format   The format of bitmap.
+     * @return bytes
+     */
+    public static byte[] drawable2Bytes(final Drawable drawable, final CompressFormat format, int quality) {
+        return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable), format, quality);
+    }
+
+    /**
+     * Bytes to drawable.
+     *
+     * @param bytes The bytes.
+     * @return drawable
+     */
+    public static Drawable bytes2Drawable(final byte[] bytes) {
+        return bitmap2Drawable(bytes2Bitmap(bytes));
+    }
+
+    /**
+     * View to bitmap.
+     *
+     * @param view The view.
+     * @return bitmap
+     */
+    public static Bitmap view2Bitmap(final View view) {
+        if (view == null) return null;
+        boolean drawingCacheEnabled = view.isDrawingCacheEnabled();
+        boolean willNotCacheDrawing = view.willNotCacheDrawing();
+        view.setDrawingCacheEnabled(true);
+        view.setWillNotCacheDrawing(false);
+        Bitmap drawingCache = view.getDrawingCache();
+        Bitmap bitmap;
+        if (null == drawingCache || drawingCache.isRecycled()) {
+            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
+            view.buildDrawingCache();
+            drawingCache = view.getDrawingCache();
+            if (null == drawingCache || drawingCache.isRecycled()) {
+                bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.RGB_565);
+                Canvas canvas = new Canvas(bitmap);
+                view.draw(canvas);
+            } else {
+                bitmap = Bitmap.createBitmap(drawingCache);
+            }
+        } else {
+            bitmap = Bitmap.createBitmap(drawingCache);
+        }
+        view.setWillNotCacheDrawing(willNotCacheDrawing);
+        view.setDrawingCacheEnabled(drawingCacheEnabled);
+        return bitmap;
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param file The file.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final File file) {
+        if (file == null) return null;
+        return BitmapFactory.decodeFile(file.getAbsolutePath());
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param file      The file.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final File file, final int maxWidth, final int maxHeight) {
+        if (file == null) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param filePath The path of file.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final String filePath) {
+        if (UtilsBridge.isSpace(filePath)) return null;
+        return BitmapFactory.decodeFile(filePath);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param filePath  The path of file.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final String filePath, final int maxWidth, final int maxHeight) {
+        if (UtilsBridge.isSpace(filePath)) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFile(filePath, options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFile(filePath, options);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param is The input stream.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final InputStream is) {
+        if (is == null) return null;
+        return BitmapFactory.decodeStream(is);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param is        The input stream.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final InputStream is, final int maxWidth, final int maxHeight) {
+        if (is == null) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeStream(is, null, options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeStream(is, null, options);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param data   The data.
+     * @param offset The offset.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final byte[] data, final int offset) {
+        if (data.length == 0) return null;
+        return BitmapFactory.decodeByteArray(data, offset, data.length);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param data      The data.
+     * @param offset    The offset.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final byte[] data,
+                                   final int offset,
+                                   final int maxWidth,
+                                   final int maxHeight) {
+        if (data.length == 0) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeByteArray(data, offset, data.length, options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeByteArray(data, offset, data.length, options);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param resId The resource id.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(@DrawableRes final int resId) {
+        Drawable drawable = ContextCompat.getDrawable(Utils.getApp(), resId);
+        if (drawable == null) {
+            return null;
+        }
+        Canvas canvas = new Canvas();
+        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        canvas.setBitmap(bitmap);
+        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param resId     The resource id.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(@DrawableRes final int resId,
+                                   final int maxWidth,
+                                   final int maxHeight) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        final Resources resources = Utils.getApp().getResources();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeResource(resources, resId, options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeResource(resources, resId, options);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param fd The file descriptor.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final FileDescriptor fd) {
+        if (fd == null) return null;
+        return BitmapFactory.decodeFileDescriptor(fd);
+    }
+
+    /**
+     * Return bitmap.
+     *
+     * @param fd        The file descriptor
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final FileDescriptor fd,
+                                   final int maxWidth,
+                                   final int maxHeight) {
+        if (fd == null) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFileDescriptor(fd, null, options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFileDescriptor(fd, null, options);
+    }
+
+    /**
+     * Return the bitmap with the specified color.
+     *
+     * @param src   The source of bitmap.
+     * @param color The color.
+     * @return the bitmap with the specified color
+     */
+    public static Bitmap drawColor(@NonNull final Bitmap src, @ColorInt final int color) {
+        return drawColor(src, color, false);
+    }
+
+    /**
+     * Return the bitmap with the specified color.
+     *
+     * @param src     The source of bitmap.
+     * @param color   The color.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the bitmap with the specified color
+     */
+    public static Bitmap drawColor(@NonNull final Bitmap src,
+                                   @ColorInt final int color,
+                                   final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
+        Canvas canvas = new Canvas(ret);
+        canvas.drawColor(color, PorterDuff.Mode.DARKEN);
+        return ret;
+    }
+
+    /**
+     * Return the scaled bitmap.
+     *
+     * @param src       The source of bitmap.
+     * @param newWidth  The new width.
+     * @param newHeight The new height.
+     * @return the scaled bitmap
+     */
+    public static Bitmap scale(final Bitmap src, final int newWidth, final int newHeight) {
+        return scale(src, newWidth, newHeight, false);
+    }
+
+    /**
+     * Return the scaled bitmap.
+     *
+     * @param src       The source of bitmap.
+     * @param newWidth  The new width.
+     * @param newHeight The new height.
+     * @param recycle   True to recycle the source of bitmap, false otherwise.
+     * @return the scaled bitmap
+     */
+    public static Bitmap scale(final Bitmap src,
+                               final int newWidth,
+                               final int newHeight,
+                               final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = Bitmap.createScaledBitmap(src, newWidth, newHeight, true);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the scaled bitmap
+     *
+     * @param src         The source of bitmap.
+     * @param scaleWidth  The scale of width.
+     * @param scaleHeight The scale of height.
+     * @return the scaled bitmap
+     */
+    public static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight) {
+        return scale(src, scaleWidth, scaleHeight, false);
+    }
+
+    /**
+     * Return the scaled bitmap
+     *
+     * @param src         The source of bitmap.
+     * @param scaleWidth  The scale of width.
+     * @param scaleHeight The scale of height.
+     * @param recycle     True to recycle the source of bitmap, false otherwise.
+     * @return the scaled bitmap
+     */
+    public static Bitmap scale(final Bitmap src,
+                               final float scaleWidth,
+                               final float scaleHeight,
+                               final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Matrix matrix = new Matrix();
+        matrix.setScale(scaleWidth, scaleHeight);
+        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the clipped bitmap.
+     *
+     * @param src    The source of bitmap.
+     * @param x      The x coordinate of the first pixel.
+     * @param y      The y coordinate of the first pixel.
+     * @param width  The width.
+     * @param height The height.
+     * @return the clipped bitmap
+     */
+    public static Bitmap clip(final Bitmap src,
+                              final int x,
+                              final int y,
+                              final int width,
+                              final int height) {
+        return clip(src, x, y, width, height, false);
+    }
+
+    /**
+     * Return the clipped bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param x       The x coordinate of the first pixel.
+     * @param y       The y coordinate of the first pixel.
+     * @param width   The width.
+     * @param height  The height.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the clipped bitmap
+     */
+    public static Bitmap clip(final Bitmap src,
+                              final int x,
+                              final int y,
+                              final int width,
+                              final int height,
+                              final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = Bitmap.createBitmap(src, x, y, width, height);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the skewed bitmap.
+     *
+     * @param src The source of bitmap.
+     * @param kx  The skew factor of x.
+     * @param ky  The skew factor of y.
+     * @return the skewed bitmap
+     */
+    public static Bitmap skew(final Bitmap src, final float kx, final float ky) {
+        return skew(src, kx, ky, 0, 0, false);
+    }
+
+    /**
+     * Return the skewed bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param kx      The skew factor of x.
+     * @param ky      The skew factor of y.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the skewed bitmap
+     */
+    public static Bitmap skew(final Bitmap src,
+                              final float kx,
+                              final float ky,
+                              final boolean recycle) {
+        return skew(src, kx, ky, 0, 0, recycle);
+    }
+
+    /**
+     * Return the skewed bitmap.
+     *
+     * @param src The source of bitmap.
+     * @param kx  The skew factor of x.
+     * @param ky  The skew factor of y.
+     * @param px  The x coordinate of the pivot point.
+     * @param py  The y coordinate of the pivot point.
+     * @return the skewed bitmap
+     */
+    public static Bitmap skew(final Bitmap src,
+                              final float kx,
+                              final float ky,
+                              final float px,
+                              final float py) {
+        return skew(src, kx, ky, px, py, false);
+    }
+
+    /**
+     * Return the skewed bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param kx      The skew factor of x.
+     * @param ky      The skew factor of y.
+     * @param px      The x coordinate of the pivot point.
+     * @param py      The y coordinate of the pivot point.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the skewed bitmap
+     */
+    public static Bitmap skew(final Bitmap src,
+                              final float kx,
+                              final float ky,
+                              final float px,
+                              final float py,
+                              final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Matrix matrix = new Matrix();
+        matrix.setSkew(kx, ky, px, py);
+        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the rotated bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param degrees The number of degrees.
+     * @param px      The x coordinate of the pivot point.
+     * @param py      The y coordinate of the pivot point.
+     * @return the rotated bitmap
+     */
+    public static Bitmap rotate(final Bitmap src,
+                                final int degrees,
+                                final float px,
+                                final float py) {
+        return rotate(src, degrees, px, py, false);
+    }
+
+    /**
+     * Return the rotated bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param degrees The number of degrees.
+     * @param px      The x coordinate of the pivot point.
+     * @param py      The y coordinate of the pivot point.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the rotated bitmap
+     */
+    public static Bitmap rotate(final Bitmap src,
+                                final int degrees,
+                                final float px,
+                                final float py,
+                                final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        if (degrees == 0) return src;
+        Matrix matrix = new Matrix();
+        matrix.setRotate(degrees, px, py);
+        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the rotated degree.
+     *
+     * @param filePath The path of file.
+     * @return the rotated degree
+     */
+    public static int getRotateDegree(final String filePath) {
+        try {
+            ExifInterface exifInterface = new ExifInterface(filePath);
+            int orientation = exifInterface.getAttributeInt(
+                    ExifInterface.TAG_ORIENTATION,
+                    ExifInterface.ORIENTATION_NORMAL
+            );
+            switch (orientation) {
+                case ExifInterface.ORIENTATION_ROTATE_90:
+                    return 90;
+                case ExifInterface.ORIENTATION_ROTATE_180:
+                    return 180;
+                case ExifInterface.ORIENTATION_ROTATE_270:
+                    return 270;
+                default:
+                    return 0;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return -1;
+        }
+    }
+
+    /**
+     * Return the round bitmap.
+     *
+     * @param src The source of bitmap.
+     * @return the round bitmap
+     */
+    public static Bitmap toRound(final Bitmap src) {
+        return toRound(src, 0, 0, false);
+    }
+
+    /**
+     * Return the round bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the round bitmap
+     */
+    public static Bitmap toRound(final Bitmap src, final boolean recycle) {
+        return toRound(src, 0, 0, recycle);
+    }
+
+    /**
+     * Return the round bitmap.
+     *
+     * @param src         The source of bitmap.
+     * @param borderSize  The size of border.
+     * @param borderColor The color of border.
+     * @return the round bitmap
+     */
+    public static Bitmap toRound(final Bitmap src,
+                                 @IntRange(from = 0) int borderSize,
+                                 @ColorInt int borderColor) {
+        return toRound(src, borderSize, borderColor, false);
+    }
+
+    /**
+     * Return the round bitmap.
+     *
+     * @param src         The source of bitmap.
+     * @param recycle     True to recycle the source of bitmap, false otherwise.
+     * @param borderSize  The size of border.
+     * @param borderColor The color of border.
+     * @return the round bitmap
+     */
+    public static Bitmap toRound(final Bitmap src,
+                                 @IntRange(from = 0) int borderSize,
+                                 @ColorInt int borderColor,
+                                 final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        int width = src.getWidth();
+        int height = src.getHeight();
+        int size = Math.min(width, height);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig());
+        float center = size / 2f;
+        RectF rectF = new RectF(0, 0, width, height);
+        rectF.inset((width - size) / 2f, (height - size) / 2f);
+        Matrix matrix = new Matrix();
+        matrix.setTranslate(rectF.left, rectF.top);
+        if (width != height) {
+            matrix.preScale((float) size / width, (float) size / height);
+        }
+        BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+        shader.setLocalMatrix(matrix);
+        paint.setShader(shader);
+        Canvas canvas = new Canvas(ret);
+        canvas.drawRoundRect(rectF, center, center, paint);
+        if (borderSize > 0) {
+            paint.setShader(null);
+            paint.setColor(borderColor);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeWidth(borderSize);
+            float radius = center - borderSize / 2f;
+            canvas.drawCircle(width / 2f, height / 2f, radius, paint);
+        }
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the round corner bitmap.
+     *
+     * @param src    The source of bitmap.
+     * @param radius The radius of corner.
+     * @return the round corner bitmap
+     */
+    public static Bitmap toRoundCorner(final Bitmap src, final float radius) {
+        return toRoundCorner(src, radius, 0, 0, false);
+    }
+
+    /**
+     * Return the round corner bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param radius  The radius of corner.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the round corner bitmap
+     */
+    public static Bitmap toRoundCorner(final Bitmap src,
+                                       final float radius,
+                                       final boolean recycle) {
+        return toRoundCorner(src, radius, 0, 0, recycle);
+    }
+
+    /**
+     * Return the round corner bitmap.
+     *
+     * @param src         The source of bitmap.
+     * @param radius      The radius of corner.
+     * @param borderSize  The size of border.
+     * @param borderColor The color of border.
+     * @return the round corner bitmap
+     */
+    public static Bitmap toRoundCorner(final Bitmap src,
+                                       final float radius,
+                                       @FloatRange(from = 0) float borderSize,
+                                       @ColorInt int borderColor) {
+        return toRoundCorner(src, radius, borderSize, borderColor, false);
+    }
+
+    /**
+     * Return the round corner bitmap.
+     *
+     * @param src         The source of bitmap.
+     * @param radii       Array of 8 values, 4 pairs of [X,Y] radii
+     * @param borderSize  The size of border.
+     * @param borderColor The color of border.
+     * @return the round corner bitmap
+     */
+    public static Bitmap toRoundCorner(final Bitmap src,
+                                       final float[] radii,
+                                       @FloatRange(from = 0) float borderSize,
+                                       @ColorInt int borderColor) {
+        return toRoundCorner(src, radii, borderSize, borderColor, false);
+    }
+
+    /**
+     * Return the round corner bitmap.
+     *
+     * @param src         The source of bitmap.
+     * @param radius      The radius of corner.
+     * @param borderSize  The size of border.
+     * @param borderColor The color of border.
+     * @param recycle     True to recycle the source of bitmap, false otherwise.
+     * @return the round corner bitmap
+     */
+    public static Bitmap toRoundCorner(final Bitmap src,
+                                       final float radius,
+                                       @FloatRange(from = 0) float borderSize,
+                                       @ColorInt int borderColor,
+                                       final boolean recycle) {
+        float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};
+        return toRoundCorner(src, radii, borderSize, borderColor, recycle);
+    }
+
+    /**
+     * Return the round corner bitmap.
+     *
+     * @param src         The source of bitmap.
+     * @param radii       Array of 8 values, 4 pairs of [X,Y] radii
+     * @param borderSize  The size of border.
+     * @param borderColor The color of border.
+     * @param recycle     True to recycle the source of bitmap, false otherwise.
+     * @return the round corner bitmap
+     */
+    public static Bitmap toRoundCorner(final Bitmap src,
+                                       final float[] radii,
+                                       @FloatRange(from = 0) float borderSize,
+                                       @ColorInt int borderColor,
+                                       final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        int width = src.getWidth();
+        int height = src.getHeight();
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig());
+        BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+        paint.setShader(shader);
+        Canvas canvas = new Canvas(ret);
+        RectF rectF = new RectF(0, 0, width, height);
+        float halfBorderSize = borderSize / 2f;
+        rectF.inset(halfBorderSize, halfBorderSize);
+        Path path = new Path();
+        path.addRoundRect(rectF, radii, Path.Direction.CW);
+        canvas.drawPath(path, paint);
+        if (borderSize > 0) {
+            paint.setShader(null);
+            paint.setColor(borderColor);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeWidth(borderSize);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+            canvas.drawPath(path, paint);
+        }
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the round corner bitmap with border.
+     *
+     * @param src          The source of bitmap.
+     * @param borderSize   The size of border.
+     * @param color        The color of border.
+     * @param cornerRadius The radius of corner.
+     * @return the round corner bitmap with border
+     */
+    public static Bitmap addCornerBorder(final Bitmap src,
+                                         @FloatRange(from = 1) final float borderSize,
+                                         @ColorInt final int color,
+                                         @FloatRange(from = 0) final float cornerRadius) {
+        return addBorder(src, borderSize, color, false, cornerRadius, false);
+    }
+
+    /**
+     * Return the round corner bitmap with border.
+     *
+     * @param src        The source of bitmap.
+     * @param borderSize The size of border.
+     * @param color      The color of border.
+     * @param radii      Array of 8 values, 4 pairs of [X,Y] radii
+     * @return the round corner bitmap with border
+     */
+    public static Bitmap addCornerBorder(final Bitmap src,
+                                         @FloatRange(from = 1) final float borderSize,
+                                         @ColorInt final int color,
+                                         final float[] radii) {
+        return addBorder(src, borderSize, color, false, radii, false);
+    }
+
+    /**
+     * Return the round corner bitmap with border.
+     *
+     * @param src        The source of bitmap.
+     * @param borderSize The size of border.
+     * @param color      The color of border.
+     * @param radii      Array of 8 values, 4 pairs of [X,Y] radii
+     * @param recycle    True to recycle the source of bitmap, false otherwise.
+     * @return the round corner bitmap with border
+     */
+    public static Bitmap addCornerBorder(final Bitmap src,
+                                         @FloatRange(from = 1) final float borderSize,
+                                         @ColorInt final int color,
+                                         final float[] radii,
+                                         final boolean recycle) {
+        return addBorder(src, borderSize, color, false, radii, recycle);
+    }
+
+    /**
+     * Return the round corner bitmap with border.
+     *
+     * @param src          The source of bitmap.
+     * @param borderSize   The size of border.
+     * @param color        The color of border.
+     * @param cornerRadius The radius of corner.
+     * @param recycle      True to recycle the source of bitmap, false otherwise.
+     * @return the round corner bitmap with border
+     */
+    public static Bitmap addCornerBorder(final Bitmap src,
+                                         @FloatRange(from = 1) final float borderSize,
+                                         @ColorInt final int color,
+                                         @FloatRange(from = 0) final float cornerRadius,
+                                         final boolean recycle) {
+        return addBorder(src, borderSize, color, false, cornerRadius, recycle);
+    }
+
+    /**
+     * Return the round bitmap with border.
+     *
+     * @param src        The source of bitmap.
+     * @param borderSize The size of border.
+     * @param color      The color of border.
+     * @return the round bitmap with border
+     */
+    public static Bitmap addCircleBorder(final Bitmap src,
+                                         @FloatRange(from = 1) final float borderSize,
+                                         @ColorInt final int color) {
+        return addBorder(src, borderSize, color, true, 0, false);
+    }
+
+    /**
+     * Return the round bitmap with border.
+     *
+     * @param src        The source of bitmap.
+     * @param borderSize The size of border.
+     * @param color      The color of border.
+     * @param recycle    True to recycle the source of bitmap, false otherwise.
+     * @return the round bitmap with border
+     */
+    public static Bitmap addCircleBorder(final Bitmap src,
+                                         @FloatRange(from = 1) final float borderSize,
+                                         @ColorInt final int color,
+                                         final boolean recycle) {
+        return addBorder(src, borderSize, color, true, 0, recycle);
+    }
+
+    /**
+     * Return the bitmap with border.
+     *
+     * @param src          The source of bitmap.
+     * @param borderSize   The size of border.
+     * @param color        The color of border.
+     * @param isCircle     True to draw circle, false to draw corner.
+     * @param cornerRadius The radius of corner.
+     * @param recycle      True to recycle the source of bitmap, false otherwise.
+     * @return the bitmap with border
+     */
+    private static Bitmap addBorder(final Bitmap src,
+                                    @FloatRange(from = 1) final float borderSize,
+                                    @ColorInt final int color,
+                                    final boolean isCircle,
+                                    final float cornerRadius,
+                                    final boolean recycle) {
+        float[] radii = {cornerRadius, cornerRadius, cornerRadius, cornerRadius,
+                cornerRadius, cornerRadius, cornerRadius, cornerRadius};
+        return addBorder(src, borderSize, color, isCircle, radii, recycle);
+    }
+
+    /**
+     * Return the bitmap with border.
+     *
+     * @param src        The source of bitmap.
+     * @param borderSize The size of border.
+     * @param color      The color of border.
+     * @param isCircle   True to draw circle, false to draw corner.
+     * @param radii      Array of 8 values, 4 pairs of [X,Y] radii
+     * @param recycle    True to recycle the source of bitmap, false otherwise.
+     * @return the bitmap with border
+     */
+    private static Bitmap addBorder(final Bitmap src,
+                                    @FloatRange(from = 1) final float borderSize,
+                                    @ColorInt final int color,
+                                    final boolean isCircle,
+                                    final float[] radii,
+                                    final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
+        int width = ret.getWidth();
+        int height = ret.getHeight();
+        Canvas canvas = new Canvas(ret);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setColor(color);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(borderSize);
+        if (isCircle) {
+            float radius = Math.min(width, height) / 2f - borderSize / 2f;
+            canvas.drawCircle(width / 2f, height / 2f, radius, paint);
+        } else {
+            RectF rectF = new RectF(0, 0, width, height);
+            float halfBorderSize = borderSize / 2f;
+            rectF.inset(halfBorderSize, halfBorderSize);
+            Path path = new Path();
+            path.addRoundRect(rectF, radii, Path.Direction.CW);
+            canvas.drawPath(path, paint);
+        }
+        return ret;
+    }
+
+    /**
+     * Return the bitmap with reflection.
+     *
+     * @param src              The source of bitmap.
+     * @param reflectionHeight The height of reflection.
+     * @return the bitmap with reflection
+     */
+    public static Bitmap addReflection(final Bitmap src, final int reflectionHeight) {
+        return addReflection(src, reflectionHeight, false);
+    }
+
+    /**
+     * Return the bitmap with reflection.
+     *
+     * @param src              The source of bitmap.
+     * @param reflectionHeight The height of reflection.
+     * @param recycle          True to recycle the source of bitmap, false otherwise.
+     * @return the bitmap with reflection
+     */
+    public static Bitmap addReflection(final Bitmap src,
+                                       final int reflectionHeight,
+                                       final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        final int REFLECTION_GAP = 0;
+        int srcWidth = src.getWidth();
+        int srcHeight = src.getHeight();
+        Matrix matrix = new Matrix();
+        matrix.preScale(1, -1);
+        Bitmap reflectionBitmap = Bitmap.createBitmap(src, 0, srcHeight - reflectionHeight,
+                srcWidth, reflectionHeight, matrix, false);
+        Bitmap ret = Bitmap.createBitmap(srcWidth, srcHeight + reflectionHeight, src.getConfig());
+        Canvas canvas = new Canvas(ret);
+        canvas.drawBitmap(src, 0, 0, null);
+        canvas.drawBitmap(reflectionBitmap, 0, srcHeight + REFLECTION_GAP, null);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        LinearGradient shader = new LinearGradient(
+                0, srcHeight,
+                0, ret.getHeight() + REFLECTION_GAP,
+                0x70FFFFFF,
+                0x00FFFFFF,
+                Shader.TileMode.MIRROR);
+        paint.setShader(shader);
+        paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_IN));
+        canvas.drawRect(0, srcHeight + REFLECTION_GAP, srcWidth, ret.getHeight(), paint);
+        if (!reflectionBitmap.isRecycled()) reflectionBitmap.recycle();
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the bitmap with text watermarking.
+     *
+     * @param src      The source of bitmap.
+     * @param content  The content of text.
+     * @param textSize The size of text.
+     * @param color    The color of text.
+     * @param x        The x coordinate of the first pixel.
+     * @param y        The y coordinate of the first pixel.
+     * @return the bitmap with text watermarking
+     */
+    public static Bitmap addTextWatermark(final Bitmap src,
+                                          final String content,
+                                          final int textSize,
+                                          @ColorInt final int color,
+                                          final float x,
+                                          final float y) {
+        return addTextWatermark(src, content, textSize, color, x, y, false);
+    }
+
+    /**
+     * Return the bitmap with text watermarking.
+     *
+     * @param src      The source of bitmap.
+     * @param content  The content of text.
+     * @param textSize The size of text.
+     * @param color    The color of text.
+     * @param x        The x coordinate of the first pixel.
+     * @param y        The y coordinate of the first pixel.
+     * @param recycle  True to recycle the source of bitmap, false otherwise.
+     * @return the bitmap with text watermarking
+     */
+    public static Bitmap addTextWatermark(final Bitmap src,
+                                          final String content,
+                                          final float textSize,
+                                          @ColorInt final int color,
+                                          final float x,
+                                          final float y,
+                                          final boolean recycle) {
+        if (isEmptyBitmap(src) || content == null) return null;
+        Bitmap ret = src.copy(src.getConfig(), true);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        Canvas canvas = new Canvas(ret);
+        paint.setColor(color);
+        paint.setTextSize(textSize);
+        Rect bounds = new Rect();
+        paint.getTextBounds(content, 0, content.length(), bounds);
+        canvas.drawText(content, x, y + textSize, paint);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the bitmap with image watermarking.
+     *
+     * @param src       The source of bitmap.
+     * @param watermark The image watermarking.
+     * @param x         The x coordinate of the first pixel.
+     * @param y         The y coordinate of the first pixel.
+     * @param alpha     The alpha of watermark.
+     * @return the bitmap with image watermarking
+     */
+    public static Bitmap addImageWatermark(final Bitmap src,
+                                           final Bitmap watermark,
+                                           final int x, final int y,
+                                           final int alpha) {
+        return addImageWatermark(src, watermark, x, y, alpha, false);
+    }
+
+    /**
+     * Return the bitmap with image watermarking.
+     *
+     * @param src       The source of bitmap.
+     * @param watermark The image watermarking.
+     * @param x         The x coordinate of the first pixel.
+     * @param y         The y coordinate of the first pixel.
+     * @param alpha     The alpha of watermark.
+     * @param recycle   True to recycle the source of bitmap, false otherwise.
+     * @return the bitmap with image watermarking
+     */
+    public static Bitmap addImageWatermark(final Bitmap src,
+                                           final Bitmap watermark,
+                                           final int x,
+                                           final int y,
+                                           final int alpha,
+                                           final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = src.copy(src.getConfig(), true);
+        if (!isEmptyBitmap(watermark)) {
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            Canvas canvas = new Canvas(ret);
+            paint.setAlpha(alpha);
+            canvas.drawBitmap(watermark, x, y, paint);
+        }
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the alpha bitmap.
+     *
+     * @param src The source of bitmap.
+     * @return the alpha bitmap
+     */
+    public static Bitmap toAlpha(final Bitmap src) {
+        return toAlpha(src, false);
+    }
+
+    /**
+     * Return the alpha bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the alpha bitmap
+     */
+    public static Bitmap toAlpha(final Bitmap src, final Boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = src.extractAlpha();
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the gray bitmap.
+     *
+     * @param src The source of bitmap.
+     * @return the gray bitmap
+     */
+    public static Bitmap toGray(final Bitmap src) {
+        return toGray(src, false);
+    }
+
+    /**
+     * Return the gray bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the gray bitmap
+     */
+    public static Bitmap toGray(final Bitmap src, final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        Bitmap ret = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());
+        Canvas canvas = new Canvas(ret);
+        Paint paint = new Paint();
+        ColorMatrix colorMatrix = new ColorMatrix();
+        colorMatrix.setSaturation(0);
+        ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
+        paint.setColorFilter(colorMatrixColorFilter);
+        canvas.drawBitmap(src, 0, 0, paint);
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the blur bitmap fast.
+     * <p>zoom out, blur, zoom in</p>
+     *
+     * @param src    The source of bitmap.
+     * @param scale  The scale(0...1).
+     * @param radius The radius(0...25).
+     * @return the blur bitmap
+     */
+    public static Bitmap fastBlur(final Bitmap src,
+                                  @FloatRange(
+                                          from = 0, to = 1, fromInclusive = false
+                                  ) final float scale,
+                                  @FloatRange(
+                                          from = 0, to = 25, fromInclusive = false
+                                  ) final float radius) {
+        return fastBlur(src, scale, radius, false, false);
+    }
+
+    /**
+     * Return the blur bitmap fast.
+     * <p>zoom out, blur, zoom in</p>
+     *
+     * @param src    The source of bitmap.
+     * @param scale  The scale(0...1).
+     * @param radius The radius(0...25).
+     * @return the blur bitmap
+     */
+    public static Bitmap fastBlur(final Bitmap src,
+                                  @FloatRange(
+                                          from = 0, to = 1, fromInclusive = false
+                                  ) final float scale,
+                                  @FloatRange(
+                                          from = 0, to = 25, fromInclusive = false
+                                  ) final float radius,
+                                  final boolean recycle) {
+        return fastBlur(src, scale, radius, recycle, false);
+    }
+
+    /**
+     * Return the blur bitmap fast.
+     * <p>zoom out, blur, zoom in</p>
+     *
+     * @param src           The source of bitmap.
+     * @param scale         The scale(0...1).
+     * @param radius        The radius(0...25).
+     * @param recycle       True to recycle the source of bitmap, false otherwise.
+     * @param isReturnScale True to return the scale blur bitmap, false otherwise.
+     * @return the blur bitmap
+     */
+    public static Bitmap fastBlur(final Bitmap src,
+                                  @FloatRange(
+                                          from = 0, to = 1, fromInclusive = false
+                                  ) final float scale,
+                                  @FloatRange(
+                                          from = 0, to = 25, fromInclusive = false
+                                  ) final float radius,
+                                  final boolean recycle,
+                                  final boolean isReturnScale) {
+        if (isEmptyBitmap(src)) return null;
+        int width = src.getWidth();
+        int height = src.getHeight();
+        Matrix matrix = new Matrix();
+        matrix.setScale(scale, scale);
+        Bitmap scaleBitmap =
+                Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
+        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
+        Canvas canvas = new Canvas();
+        PorterDuffColorFilter filter = new PorterDuffColorFilter(
+                Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);
+        paint.setColorFilter(filter);
+        canvas.scale(scale, scale);
+        canvas.drawBitmap(scaleBitmap, 0, 0, paint);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            scaleBitmap = renderScriptBlur(scaleBitmap, radius, recycle);
+        } else {
+            scaleBitmap = stackBlur(scaleBitmap, (int) radius, recycle);
+        }
+        if (scale == 1 || isReturnScale) {
+            if (recycle && !src.isRecycled() && scaleBitmap != src) src.recycle();
+            return scaleBitmap;
+        }
+        Bitmap ret = Bitmap.createScaledBitmap(scaleBitmap, width, height, true);
+        if (!scaleBitmap.isRecycled()) scaleBitmap.recycle();
+        if (recycle && !src.isRecycled() && ret != src) src.recycle();
+        return ret;
+    }
+
+    /**
+     * Return the blur bitmap using render script.
+     *
+     * @param src    The source of bitmap.
+     * @param radius The radius(0...25).
+     * @return the blur bitmap
+     */
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static Bitmap renderScriptBlur(final Bitmap src,
+                                          @FloatRange(
+                                                  from = 0, to = 25, fromInclusive = false
+                                          ) final float radius) {
+        return renderScriptBlur(src, radius, false);
+    }
+
+    /**
+     * Return the blur bitmap using render script.
+     *
+     * @param src     The source of bitmap.
+     * @param radius  The radius(0...25).
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the blur bitmap
+     */
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static Bitmap renderScriptBlur(final Bitmap src,
+                                          @FloatRange(
+                                                  from = 0, to = 25, fromInclusive = false
+                                          ) final float radius,
+                                          final boolean recycle) {
+        RenderScript rs = null;
+        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
+        try {
+            rs = RenderScript.create(Utils.getApp());
+            rs.setMessageHandler(new RenderScript.RSMessageHandler());
+            Allocation input = Allocation.createFromBitmap(rs,
+                    ret,
+                    Allocation.MipmapControl.MIPMAP_NONE,
+                    Allocation.USAGE_SCRIPT);
+            Allocation output = Allocation.createTyped(rs, input.getType());
+            ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+            blurScript.setInput(input);
+            blurScript.setRadius(radius);
+            blurScript.forEach(output);
+            output.copyTo(ret);
+        } finally {
+            if (rs != null) {
+                rs.destroy();
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Return the blur bitmap using stack.
+     *
+     * @param src    The source of bitmap.
+     * @param radius The radius(0...25).
+     * @return the blur bitmap
+     */
+    public static Bitmap stackBlur(final Bitmap src, final int radius) {
+        return stackBlur(src, radius, false);
+    }
+
+    /**
+     * Return the blur bitmap using stack.
+     *
+     * @param src     The source of bitmap.
+     * @param radius  The radius(0...25).
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the blur bitmap
+     */
+    public static Bitmap stackBlur(final Bitmap src, int radius, final boolean recycle) {
+        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
+        if (radius < 1) {
+            radius = 1;
+        }
+        int w = ret.getWidth();
+        int h = ret.getHeight();
+
+        int[] pix = new int[w * h];
+        ret.getPixels(pix, 0, w, 0, 0, w, h);
+
+        int wm = w - 1;
+        int hm = h - 1;
+        int wh = w * h;
+        int div = radius + radius + 1;
+
+        int r[] = new int[wh];
+        int g[] = new int[wh];
+        int b[] = new int[wh];
+        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
+        int vmin[] = new int[Math.max(w, h)];
+
+        int divsum = (div + 1) >> 1;
+        divsum *= divsum;
+        int dv[] = new int[256 * divsum];
+        for (i = 0; i < 256 * divsum; i++) {
+            dv[i] = (i / divsum);
+        }
+
+        yw = yi = 0;
+
+        int[][] stack = new int[div][3];
+        int stackpointer;
+        int stackstart;
+        int[] sir;
+        int rbs;
+        int r1 = radius + 1;
+        int routsum, goutsum, boutsum;
+        int rinsum, ginsum, binsum;
+
+        for (y = 0; y < h; y++) {
+            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
+            for (i = -radius; i <= radius; i++) {
+                p = pix[yi + Math.min(wm, Math.max(i, 0))];
+                sir = stack[i + radius];
+                sir[0] = (p & 0xff0000) >> 16;
+                sir[1] = (p & 0x00ff00) >> 8;
+                sir[2] = (p & 0x0000ff);
+                rbs = r1 - Math.abs(i);
+                rsum += sir[0] * rbs;
+                gsum += sir[1] * rbs;
+                bsum += sir[2] * rbs;
+                if (i > 0) {
+                    rinsum += sir[0];
+                    ginsum += sir[1];
+                    binsum += sir[2];
+                } else {
+                    routsum += sir[0];
+                    goutsum += sir[1];
+                    boutsum += sir[2];
+                }
+            }
+            stackpointer = radius;
+
+            for (x = 0; x < w; x++) {
+
+                r[yi] = dv[rsum];
+                g[yi] = dv[gsum];
+                b[yi] = dv[bsum];
+
+                rsum -= routsum;
+                gsum -= goutsum;
+                bsum -= boutsum;
+
+                stackstart = stackpointer - radius + div;
+                sir = stack[stackstart % div];
+
+                routsum -= sir[0];
+                goutsum -= sir[1];
+                boutsum -= sir[2];
+
+                if (y == 0) {
+                    vmin[x] = Math.min(x + radius + 1, wm);
+                }
+                p = pix[yw + vmin[x]];
+
+                sir[0] = (p & 0xff0000) >> 16;
+                sir[1] = (p & 0x00ff00) >> 8;
+                sir[2] = (p & 0x0000ff);
+
+                rinsum += sir[0];
+                ginsum += sir[1];
+                binsum += sir[2];
+
+                rsum += rinsum;
+                gsum += ginsum;
+                bsum += binsum;
+
+                stackpointer = (stackpointer + 1) % div;
+                sir = stack[(stackpointer) % div];
+
+                routsum += sir[0];
+                goutsum += sir[1];
+                boutsum += sir[2];
+
+                rinsum -= sir[0];
+                ginsum -= sir[1];
+                binsum -= sir[2];
+
+                yi++;
+            }
+            yw += w;
+        }
+        for (x = 0; x < w; x++) {
+            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
+            yp = -radius * w;
+            for (i = -radius; i <= radius; i++) {
+                yi = Math.max(0, yp) + x;
+
+                sir = stack[i + radius];
+
+                sir[0] = r[yi];
+                sir[1] = g[yi];
+                sir[2] = b[yi];
+
+                rbs = r1 - Math.abs(i);
+
+                rsum += r[yi] * rbs;
+                gsum += g[yi] * rbs;
+                bsum += b[yi] * rbs;
+
+                if (i > 0) {
+                    rinsum += sir[0];
+                    ginsum += sir[1];
+                    binsum += sir[2];
+                } else {
+                    routsum += sir[0];
+                    goutsum += sir[1];
+                    boutsum += sir[2];
+                }
+
+                if (i < hm) {
+                    yp += w;
+                }
+            }
+            yi = x;
+            stackpointer = radius;
+            for (y = 0; y < h; y++) {
+                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
+                pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
+
+                rsum -= routsum;
+                gsum -= goutsum;
+                bsum -= boutsum;
+
+                stackstart = stackpointer - radius + div;
+                sir = stack[stackstart % div];
+
+                routsum -= sir[0];
+                goutsum -= sir[1];
+                boutsum -= sir[2];
+
+                if (x == 0) {
+                    vmin[y] = Math.min(y + r1, hm) * w;
+                }
+                p = x + vmin[y];
+
+                sir[0] = r[p];
+                sir[1] = g[p];
+                sir[2] = b[p];
+
+                rinsum += sir[0];
+                ginsum += sir[1];
+                binsum += sir[2];
+
+                rsum += rinsum;
+                gsum += ginsum;
+                bsum += binsum;
+
+                stackpointer = (stackpointer + 1) % div;
+                sir = stack[stackpointer];
+
+                routsum += sir[0];
+                goutsum += sir[1];
+                boutsum += sir[2];
+
+                rinsum -= sir[0];
+                ginsum -= sir[1];
+                binsum -= sir[2];
+
+                yi += w;
+            }
+        }
+        ret.setPixels(pix, 0, w, 0, 0, w, h);
+        return ret;
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src      The source of bitmap.
+     * @param filePath The path of file.
+     * @param format   The format of the image.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final String filePath,
+                               final CompressFormat format) {
+        return save(src, filePath, format, 100, false);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src    The source of bitmap.
+     * @param file   The file.
+     * @param format The format of the image.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src, final File file, final CompressFormat format) {
+        return save(src, file, format, 100, false);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src      The source of bitmap.
+     * @param filePath The path of file.
+     * @param format   The format of the image.
+     * @param recycle  True to recycle the source of bitmap, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final String filePath,
+                               final CompressFormat format,
+                               final boolean recycle) {
+        return save(src, filePath, format, 100, recycle);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param file    The file.
+     * @param format  The format of the image.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final File file,
+                               final CompressFormat format,
+                               final boolean recycle) {
+        return save(src, file, format, 100, recycle);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src      The source of bitmap.
+     * @param filePath The path of file.
+     * @param format   The format of the image.
+     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
+     *                 small size, 100 meaning compress for max quality. Some
+     *                 formats, like PNG which is lossless, will ignore the
+     *                 quality setting
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final String filePath,
+                               final CompressFormat format,
+                               final int quality) {
+        return save(src, UtilsBridge.getFileByPath(filePath), format, quality, false);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src    The source of bitmap.
+     * @param file   The file.
+     * @param format The format of the image.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final File file,
+                               final CompressFormat format,
+                               final int quality) {
+        return save(src, file, format, quality, false);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src      The source of bitmap.
+     * @param filePath The path of file.
+     * @param format   The format of the image.
+     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
+     *                 small size, 100 meaning compress for max quality. Some
+     *                 formats, like PNG which is lossless, will ignore the
+     *                 quality setting
+     * @param recycle  True to recycle the source of bitmap, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final String filePath,
+                               final CompressFormat format,
+                               final int quality,
+                               final boolean recycle) {
+        return save(src, UtilsBridge.getFileByPath(filePath), format, quality, recycle);
+    }
+
+    /**
+     * Save the bitmap.
+     *
+     * @param src     The source of bitmap.
+     * @param file    The file.
+     * @param format  The format of the image.
+     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+     *                small size, 100 meaning compress for max quality. Some
+     *                formats, like PNG which is lossless, will ignore the
+     *                quality setting
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean save(final Bitmap src,
+                               final File file,
+                               final CompressFormat format,
+                               final int quality,
+                               final boolean recycle) {
+        if (isEmptyBitmap(src)) {
+            Log.e("ImageUtils", "bitmap is empty.");
+            return false;
+        }
+        if (src.isRecycled()) {
+            Log.e("ImageUtils", "bitmap is recycled.");
+            return false;
+        }
+        if (!UtilsBridge.createFileByDeleteOldFile(file)) {
+            Log.e("ImageUtils", "create or delete file <" + file + "> failed.");
+            return false;
+        }
+        OutputStream os = null;
+        boolean ret = false;
+        try {
+            os = new BufferedOutputStream(new FileOutputStream(file));
+            ret = src.compress(format, quality, os);
+            if (recycle && !src.isRecycled()) src.recycle();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * @param src    The source of bitmap.
+     * @param format The format of the image.
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final CompressFormat format) {
+        return save2Album(src, "", format, 100, false);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param format  The format of the image.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final CompressFormat format,
+                                  final boolean recycle) {
+        return save2Album(src, "", format, 100, recycle);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param format  The format of the image.
+     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+     *                small size, 100 meaning compress for max quality. Some
+     *                formats, like PNG which is lossless, will ignore the
+     *                quality setting
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final CompressFormat format,
+                                  final int quality) {
+        return save2Album(src, "", format, quality, false);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param format  The format of the image.
+     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+     *                small size, 100 meaning compress for max quality. Some
+     *                formats, like PNG which is lossless, will ignore the
+     *                quality setting
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final CompressFormat format,
+                                  final int quality,
+                                  final boolean recycle) {
+        return save2Album(src, "", format, quality, recycle);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param dirName The name of directory.
+     * @param format  The format of the image.
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final String dirName,
+                                  final CompressFormat format) {
+        return save2Album(src, dirName, format, 100, false);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param dirName The name of directory.
+     * @param format  The format of the image.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final String dirName,
+                                  final CompressFormat format,
+                                  final boolean recycle) {
+        return save2Album(src, dirName, format, 100, recycle);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param dirName The name of directory.
+     * @param format  The format of the image.
+     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+     *                small size, 100 meaning compress for max quality. Some
+     *                formats, like PNG which is lossless, will ignore the
+     *                quality setting
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final String dirName,
+                                  final CompressFormat format,
+                                  final int quality) {
+        return save2Album(src, dirName, format, quality, false);
+    }
+
+    /**
+     * @param src     The source of bitmap.
+     * @param dirName The name of directory.
+     * @param format  The format of the image.
+     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+     *                small size, 100 meaning compress for max quality. Some
+     *                formats, like PNG which is lossless, will ignore the
+     *                quality setting
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the file if save success, otherwise return null.
+     */
+    @Nullable
+    public static File save2Album(final Bitmap src,
+                                  final String dirName,
+                                  final CompressFormat format,
+                                  final int quality,
+                                  final boolean recycle) {
+        String safeDirName = TextUtils.isEmpty(dirName) ? Utils.getApp().getPackageName() : dirName;
+        String suffix = CompressFormat.JPEG.equals(format) ? "JPG" : format.name();
+        String fileName = System.currentTimeMillis() + "_" + quality + "." + suffix;
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            if (!UtilsBridge.isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+                Log.e("ImageUtils", "save to album need storage permission");
+                return null;
+            }
+            File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
+            File destFile = new File(picDir, safeDirName + "/" + fileName);
+            if (!save(src, destFile, format, quality, recycle)) {
+                return null;
+            }
+            UtilsBridge.notifySystemToScan(destFile);
+            return destFile;
+        } else {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+            contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
+            Uri contentUri;
+            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+            } else {
+                contentUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI;
+            }
+            contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/" + safeDirName);
+            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
+            Uri uri = Utils.getApp().getContentResolver().insert(contentUri, contentValues);
+            if (uri == null) {
+                return null;
+            }
+            OutputStream os = null;
+            try {
+                os = Utils.getApp().getContentResolver().openOutputStream(uri);
+                src.compress(format, quality, os);
+
+                contentValues.clear();
+                contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
+                Utils.getApp().getContentResolver().update(uri, contentValues, null, null);
+
+                return UtilsBridge.uri2File(uri);
+            } catch (Exception e) {
+                Utils.getApp().getContentResolver().delete(uri, null, null);
+                e.printStackTrace();
+                return null;
+            } finally {
+                try {
+                    if (os != null) {
+                        os.close();
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Return whether it is a image according to the file name.
+     *
+     * @param file The file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isImage(final File file) {
+        if (file == null || !file.exists()) {
+            return false;
+        }
+        return isImage(file.getPath());
+    }
+
+    /**
+     * Return whether it is a image according to the file name.
+     *
+     * @param filePath The path of file.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isImage(final String filePath) {
+        try {
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeFile(filePath, options);
+            return options.outWidth > 0 && options.outHeight > 0;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * Return the type of image.
+     *
+     * @param filePath The path of file.
+     * @return the type of image
+     */
+    public static ImageType getImageType(final String filePath) {
+        return getImageType(UtilsBridge.getFileByPath(filePath));
+    }
+
+    /**
+     * Return the type of image.
+     *
+     * @param file The file.
+     * @return the type of image
+     */
+    public static ImageType getImageType(final File file) {
+        if (file == null) return null;
+        InputStream is = null;
+        try {
+            is = new FileInputStream(file);
+            ImageType type = getImageType(is);
+            if (type != null) {
+                return type;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    private static ImageType getImageType(final InputStream is) {
+        if (is == null) return null;
+        try {
+            byte[] bytes = new byte[12];
+            return is.read(bytes) != -1 ? getImageType(bytes) : null;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static ImageType getImageType(final byte[] bytes) {
+        String type = UtilsBridge.bytes2HexString(bytes).toUpperCase();
+        if (type.contains("FFD8FF")) {
+            return ImageType.TYPE_JPG;
+        } else if (type.contains("89504E47")) {
+            return ImageType.TYPE_PNG;
+        } else if (type.contains("47494638")) {
+            return ImageType.TYPE_GIF;
+        } else if (type.contains("49492A00") || type.contains("4D4D002A")) {
+            return ImageType.TYPE_TIFF;
+        } else if (type.contains("424D")) {
+            return ImageType.TYPE_BMP;
+        } else if (type.startsWith("52494646") && type.endsWith("57454250")) {//524946461c57000057454250-12个字节
+            return ImageType.TYPE_WEBP;
+        } else if (type.contains("00000100") || type.contains("00000200")) {
+            return ImageType.TYPE_ICO;
+        } else {
+            return ImageType.TYPE_UNKNOWN;
+        }
+    }
+
+    private static boolean isJPEG(final byte[] b) {
+        return b.length >= 2
+                && (b[0] == (byte) 0xFF) && (b[1] == (byte) 0xD8);
+    }
+
+    private static boolean isGIF(final byte[] b) {
+        return b.length >= 6
+                && b[0] == 'G' && b[1] == 'I'
+                && b[2] == 'F' && b[3] == '8'
+                && (b[4] == '7' || b[4] == '9') && b[5] == 'a';
+    }
+
+    private static boolean isPNG(final byte[] b) {
+        return b.length >= 8
+                && (b[0] == (byte) 137 && b[1] == (byte) 80
+                && b[2] == (byte) 78 && b[3] == (byte) 71
+                && b[4] == (byte) 13 && b[5] == (byte) 10
+                && b[6] == (byte) 26 && b[7] == (byte) 10);
+    }
+
+    private static boolean isBMP(final byte[] b) {
+        return b.length >= 2
+                && (b[0] == 0x42) && (b[1] == 0x4d);
+    }
+
+    private static boolean isEmptyBitmap(final Bitmap src) {
+        return src == null || src.getWidth() == 0 || src.getHeight() == 0;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // about compress
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the compressed bitmap using scale.
+     *
+     * @param src       The source of bitmap.
+     * @param newWidth  The new width.
+     * @param newHeight The new height.
+     * @return the compressed bitmap
+     */
+    public static Bitmap compressByScale(final Bitmap src,
+                                         final int newWidth,
+                                         final int newHeight) {
+        return scale(src, newWidth, newHeight, false);
+    }
+
+    /**
+     * Return the compressed bitmap using scale.
+     *
+     * @param src       The source of bitmap.
+     * @param newWidth  The new width.
+     * @param newHeight The new height.
+     * @param recycle   True to recycle the source of bitmap, false otherwise.
+     * @return the compressed bitmap
+     */
+    public static Bitmap compressByScale(final Bitmap src,
+                                         final int newWidth,
+                                         final int newHeight,
+                                         final boolean recycle) {
+        return scale(src, newWidth, newHeight, recycle);
+    }
+
+    /**
+     * Return the compressed bitmap using scale.
+     *
+     * @param src         The source of bitmap.
+     * @param scaleWidth  The scale of width.
+     * @param scaleHeight The scale of height.
+     * @return the compressed bitmap
+     */
+    public static Bitmap compressByScale(final Bitmap src,
+                                         final float scaleWidth,
+                                         final float scaleHeight) {
+        return scale(src, scaleWidth, scaleHeight, false);
+    }
+
+    /**
+     * Return the compressed bitmap using scale.
+     *
+     * @param src         The source of bitmap.
+     * @param scaleWidth  The scale of width.
+     * @param scaleHeight The scale of height.
+     * @param recycle     True to recycle the source of bitmap, false otherwise.
+     * @return he compressed bitmap
+     */
+    public static Bitmap compressByScale(final Bitmap src,
+                                         final float scaleWidth,
+                                         final float scaleHeight,
+                                         final boolean recycle) {
+        return scale(src, scaleWidth, scaleHeight, recycle);
+    }
+
+    /**
+     * Return the compressed data using quality.
+     *
+     * @param src     The source of bitmap.
+     * @param quality The quality.
+     * @return the compressed data using quality
+     */
+    public static byte[] compressByQuality(final Bitmap src,
+                                           @IntRange(from = 0, to = 100) final int quality) {
+        return compressByQuality(src, quality, false);
+    }
+
+    /**
+     * Return the compressed data using quality.
+     *
+     * @param src     The source of bitmap.
+     * @param quality The quality.
+     * @param recycle True to recycle the source of bitmap, false otherwise.
+     * @return the compressed data using quality
+     */
+    public static byte[] compressByQuality(final Bitmap src,
+                                           @IntRange(from = 0, to = 100) final int quality,
+                                           final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        src.compress(Bitmap.CompressFormat.JPEG, quality, baos);
+        byte[] bytes = baos.toByteArray();
+        if (recycle && !src.isRecycled()) src.recycle();
+        return bytes;
+    }
+
+    /**
+     * Return the compressed data using quality.
+     *
+     * @param src         The source of bitmap.
+     * @param maxByteSize The maximum size of byte.
+     * @return the compressed data using quality
+     */
+    public static byte[] compressByQuality(final Bitmap src, final long maxByteSize) {
+        return compressByQuality(src, maxByteSize, false);
+    }
+
+    /**
+     * Return the compressed data using quality.
+     *
+     * @param src         The source of bitmap.
+     * @param maxByteSize The maximum size of byte.
+     * @param recycle     True to recycle the source of bitmap, false otherwise.
+     * @return the compressed data using quality
+     */
+    public static byte[] compressByQuality(final Bitmap src,
+                                           final long maxByteSize,
+                                           final boolean recycle) {
+        if (isEmptyBitmap(src) || maxByteSize <= 0) return new byte[0];
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        src.compress(CompressFormat.JPEG, 100, baos);
+        byte[] bytes;
+        if (baos.size() <= maxByteSize) {
+            bytes = baos.toByteArray();
+        } else {
+            baos.reset();
+            src.compress(CompressFormat.JPEG, 0, baos);
+            if (baos.size() >= maxByteSize) {
+                bytes = baos.toByteArray();
+            } else {
+                // find the best quality using binary search
+                int st = 0;
+                int end = 100;
+                int mid = 0;
+                while (st < end) {
+                    mid = (st + end) / 2;
+                    baos.reset();
+                    src.compress(CompressFormat.JPEG, mid, baos);
+                    int len = baos.size();
+                    if (len == maxByteSize) {
+                        break;
+                    } else if (len > maxByteSize) {
+                        end = mid - 1;
+                    } else {
+                        st = mid + 1;
+                    }
+                }
+                if (end == mid - 1) {
+                    baos.reset();
+                    src.compress(CompressFormat.JPEG, st, baos);
+                }
+                bytes = baos.toByteArray();
+            }
+        }
+        if (recycle && !src.isRecycled()) src.recycle();
+        return bytes;
+    }
+
+    /**
+     * Return the compressed bitmap using sample size.
+     *
+     * @param src        The source of bitmap.
+     * @param sampleSize The sample size.
+     * @return the compressed bitmap
+     */
+
+    public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize) {
+        return compressBySampleSize(src, sampleSize, false);
+    }
+
+    /**
+     * Return the compressed bitmap using sample size.
+     *
+     * @param src        The source of bitmap.
+     * @param sampleSize The sample size.
+     * @param recycle    True to recycle the source of bitmap, false otherwise.
+     * @return the compressed bitmap
+     */
+    public static Bitmap compressBySampleSize(final Bitmap src,
+                                              final int sampleSize,
+                                              final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inSampleSize = sampleSize;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+        byte[] bytes = baos.toByteArray();
+        if (recycle && !src.isRecycled()) src.recycle();
+        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+    }
+
+    /**
+     * Return the compressed bitmap using sample size.
+     *
+     * @param src       The source of bitmap.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return the compressed bitmap
+     */
+    public static Bitmap compressBySampleSize(final Bitmap src,
+                                              final int maxWidth,
+                                              final int maxHeight) {
+        return compressBySampleSize(src, maxWidth, maxHeight, false);
+    }
+
+    /**
+     * Return the compressed bitmap using sample size.
+     *
+     * @param src       The source of bitmap.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @param recycle   True to recycle the source of bitmap, false otherwise.
+     * @return the compressed bitmap
+     */
+    public static Bitmap compressBySampleSize(final Bitmap src,
+                                              final int maxWidth,
+                                              final int maxHeight,
+                                              final boolean recycle) {
+        if (isEmptyBitmap(src)) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+        byte[] bytes = baos.toByteArray();
+        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        if (recycle && !src.isRecycled()) src.recycle();
+        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+    }
+
+    /**
+     * Return the size of bitmap.
+     *
+     * @param filePath The path of file.
+     * @return the size of bitmap
+     */
+    public static int[] getSize(String filePath) {
+        return getSize(UtilsBridge.getFileByPath(filePath));
+    }
+
+    /**
+     * Return the size of bitmap.
+     *
+     * @param file The file.
+     * @return the size of bitmap
+     */
+    public static int[] getSize(File file) {
+        if (file == null) return new int[]{0, 0};
+        BitmapFactory.Options opts = new BitmapFactory.Options();
+        opts.inJustDecodeBounds = true;
+        BitmapFactory.decodeFile(file.getAbsolutePath(), opts);
+        return new int[]{opts.outWidth, opts.outHeight};
+    }
+
+    /**
+     * Return the sample size.
+     *
+     * @param options   The options.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return the sample size
+     */
+    public static int calculateInSampleSize(final BitmapFactory.Options options,
+                                            final int maxWidth,
+                                            final int maxHeight) {
+        int height = options.outHeight;
+        int width = options.outWidth;
+        int inSampleSize = 1;
+        while (height > maxHeight || width > maxWidth) {
+            height >>= 1;
+            width >>= 1;
+            inSampleSize <<= 1;
+        }
+        return inSampleSize;
+    }
+
+    public enum ImageType {
+        TYPE_JPG("jpg"),
+
+        TYPE_PNG("png"),
+
+        TYPE_GIF("gif"),
+
+        TYPE_TIFF("tiff"),
+
+        TYPE_BMP("bmp"),
+
+        TYPE_WEBP("webp"),
+
+        TYPE_ICO("ico"),
+
+        TYPE_UNKNOWN("unknown");
+
+        String value;
+
+        ImageType(String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+}

+ 538 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/IntentUtils.java

@@ -0,0 +1,538 @@
+package com.cooleshow.base.utils;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.provider.Settings;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+import androidx.core.content.FileProvider;
+
+import static android.Manifest.permission.CALL_PHONE;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/23
+ *     desc  : utils about intent
+ * </pre>
+ */
+public final class IntentUtils {
+
+    private IntentUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether the intent is available.
+     *
+     * @param intent The intent.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isIntentAvailable(final Intent intent) {
+        return Utils.getApp()
+                .getPackageManager()
+                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                .size() > 0;
+    }
+
+    /**
+     * Return the intent of install app.
+     * <p>Target APIs greater than 25 must hold
+     * {@code <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />}</p>
+     *
+     * @param filePath The path of file.
+     * @return the intent of install app
+     */
+    public static Intent getInstallAppIntent(final String filePath) {
+        return getInstallAppIntent(UtilsBridge.getFileByPath(filePath));
+    }
+
+    /**
+     * Return the intent of install app.
+     * <p>Target APIs greater than 25 must hold
+     * {@code <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />}</p>
+     *
+     * @param file The file.
+     * @return the intent of install app
+     */
+    public static Intent getInstallAppIntent(final File file) {
+        if (!UtilsBridge.isFileExists(file)) return null;
+        Uri uri;
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            uri = Uri.fromFile(file);
+        } else {
+            String authority = Utils.getApp().getPackageName() + ".utilcode.fileprovider";
+            uri = FileProvider.getUriForFile(Utils.getApp(), authority, file);
+        }
+        return getInstallAppIntent(uri);
+    }
+
+    /**
+     * Return the intent of install app.
+     * <p>Target APIs greater than 25 must hold
+     * {@code <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />}</p>
+     *
+     * @param uri The uri.
+     * @return the intent of install app
+     */
+    public static Intent getInstallAppIntent(final Uri uri) {
+        if (uri == null) return null;
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        String type = "application/vnd.android.package-archive";
+        intent.setDataAndType(uri, type);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        }
+        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    /**
+     * Return the intent of uninstall app.
+     * <p>Target APIs greater than 25 must hold
+     * Must hold {@code <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />}</p>
+     *
+     * @param pkgName The name of the package.
+     * @return the intent of uninstall app
+     */
+    public static Intent getUninstallAppIntent(final String pkgName) {
+        Intent intent = new Intent(Intent.ACTION_DELETE);
+        intent.setData(Uri.parse("package:" + pkgName));
+        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    /**
+     * Return the intent of launch app.
+     *
+     * @param pkgName The name of the package.
+     * @return the intent of launch app
+     */
+    public static Intent getLaunchAppIntent(final String pkgName) {
+        String launcherActivity = UtilsBridge.getLauncherActivity(pkgName);
+        if (UtilsBridge.isSpace(launcherActivity)) return null;
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setClassName(pkgName, launcherActivity);
+        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    /**
+     * Return the intent of launch app details settings.
+     *
+     * @param pkgName The name of the package.
+     * @return the intent of launch app details settings
+     */
+    public static Intent getLaunchAppDetailsSettingsIntent(final String pkgName) {
+        return getLaunchAppDetailsSettingsIntent(pkgName, false);
+    }
+
+    /**
+     * Return the intent of launch app details settings.
+     *
+     * @param pkgName The name of the package.
+     * @return the intent of launch app details settings
+     */
+    public static Intent getLaunchAppDetailsSettingsIntent(final String pkgName, final boolean isNewTask) {
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(Uri.parse("package:" + pkgName));
+        return getIntent(intent, isNewTask);
+    }
+
+    /**
+     * Return the intent of share text.
+     *
+     * @param content The content.
+     * @return the intent of share text
+     */
+    public static Intent getShareTextIntent(final String content) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType("text/plain");
+        intent.putExtra(Intent.EXTRA_TEXT, content);
+        intent = Intent.createChooser(intent, "");
+        return getIntent(intent, true);
+    }
+
+    /**
+     * Return the intent of share image.
+     *
+     * @param imagePath The path of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareImageIntent(final String imagePath) {
+        return getShareTextImageIntent("", imagePath);
+    }
+
+    /**
+     * Return the intent of share image.
+     *
+     * @param imageFile The file of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareImageIntent(final File imageFile) {
+        return getShareTextImageIntent("", imageFile);
+    }
+
+    /**
+     * Return the intent of share image.
+     *
+     * @param imageUri The uri of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareImageIntent(final Uri imageUri) {
+        return getShareTextImageIntent("", imageUri);
+    }
+
+    /**
+     * Return the intent of share image.
+     *
+     * @param content   The content.
+     * @param imagePath The path of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareTextImageIntent(@Nullable final String content, final String imagePath) {
+        return getShareTextImageIntent(content, UtilsBridge.getFileByPath(imagePath));
+    }
+
+    /**
+     * Return the intent of share image.
+     *
+     * @param content   The content.
+     * @param imageFile The file of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareTextImageIntent(@Nullable final String content, final File imageFile) {
+        return getShareTextImageIntent(content, UtilsBridge.file2Uri(imageFile));
+    }
+
+    /**
+     * Return the intent of share image.
+     *
+     * @param content  The content.
+     * @param imageUri The uri of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareTextImageIntent(@Nullable final String content, final Uri imageUri) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.putExtra(Intent.EXTRA_TEXT, content);
+        intent.putExtra(Intent.EXTRA_STREAM, imageUri);
+        intent.setType("image/*");
+        intent = Intent.createChooser(intent, "");
+        return getIntent(intent, true);
+    }
+
+    /**
+     * Return the intent of share images.
+     *
+     * @param imagePaths The paths of images.
+     * @return the intent of share images
+     */
+    public static Intent getShareImageIntent(final LinkedList<String> imagePaths) {
+        return getShareTextImageIntent("", imagePaths);
+    }
+
+    /**
+     * Return the intent of share images.
+     *
+     * @param images The files of images.
+     * @return the intent of share images
+     */
+    public static Intent getShareImageIntent(final List<File> images) {
+        return getShareTextImageIntent("", images);
+    }
+
+    /**
+     * Return the intent of share images.
+     *
+     * @param uris The uris of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareImageIntent(final ArrayList<Uri> uris) {
+        return getShareTextImageIntent("", uris);
+    }
+
+    /**
+     * Return the intent of share images.
+     *
+     * @param content    The content.
+     * @param imagePaths The paths of images.
+     * @return the intent of share images
+     */
+    public static Intent getShareTextImageIntent(@Nullable final String content,
+                                                 final LinkedList<String> imagePaths) {
+        List<File> files = new ArrayList<>();
+        if (imagePaths != null) {
+            for (String imagePath : imagePaths) {
+                File file = UtilsBridge.getFileByPath(imagePath);
+                if (file != null) {
+                    files.add(file);
+                }
+            }
+        }
+        return getShareTextImageIntent(content, files);
+    }
+
+    /**
+     * Return the intent of share images.
+     *
+     * @param content The content.
+     * @param images  The files of images.
+     * @return the intent of share images
+     */
+    public static Intent getShareTextImageIntent(@Nullable final String content, final List<File> images) {
+        ArrayList<Uri> uris = new ArrayList<>();
+        if (images != null) {
+            for (File image : images) {
+                Uri uri = UtilsBridge.file2Uri(image);
+                if (uri != null) {
+                    uris.add(uri);
+                }
+            }
+        }
+        return getShareTextImageIntent(content, uris);
+    }
+
+    /**
+     * Return the intent of share images.
+     *
+     * @param content The content.
+     * @param uris    The uris of image.
+     * @return the intent of share image
+     */
+    public static Intent getShareTextImageIntent(@Nullable final String content, final ArrayList<Uri> uris) {
+        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+        intent.putExtra(Intent.EXTRA_TEXT, content);
+        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+        intent.setType("image/*");
+        intent = Intent.createChooser(intent, "");
+        return getIntent(intent, true);
+    }
+
+    /**
+     * Return the intent of component.
+     *
+     * @param pkgName   The name of the package.
+     * @param className The name of class.
+     * @return the intent of component
+     */
+    public static Intent getComponentIntent(final String pkgName, final String className) {
+        return getComponentIntent(pkgName, className, null, false);
+    }
+
+    /**
+     * Return the intent of component.
+     *
+     * @param pkgName   The name of the package.
+     * @param className The name of class.
+     * @param isNewTask True to add flag of new task, false otherwise.
+     * @return the intent of component
+     */
+    public static Intent getComponentIntent(final String pkgName,
+                                            final String className,
+                                            final boolean isNewTask) {
+        return getComponentIntent(pkgName, className, null, isNewTask);
+    }
+
+    /**
+     * Return the intent of component.
+     *
+     * @param pkgName   The name of the package.
+     * @param className The name of class.
+     * @param bundle    The Bundle of extras to add to this intent.
+     * @return the intent of component
+     */
+    public static Intent getComponentIntent(final String pkgName,
+                                            final String className,
+                                            final Bundle bundle) {
+        return getComponentIntent(pkgName, className, bundle, false);
+    }
+
+    /**
+     * Return the intent of component.
+     *
+     * @param pkgName   The name of the package.
+     * @param className The name of class.
+     * @param bundle    The Bundle of extras to add to this intent.
+     * @param isNewTask True to add flag of new task, false otherwise.
+     * @return the intent of component
+     */
+    public static Intent getComponentIntent(final String pkgName,
+                                            final String className,
+                                            final Bundle bundle,
+                                            final boolean isNewTask) {
+        Intent intent = new Intent();
+        if (bundle != null) intent.putExtras(bundle);
+        ComponentName cn = new ComponentName(pkgName, className);
+        intent.setComponent(cn);
+        return getIntent(intent, isNewTask);
+    }
+
+    /**
+     * Return the intent of shutdown.
+     * <p>Requires root permission
+     * or hold {@code android:sharedUserId="android.uid.system"},
+     * {@code <uses-permission android:name="android.permission.SHUTDOWN" />}
+     * in manifest.</p>
+     *
+     * @return the intent of shutdown
+     */
+    public static Intent getShutdownIntent() {
+        Intent intent;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            intent = new Intent("com.android.internal.intent.action.REQUEST_SHUTDOWN");
+        } else {
+            intent = new Intent("android.intent.action.ACTION_REQUEST_SHUTDOWN");
+        }
+        intent.putExtra("android.intent.extra.KEY_CONFIRM", false);
+        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    /**
+     * Return the intent of dial.
+     *
+     * @param phoneNumber The phone number.
+     * @return the intent of dial
+     */
+    public static Intent getDialIntent(@NonNull final String phoneNumber) {
+        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + Uri.encode(phoneNumber)));
+        return getIntent(intent, true);
+    }
+
+    /**
+     * Return the intent of call.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.CALL_PHONE" />}</p>
+     *
+     * @param phoneNumber The phone number.
+     * @return the intent of call
+     */
+    @RequiresPermission(CALL_PHONE)
+    public static Intent getCallIntent(@NonNull final String phoneNumber) {
+        Intent intent = new Intent("android.intent.action.CALL", Uri.parse("tel:" + Uri.encode(phoneNumber)));
+        return getIntent(intent, true);
+    }
+
+    /**
+     * Return the intent of send SMS.
+     *
+     * @param phoneNumber The phone number.
+     * @param content     The content of SMS.
+     * @return the intent of send SMS
+     */
+    public static Intent getSendSmsIntent(@NonNull final String phoneNumber, final String content) {
+        Uri uri = Uri.parse("smsto:" + Uri.encode(phoneNumber));
+        Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
+        intent.putExtra("sms_body", content);
+        return getIntent(intent, true);
+    }
+
+    /**
+     * Return the intent of capture.
+     *
+     * @param outUri The uri of output.
+     * @return the intent of capture
+     */
+    public static Intent getCaptureIntent(final Uri outUri) {
+        return getCaptureIntent(outUri, false);
+    }
+
+    /**
+     * Return the intent of capture.
+     *
+     * @param outUri    The uri of output.
+     * @param isNewTask True to add flag of new task, false otherwise.
+     * @return the intent of capture
+     */
+    public static Intent getCaptureIntent(final Uri outUri, final boolean isNewTask) {
+        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        return getIntent(intent, isNewTask);
+    }
+
+    private static Intent getIntent(final Intent intent, final boolean isNewTask) {
+        return isNewTask ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) : intent;
+    }
+
+//    /**
+//     * 获取选择照片的 Intent
+//     *
+//     * @return
+//     */
+//    public static Intent getPickIntentWithGallery() {
+//        Intent intent = new Intent(Intent.ACTION_PICK);
+//        return intent.setType("image*//*");
+//    }
+//
+//    /**
+//     * 获取从文件中选择照片的 Intent
+//     *
+//     * @return
+//     */
+//    public static Intent getPickIntentWithDocuments() {
+//        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+//        return intent.setType("image*//*");
+//    }
+//
+//
+//    public static Intent buildImageGetIntent(final Uri saveTo, final int outputX, final int outputY, final boolean returnData) {
+//        return buildImageGetIntent(saveTo, 1, 1, outputX, outputY, returnData);
+//    }
+//
+//    public static Intent buildImageGetIntent(Uri saveTo, int aspectX, int aspectY,
+//                                             int outputX, int outputY, boolean returnData) {
+//        Intent intent = new Intent();
+//        if (Build.VERSION.SDK_INT < 19) {
+//            intent.setAction(Intent.ACTION_GET_CONTENT);
+//        } else {
+//            intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+//            intent.addCategory(Intent.CATEGORY_OPENABLE);
+//        }
+//        intent.setType("image*//*");
+//        intent.putExtra("output", saveTo);
+//        intent.putExtra("aspectX", aspectX);
+//        intent.putExtra("aspectY", aspectY);
+//        intent.putExtra("outputX", outputX);
+//        intent.putExtra("outputY", outputY);
+//        intent.putExtra("scale", true);
+//        intent.putExtra("return-data", returnData);
+//        intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
+//        return intent;
+//    }
+//
+//    public static Intent buildImageCropIntent(final Uri uriFrom, final Uri uriTo, final int outputX, final int outputY, final boolean returnData) {
+//        return buildImageCropIntent(uriFrom, uriTo, 1, 1, outputX, outputY, returnData);
+//    }
+//
+//    public static Intent buildImageCropIntent(Uri uriFrom, Uri uriTo, int aspectX, int aspectY,
+//                                              int outputX, int outputY, boolean returnData) {
+//        Intent intent = new Intent("com.android.camera.action.CROP");
+//        intent.setDataAndType(uriFrom, "image*//*");
+//        intent.putExtra("crop", "true");
+//        intent.putExtra("output", uriTo);
+//        intent.putExtra("aspectX", aspectX);
+//        intent.putExtra("aspectY", aspectY);
+//        intent.putExtra("outputX", outputX);
+//        intent.putExtra("outputY", outputY);
+//        intent.putExtra("scale", true);
+//        intent.putExtra("return-data", returnData);
+//        intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
+//        return intent;
+//    }
+//
+//    public static Intent buildImageCaptureIntent(final Uri uri) {
+//        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+//        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+//        return intent;
+//    }
+}

+ 255 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/JsonUtils.java

@@ -0,0 +1,255 @@
+package com.cooleshow.base.utils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/01/07
+ *     desc  : utils about json
+ * </pre>
+ */
+public final class JsonUtils {
+
+    private static final byte TYPE_BOOLEAN     = 0x00;
+    private static final byte TYPE_INT         = 0x01;
+    private static final byte TYPE_LONG        = 0x02;
+    private static final byte TYPE_DOUBLE      = 0x03;
+    private static final byte TYPE_STRING      = 0x04;
+    private static final byte TYPE_JSON_OBJECT = 0x05;
+    private static final byte TYPE_JSON_ARRAY  = 0x06;
+
+    private JsonUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    
+    /**
+     * Checks if a given input is a JSONObject.
+     *
+     * @param input Anything.
+     * @return true if it is a JSONObject.
+     */
+    public static <T> boolean isJSONObject(final T input) {
+        return input instanceof JSONObject;
+    }
+
+    /**
+     * Checks if a given input is a JSONArray
+     *
+     * @param input Anything.
+     * @return true if it is a JSONArray.
+     */
+    public static <T> boolean isJSONArray(final T input) {
+        return input instanceof JSONArray;
+    }
+    
+    public static boolean getBoolean(final JSONObject jsonObject,
+                                     final String key) {
+        return getBoolean(jsonObject, key, false);
+    }
+
+    public static boolean getBoolean(final JSONObject jsonObject,
+                                     final String key,
+                                     final boolean defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_BOOLEAN);
+    }
+
+    public static boolean getBoolean(final String json,
+                                     final String key) {
+        return getBoolean(json, key, false);
+    }
+
+    public static boolean getBoolean(final String json,
+                                     final String key,
+                                     final boolean defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_BOOLEAN);
+    }
+
+    public static int getInt(final JSONObject jsonObject,
+                             final String key) {
+        return getInt(jsonObject, key, -1);
+    }
+
+    public static int getInt(final JSONObject jsonObject,
+                             final String key,
+                             final int defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_INT);
+    }
+
+    public static int getInt(final String json,
+                             final String key) {
+        return getInt(json, key, -1);
+    }
+
+    public static int getInt(final String json,
+                             final String key,
+                             final int defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_INT);
+    }
+
+    public static long getLong(final JSONObject jsonObject,
+                               final String key) {
+        return getLong(jsonObject, key, -1);
+    }
+
+    public static long getLong(final JSONObject jsonObject,
+                               final String key,
+                               final long defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_LONG);
+    }
+
+    public static long getLong(final String json,
+                               final String key) {
+        return getLong(json, key, -1);
+    }
+
+    public static long getLong(final String json,
+                               final String key,
+                               final long defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_LONG);
+    }
+
+    public static double getDouble(final JSONObject jsonObject,
+                                   final String key) {
+        return getDouble(jsonObject, key, -1);
+    }
+
+    public static double getDouble(final JSONObject jsonObject,
+                                   final String key,
+                                   final double defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_DOUBLE);
+    }
+
+    public static double getDouble(final String json,
+                                   final String key) {
+        return getDouble(json, key, -1);
+    }
+
+    public static double getDouble(final String json,
+                                   final String key,
+                                   final double defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_DOUBLE);
+    }
+
+    public static String getString(final JSONObject jsonObject,
+                                   final String key) {
+        return getString(jsonObject, key, "");
+    }
+
+    public static String getString(final JSONObject jsonObject,
+                                   final String key,
+                                   final String defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_STRING);
+    }
+
+    public static String getString(final String json,
+                                   final String key) {
+        return getString(json, key, "");
+    }
+
+    public static String getString(final String json,
+                                   final String key,
+                                   final String defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_STRING);
+    }
+
+    public static JSONObject getJSONObject(final JSONObject jsonObject,
+                                           final String key,
+                                           final JSONObject defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_JSON_OBJECT);
+    }
+
+    public static JSONObject getJSONObject(final String json,
+                                           final String key,
+                                           final JSONObject defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_JSON_OBJECT);
+    }
+
+    public static JSONArray getJSONArray(final JSONObject jsonObject,
+                                         final String key,
+                                         final JSONArray defaultValue) {
+        return getValueByType(jsonObject, key, defaultValue, TYPE_JSON_ARRAY);
+    }
+
+    public static JSONArray getJSONArray(final String json,
+                                         final String key,
+                                         final JSONArray defaultValue) {
+        return getValueByType(json, key, defaultValue, TYPE_JSON_ARRAY);
+    }
+
+    private static <T> T getValueByType(final JSONObject jsonObject,
+                                        final String key,
+                                        final T defaultValue,
+                                        final byte type) {
+        if (jsonObject == null || key == null || key.length() == 0) {
+            return defaultValue;
+        }
+        try {
+            Object ret;
+            if (type == TYPE_BOOLEAN) {
+                ret = jsonObject.getBoolean(key);
+            } else if (type == TYPE_INT) {
+                ret = jsonObject.getInt(key);
+            } else if (type == TYPE_LONG) {
+                ret = jsonObject.getLong(key);
+            } else if (type == TYPE_DOUBLE) {
+                ret = jsonObject.getDouble(key);
+            } else if (type == TYPE_STRING) {
+                ret = jsonObject.getString(key);
+            } else if (type == TYPE_JSON_OBJECT) {
+                ret = jsonObject.getJSONObject(key);
+            } else if (type == TYPE_JSON_ARRAY) {
+                ret = jsonObject.getJSONArray(key);
+            } else {
+                return defaultValue;
+            }
+            //noinspection unchecked
+            return (T) ret;
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return defaultValue;
+        }
+    }
+
+    private static <T> T getValueByType(final String json,
+                                        final String key,
+                                        final T defaultValue,
+                                        final byte type) {
+        if (json == null || json.length() == 0
+                || key == null || key.length() == 0) {
+            return defaultValue;
+        }
+        try {
+            return getValueByType(new JSONObject(json), key, defaultValue, type);
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return defaultValue;
+        }
+    }
+
+    public static String formatJson(final String json) {
+        return formatJson(json, 4);
+    }
+
+    public static String formatJson(final String json, final int indentSpaces) {
+        try {
+            for (int i = 0, len = json.length(); i < len; i++) {
+                char c = json.charAt(i);
+                if (c == '{') {
+                    return new JSONObject(json).toString(indentSpaces);
+                } else if (c == '[') {
+                    return new JSONArray(json).toString(indentSpaces);
+                } else if (!Character.isWhitespace(c)) {
+                    return json;
+                }
+            }
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return json;
+    }
+}

+ 405 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/KeyboardUtils.java

@@ -0,0 +1,405 @@
+package com.cooleshow.base.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import java.lang.reflect.Field;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about keyboard
+ * </pre>
+ */
+public final class KeyboardUtils {
+
+    private static final int TAG_ON_GLOBAL_LAYOUT_LISTENER = -8;
+
+    private KeyboardUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Show the soft input.
+     */
+    public static void showSoftInput() {
+        InputMethodManager imm =
+            (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null) {
+            return;
+        }
+        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
+    }
+
+    /**
+     * Show the soft input.
+     */
+    public static void showSoftInput(@Nullable Activity activity) {
+        if (activity == null) {
+            return;
+        }
+        if (!isSoftInputVisible(activity)) {
+            toggleSoftInput();
+        }
+    }
+
+    /**
+     * Show the soft input.
+     *
+     * @param view The view.
+     */
+    public static void showSoftInput(@NonNull final View view) {
+        showSoftInput(view, 0);
+    }
+
+    /**
+     * Show the soft input.
+     *
+     * @param view The view.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} bit set.
+     */
+    public static void showSoftInput(@NonNull final View view, final int flags) {
+        InputMethodManager imm =
+            (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null) {
+            return;
+        }
+        view.setFocusable(true);
+        view.setFocusableInTouchMode(true);
+        view.requestFocus();
+        imm.showSoftInput(view, flags, new ResultReceiver(new Handler()) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == InputMethodManager.RESULT_UNCHANGED_HIDDEN
+                    || resultCode == InputMethodManager.RESULT_HIDDEN) {
+                    toggleSoftInput();
+                }
+            }
+        });
+        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
+    }
+
+    /**
+     * Hide the soft input.
+     *
+     * @param activity The activity.
+     */
+    public static void hideSoftInput(@Nullable final Activity activity) {
+        if (activity == null) {
+            return;
+        }
+        hideSoftInput(activity.getWindow());
+    }
+
+    /**
+     * Hide the soft input.
+     *
+     * @param window The window.
+     */
+    public static void hideSoftInput(@Nullable final Window window) {
+        if (window == null) {
+            return;
+        }
+        View view = window.getCurrentFocus();
+        if (view == null) {
+            View decorView = window.getDecorView();
+            View focusView = decorView.findViewWithTag("keyboardTagView");
+            if (focusView == null) {
+                view = new EditText(window.getContext());
+                view.setTag("keyboardTagView");
+                ((ViewGroup) decorView).addView(view, 0, 0);
+            } else {
+                view = focusView;
+            }
+            view.requestFocus();
+        }
+        hideSoftInput(view);
+    }
+
+    /**
+     * Hide the soft input.
+     *
+     * @param view The view.
+     */
+    public static void hideSoftInput(@NonNull final View view) {
+        InputMethodManager imm =
+            (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null) {
+            return;
+        }
+        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+    }
+
+    private static long millis;
+
+    /**
+     * Hide the soft input.
+     *
+     * @param activity The activity.
+     */
+    public static void hideSoftInputByToggle(@Nullable final Activity activity) {
+        if (activity == null) {
+            return;
+        }
+        long nowMillis = SystemClock.elapsedRealtime();
+        long delta = nowMillis - millis;
+        if (Math.abs(delta) > 500 && KeyboardUtils.isSoftInputVisible(activity)) {
+            KeyboardUtils.toggleSoftInput();
+        }
+        millis = nowMillis;
+    }
+
+    /**
+     * Toggle the soft input display or not.
+     */
+    public static void toggleSoftInput() {
+        InputMethodManager imm =
+            (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null) {
+            return;
+        }
+        imm.toggleSoftInput(0, 0);
+    }
+
+    private static int sDecorViewDelta = 0;
+
+    /**
+     * Return whether soft input is visible.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSoftInputVisible(@NonNull final Activity activity) {
+        return getDecorViewInvisibleHeight(activity.getWindow()) > 0;
+    }
+
+    private static int getDecorViewInvisibleHeight(@NonNull final Window window) {
+        final View decorView = window.getDecorView();
+        final Rect outRect = new Rect();
+        decorView.getWindowVisibleDisplayFrame(outRect);
+        Log.d("KeyboardUtils",
+            "getDecorViewInvisibleHeight: " + (decorView.getBottom() - outRect.bottom));
+        int delta = Math.abs(decorView.getBottom() - outRect.bottom);
+        if (delta <= UtilsBridge.getNavBarHeight() + UtilsBridge.getStatusBarHeight()) {
+            sDecorViewDelta = delta;
+            return 0;
+        }
+        return delta - sDecorViewDelta;
+    }
+
+    /**
+     * Register soft input changed listener.
+     *
+     * @param activity The activity.
+     * @param listener The soft input changed listener.
+     */
+    public static void registerSoftInputChangedListener(@NonNull final Activity activity,
+                                                        @NonNull
+                                                        final OnSoftInputChangedListener listener) {
+        registerSoftInputChangedListener(activity.getWindow(), listener);
+    }
+
+    /**
+     * Register soft input changed listener.
+     *
+     * @param window The window.
+     * @param listener The soft input changed listener.
+     */
+    public static void registerSoftInputChangedListener(@NonNull final Window window,
+                                                        @NonNull
+                                                        final OnSoftInputChangedListener listener) {
+        final int flags = window.getAttributes().flags;
+        if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+        }
+        final FrameLayout contentView = window.findViewById(android.R.id.content);
+        final int[] decorViewInvisibleHeightPre = { getDecorViewInvisibleHeight(window) };
+        OnGlobalLayoutListener onGlobalLayoutListener = new OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                int height = getDecorViewInvisibleHeight(window);
+                if (decorViewInvisibleHeightPre[0] != height) {
+                    listener.onSoftInputChanged(height);
+                    decorViewInvisibleHeightPre[0] = height;
+                }
+            }
+        };
+        contentView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
+        contentView.setTag(TAG_ON_GLOBAL_LAYOUT_LISTENER, onGlobalLayoutListener);
+    }
+
+    /**
+     * Unregister soft input changed listener.
+     *
+     * @param window The window.
+     */
+    public static void unregisterSoftInputChangedListener(@NonNull final Window window) {
+        final View contentView = window.findViewById(android.R.id.content);
+        if (contentView == null) {
+            return;
+        }
+        Object tag = contentView.getTag(TAG_ON_GLOBAL_LAYOUT_LISTENER);
+        if (tag instanceof OnGlobalLayoutListener) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                contentView.getViewTreeObserver().removeOnGlobalLayoutListener((OnGlobalLayoutListener) tag);
+                //这里会发生内存泄漏 如果不设置为null
+                contentView.setTag(TAG_ON_GLOBAL_LAYOUT_LISTENER, null);
+            }
+        }
+    }
+
+    /**
+     * Fix the bug of 5497 in Android.
+     * <p>Don't set adjustResize</p>
+     *
+     * @param activity The activity.
+     */
+    public static void fixAndroidBug5497(@NonNull final Activity activity) {
+        fixAndroidBug5497(activity.getWindow());
+    }
+
+    /**
+     * Fix the bug of 5497 in Android.
+     * <p>It will clean the adjustResize</p>
+     *
+     * @param window The window.
+     */
+    public static void fixAndroidBug5497(@NonNull final Window window) {
+        int softInputMode = window.getAttributes().softInputMode;
+        window.setSoftInputMode(
+            softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+        final FrameLayout contentView = window.findViewById(android.R.id.content);
+        final View contentViewChild = contentView.getChildAt(0);
+        final int paddingBottom = contentViewChild.getPaddingBottom();
+        final int[] contentViewInvisibleHeightPre5497 = { getContentViewInvisibleHeight(window) };
+        contentView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                int height = getContentViewInvisibleHeight(window);
+                if (contentViewInvisibleHeightPre5497[0] != height) {
+                    contentViewChild.setPadding(contentViewChild.getPaddingLeft(),
+                        contentViewChild.getPaddingTop(), contentViewChild.getPaddingRight(),
+                        paddingBottom + getDecorViewInvisibleHeight(window));
+                    contentViewInvisibleHeightPre5497[0] = height;
+                }
+            }
+        });
+    }
+
+    private static int getContentViewInvisibleHeight(final Window window) {
+        final View contentView = window.findViewById(android.R.id.content);
+        if (contentView == null) {
+            return 0;
+        }
+        final Rect outRect = new Rect();
+        contentView.getWindowVisibleDisplayFrame(outRect);
+        Log.d("KeyboardUtils",
+            "getContentViewInvisibleHeight: " + (contentView.getBottom() - outRect.bottom));
+        int delta = Math.abs(contentView.getBottom() - outRect.bottom);
+        if (delta <= UtilsBridge.getStatusBarHeight() + UtilsBridge.getNavBarHeight()) {
+            return 0;
+        }
+        return delta;
+    }
+
+    /**
+     * Fix the leaks of soft input.
+     *
+     * @param activity The activity.
+     */
+    public static void fixSoftInputLeaks(@NonNull final Activity activity) {
+        fixSoftInputLeaks(activity.getWindow());
+    }
+
+    /**
+     * Fix the leaks of soft input.
+     *
+     * @param window The window.
+     */
+    public static void fixSoftInputLeaks(@NonNull final Window window) {
+        InputMethodManager imm =
+            (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null) {
+            return;
+        }
+        String[] leakViews =
+            new String[] { "mLastSrvView", "mCurRootView", "mServedView", "mNextServedView" };
+        for (String leakView : leakViews) {
+            try {
+                Field leakViewField = InputMethodManager.class.getDeclaredField(leakView);
+                if (!leakViewField.isAccessible()) {
+                    leakViewField.setAccessible(true);
+                }
+                Object obj = leakViewField.get(imm);
+                if (!(obj instanceof View)) {
+                    continue;
+                }
+                View view = (View) obj;
+                if (view.getRootView() == window.getDecorView().getRootView()) {
+                    leakViewField.set(imm, null);
+                }
+            } catch (Throwable ignore) {/**/}
+        }
+    }
+
+    /**
+     * Click blank area to hide soft input.
+     * <p>Copy the following code in ur activity.</p>
+     */
+    public static void clickBlankArea2HideSoftInput() {
+        Log.i("KeyboardUtils", "Please refer to the following code.");
+        /*
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent ev) {
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                View v = getCurrentFocus();
+                if (isShouldHideKeyboard(v, ev)) {
+                    KeyboardUtils.hideSoftInput(this);
+                }
+            }
+            return super.dispatchTouchEvent(ev);
+        }
+
+        // Return whether touch the view.
+        private boolean isShouldHideKeyboard(View v, MotionEvent event) {
+            if ((v instanceof EditText)) {
+                int[] l = {0, 0};
+                v.getLocationOnScreen(l);
+                int left = l[0],
+                        top = l[1],
+                        bottom = top + v.getHeight(),
+                        right = left + v.getWidth();
+                return !(event.getRawX() > left && event.getRawX() < right
+                        && event.getRawY() > top && event.getRawY() < bottom);
+            }
+            return false;
+        }
+        */
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // interface
+    ///////////////////////////////////////////////////////////////////////////
+    public interface OnSoftInputChangedListener {
+        void onSoftInputChanged(int height);
+    }
+}

+ 318 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/LanguageUtils.java

@@ -0,0 +1,318 @@
+package com.cooleshow.base.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Locale;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/06/20
+ *     desc  : utils about language
+ * </pre>
+ */
+public class LanguageUtils {
+
+    private static final String KEY_LOCALE          = "KEY_LOCALE";
+    private static final String VALUE_FOLLOW_SYSTEM = "VALUE_FOLLOW_SYSTEM";
+
+    private LanguageUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Apply the system language.
+     */
+    public static void applySystemLanguage() {
+        applySystemLanguage(false);
+    }
+
+    /**
+     * Apply the system language.
+     *
+     * @param isRelaunchApp True to relaunch app, false to recreate all activities.
+     */
+    public static void applySystemLanguage(final boolean isRelaunchApp) {
+        applyLanguageReal(null, isRelaunchApp);
+    }
+
+    /**
+     * Apply the language.
+     *
+     * @param locale The language of locale.
+     */
+    public static void applyLanguage(@NonNull final Locale locale) {
+        applyLanguage(locale, false);
+    }
+
+    /**
+     * Apply the language.
+     *
+     * @param locale        The language of locale.
+     * @param isRelaunchApp True to relaunch app, false to recreate all activities.
+     */
+    public static void applyLanguage(@NonNull final Locale locale,
+                                     final boolean isRelaunchApp) {
+        applyLanguageReal(locale, isRelaunchApp);
+    }
+
+    private static void applyLanguageReal(final Locale locale,
+                                          final boolean isRelaunchApp) {
+        if (locale == null) {
+            UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, VALUE_FOLLOW_SYSTEM, true);
+        } else {
+            UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, locale2String(locale), true);
+        }
+
+        Locale destLocal = locale == null ? getLocal(Resources.getSystem().getConfiguration()) : locale;
+        updateAppContextLanguage(destLocal, new Utils.Consumer<Boolean>() {
+            @Override
+            public void accept(Boolean success) {
+                if (success) {
+                    restart(isRelaunchApp);
+                } else {
+                    // use relaunch app
+                    UtilsBridge.relaunchApp();
+                }
+            }
+        });
+    }
+
+    private static void restart(final boolean isRelaunchApp) {
+        if (isRelaunchApp) {
+            UtilsBridge.relaunchApp();
+        } else {
+            for (Activity activity : UtilsBridge.getActivityList()) {
+                activity.recreate();
+            }
+        }
+    }
+
+    /**
+     * Return whether applied the language by {@link LanguageUtils}.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppliedLanguage() {
+        return getAppliedLanguage() != null;
+    }
+
+    /**
+     * Return whether applied the language by {@link LanguageUtils}.
+     *
+     * @param locale The locale.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAppliedLanguage(@NonNull Locale locale) {
+        Locale appliedLocale = getAppliedLanguage();
+        if (appliedLocale == null) {
+            return false;
+        }
+        return isSameLocale(locale, appliedLocale);
+    }
+
+    /**
+     * Return the applied locale.
+     *
+     * @return the applied locale
+     */
+    public static Locale getAppliedLanguage() {
+        final String spLocaleStr = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);
+        if (TextUtils.isEmpty(spLocaleStr) || VALUE_FOLLOW_SYSTEM.equals(spLocaleStr)) {
+            return null;
+        }
+        return string2Locale(spLocaleStr);
+    }
+
+    /**
+     * Return the locale of context.
+     *
+     * @return the locale of context
+     */
+    public static Locale getContextLanguage(Context context) {
+        return getLocal(context.getResources().getConfiguration());
+    }
+
+    /**
+     * Return the locale of applicationContext.
+     *
+     * @return the locale of applicationContext
+     */
+    public static Locale getAppContextLanguage() {
+        return getContextLanguage(Utils.getApp());
+    }
+
+    /**
+     * Return the locale of system
+     *
+     * @return the locale of system
+     */
+    public static Locale getSystemLanguage() {
+        return getLocal(Resources.getSystem().getConfiguration());
+    }
+
+    /**
+     * Update the locale of applicationContext.
+     *
+     * @param destLocale The dest locale.
+     * @param consumer   The consumer.
+     */
+    public static void updateAppContextLanguage(@NonNull Locale destLocale, @Nullable Utils.Consumer<Boolean> consumer) {
+        pollCheckAppContextLocal(destLocale, 0, consumer);
+    }
+
+    static void pollCheckAppContextLocal(final Locale destLocale, final int index, final Utils.Consumer<Boolean> consumer) {
+        Resources appResources = Utils.getApp().getResources();
+        Configuration appConfig = appResources.getConfiguration();
+        Locale appLocal = getLocal(appConfig);
+
+        setLocal(appConfig, destLocale);
+
+        Utils.getApp().getResources().updateConfiguration(appConfig, appResources.getDisplayMetrics());
+
+        if (consumer == null) return;
+
+        if (isSameLocale(appLocal, destLocale)) {
+            consumer.accept(true);
+        } else {
+            if (index < 20) {
+                UtilsBridge.runOnUiThreadDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        pollCheckAppContextLocal(destLocale, index + 1, consumer);
+                    }
+                }, 16);
+                return;
+            }
+            Log.e("LanguageUtils", "appLocal didn't update.");
+            consumer.accept(false);
+        }
+    }
+
+    /**
+     * If applyLanguage not work, try to call it in {@link Activity#attachBaseContext(Context)}.
+     *
+     * @param context The baseContext.
+     * @return the context with language
+     */
+    public static Context attachBaseContext(Context context) {
+        String spLocaleStr = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);
+        if (TextUtils.isEmpty(spLocaleStr) || VALUE_FOLLOW_SYSTEM.equals(spLocaleStr)) {
+            return context;
+        }
+
+        Locale settingsLocale = string2Locale(spLocaleStr);
+        if (settingsLocale == null) return context;
+
+        Resources resources = context.getResources();
+        Configuration config = resources.getConfiguration();
+
+        setLocal(config, settingsLocale);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return context.createConfigurationContext(config);
+        } else {
+            resources.updateConfiguration(config, resources.getDisplayMetrics());
+            return context;
+        }
+    }
+
+    static void applyLanguage(final Activity activity) {
+        String spLocale = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);
+        if (TextUtils.isEmpty(spLocale)) {
+            return;
+        }
+
+        Locale destLocal;
+        if (VALUE_FOLLOW_SYSTEM.equals(spLocale)) {
+            destLocal = getLocal(Resources.getSystem().getConfiguration());
+        } else {
+            destLocal = string2Locale(spLocale);
+        }
+
+        if (destLocal == null) return;
+
+        updateConfiguration(activity, destLocal);
+        updateConfiguration(Utils.getApp(), destLocal);
+    }
+
+    private static void updateConfiguration(Context context, Locale destLocal) {
+        Resources resources = context.getResources();
+        Configuration config = resources.getConfiguration();
+        setLocal(config, destLocal);
+        resources.updateConfiguration(config, resources.getDisplayMetrics());
+    }
+
+    private static String locale2String(Locale locale) {
+        String localLanguage = locale.getLanguage(); // this may be empty
+        String localCountry = locale.getCountry(); // this may be empty
+        return localLanguage + "$" + localCountry;
+    }
+
+    private static Locale string2Locale(String str) {
+        Locale locale = string2LocaleReal(str);
+        if (locale == null) {
+            Log.e("LanguageUtils", "The string of " + str + " is not in the correct format.");
+            UtilsBridge.getSpUtils4Utils().remove(KEY_LOCALE);
+        }
+        return locale;
+    }
+
+    private static Locale string2LocaleReal(String str) {
+        if (!isRightFormatLocalStr(str)) {
+            return null;
+        }
+
+        try {
+            int splitIndex = str.indexOf("$");
+            return new Locale(str.substring(0, splitIndex), str.substring(splitIndex + 1));
+        } catch (Exception ignore) {
+            return null;
+        }
+    }
+
+    private static boolean isRightFormatLocalStr(String localStr) {
+        char[] chars = localStr.toCharArray();
+        int count = 0;
+        for (char c : chars) {
+            if (c == '$') {
+                if (count >= 1) {
+                    return false;
+                }
+                ++count;
+            }
+        }
+        return count == 1;
+    }
+
+    private static boolean isSameLocale(Locale l0, Locale l1) {
+        return UtilsBridge.equals(l1.getLanguage(), l0.getLanguage())
+                && UtilsBridge.equals(l1.getCountry(), l0.getCountry());
+    }
+
+    private static Locale getLocal(Configuration configuration) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            return configuration.getLocales().get(0);
+        } else {
+            return configuration.locale;
+        }
+    }
+
+    private static void setLocal(Configuration configuration, Locale locale) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            configuration.setLocale(locale);
+        } else {
+            configuration.locale = locale;
+        }
+    }
+}

+ 1231 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/LogUtils.java

@@ -0,0 +1,1231 @@
+package com.cooleshow.base.utils;
+
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
+import androidx.annotation.RequiresApi;
+import androidx.collection.SimpleArrayMap;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/21
+ *     desc  : utils about log
+ * </pre>
+ */
+public final class LogUtils {
+
+    public static final int V = Log.VERBOSE;
+    public static final int D = Log.DEBUG;
+    public static final int I = Log.INFO;
+    public static final int W = Log.WARN;
+    public static final int E = Log.ERROR;
+    public static final int A = Log.ASSERT;
+
+    @IntDef({V, D, I, W, E, A})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TYPE {
+    }
+
+    private static final char[] T = new char[]{'V', 'D', 'I', 'W', 'E', 'A'};
+
+    private static final int FILE = 0x10;
+    private static final int JSON = 0x20;
+    private static final int XML  = 0x30;
+
+    private static final String FILE_SEP       = System.getProperty("file.separator");
+    private static final String LINE_SEP       = System.getProperty("line.separator");
+    private static final String TOP_CORNER     = "┌";
+    private static final String MIDDLE_CORNER  = "├";
+    private static final String LEFT_BORDER    = "│ ";
+    private static final String BOTTOM_CORNER  = "└";
+    private static final String SIDE_DIVIDER   =
+            "────────────────────────────────────────────────────────";
+    private static final String MIDDLE_DIVIDER =
+            "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
+    private static final String TOP_BORDER     = TOP_CORNER + SIDE_DIVIDER + SIDE_DIVIDER;
+    private static final String MIDDLE_BORDER  = MIDDLE_CORNER + MIDDLE_DIVIDER + MIDDLE_DIVIDER;
+    private static final String BOTTOM_BORDER  = BOTTOM_CORNER + SIDE_DIVIDER + SIDE_DIVIDER;
+    private static final int    MAX_LEN        = 1100;// fit for Chinese character
+    private static final String NOTHING        = "log nothing";
+    private static final String NULL           = "null";
+    private static final String ARGS           = "args";
+    private static final String PLACEHOLDER    = " ";
+    private static final Config CONFIG         = new Config();
+
+    private static SimpleDateFormat simpleDateFormat;
+
+    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
+
+    private static final SimpleArrayMap<Class, IFormatter> I_FORMATTER_MAP = new SimpleArrayMap<>();
+
+    private LogUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    public static Config getConfig() {
+        return CONFIG;
+    }
+
+    public static void v(final Object... contents) {
+        log(V, CONFIG.getGlobalTag(), contents);
+    }
+
+    public static void vTag(final String tag, final Object... contents) {
+        log(V, tag, contents);
+    }
+
+    public static void d(final Object... contents) {
+        log(D, CONFIG.getGlobalTag(), contents);
+    }
+
+    public static void dTag(final String tag, final Object... contents) {
+        log(D, tag, contents);
+    }
+
+    public static void i(final Object... contents) {
+        log(I, CONFIG.getGlobalTag(), contents);
+    }
+
+    public static void iTag(final String tag, final Object... contents) {
+        log(I, tag, contents);
+    }
+
+    public static void w(final Object... contents) {
+        log(W, CONFIG.getGlobalTag(), contents);
+    }
+
+    public static void wTag(final String tag, final Object... contents) {
+        log(W, tag, contents);
+    }
+
+    public static void e(final Object... contents) {
+        log(E, CONFIG.getGlobalTag(), contents);
+    }
+
+    public static void eTag(final String tag, final Object... contents) {
+        log(E, tag, contents);
+    }
+
+    public static void a(final Object... contents) {
+        log(A, CONFIG.getGlobalTag(), contents);
+    }
+
+    public static void aTag(final String tag, final Object... contents) {
+        log(A, tag, contents);
+    }
+
+    public static void file(final Object content) {
+        log(FILE | D, CONFIG.getGlobalTag(), content);
+    }
+
+    public static void file(@TYPE final int type, final Object content) {
+        log(FILE | type, CONFIG.getGlobalTag(), content);
+    }
+
+    public static void file(final String tag, final Object content) {
+        log(FILE | D, tag, content);
+    }
+
+    public static void file(@TYPE final int type, final String tag, final Object content) {
+        log(FILE | type, tag, content);
+    }
+
+    public static void json(final Object content) {
+        log(JSON | D, CONFIG.getGlobalTag(), content);
+    }
+
+    public static void json(@TYPE final int type, final Object content) {
+        log(JSON | type, CONFIG.getGlobalTag(), content);
+    }
+
+    public static void json(final String tag, final Object content) {
+        log(JSON | D, tag, content);
+    }
+
+    public static void json(@TYPE final int type, final String tag, final Object content) {
+        log(JSON | type, tag, content);
+    }
+
+    public static void xml(final String content) {
+        log(XML | D, CONFIG.getGlobalTag(), content);
+    }
+
+    public static void xml(@TYPE final int type, final String content) {
+        log(XML | type, CONFIG.getGlobalTag(), content);
+    }
+
+    public static void xml(final String tag, final String content) {
+        log(XML | D, tag, content);
+    }
+
+    public static void xml(@TYPE final int type, final String tag, final String content) {
+        log(XML | type, tag, content);
+    }
+
+    public static void log(final int type, final String tag, final Object... contents) {
+        if (!CONFIG.isLogSwitch()) return;
+        final int type_low = type & 0x0f, type_high = type & 0xf0;
+        if (CONFIG.isLog2ConsoleSwitch() || CONFIG.isLog2FileSwitch() || type_high == FILE) {
+            if (type_low < CONFIG.mConsoleFilter && type_low < CONFIG.mFileFilter) return;
+            final TagHead tagHead = processTagAndHead(tag);
+            final String body = processBody(type_high, contents);
+            if (CONFIG.isLog2ConsoleSwitch() && type_high != FILE && type_low >= CONFIG.mConsoleFilter) {
+                print2Console(type_low, tagHead.tag, tagHead.consoleHead, body);
+            }
+            if ((CONFIG.isLog2FileSwitch() || type_high == FILE) && type_low >= CONFIG.mFileFilter) {
+                EXECUTOR.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        print2File(type_low, tagHead.tag, tagHead.fileHead + body);
+                    }
+                });
+            }
+        }
+    }
+
+    public static String getCurrentLogFilePath() {
+        return getCurrentLogFilePath(new Date());
+    }
+
+    public static List<File> getLogFiles() {
+        String dir = CONFIG.getDir();
+        File logDir = new File(dir);
+        if (!logDir.exists()) return new ArrayList<>();
+        File[] files = logDir.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return isMatchLogFileName(name);
+            }
+        });
+        List<File> list = new ArrayList<>();
+        Collections.addAll(list, files);
+        return list;
+    }
+
+    private static TagHead processTagAndHead(String tag) {
+        if (!CONFIG.mTagIsSpace && !CONFIG.isLogHeadSwitch()) {
+            tag = CONFIG.getGlobalTag();
+        } else {
+            final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+            final int stackIndex = 3 + CONFIG.getStackOffset();
+            if (stackIndex >= stackTrace.length) {
+                StackTraceElement targetElement = stackTrace[3];
+                final String fileName = getFileName(targetElement);
+                if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) {
+                    int index = fileName.indexOf('.');// Use proguard may not find '.'.
+                    tag = index == -1 ? fileName : fileName.substring(0, index);
+                }
+                return new TagHead(tag, null, ": ");
+            }
+            StackTraceElement targetElement = stackTrace[stackIndex];
+            final String fileName = getFileName(targetElement);
+            if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) {
+                int index = fileName.indexOf('.');// Use proguard may not find '.'.
+                tag = index == -1 ? fileName : fileName.substring(0, index);
+            }
+            if (CONFIG.isLogHeadSwitch()) {
+                String tName = Thread.currentThread().getName();
+                final String head = new Formatter()
+                        .format("%s, %s.%s(%s:%d)",
+                                tName,
+                                targetElement.getClassName(),
+                                targetElement.getMethodName(),
+                                fileName,
+                                targetElement.getLineNumber())
+                        .toString();
+                final String fileHead = " [" + head + "]: ";
+                if (CONFIG.getStackDeep() <= 1) {
+                    return new TagHead(tag, new String[]{head}, fileHead);
+                } else {
+                    final String[] consoleHead =
+                            new String[Math.min(
+                                    CONFIG.getStackDeep(),
+                                    stackTrace.length - stackIndex
+                            )];
+                    consoleHead[0] = head;
+                    int spaceLen = tName.length() + 2;
+                    String space = new Formatter().format("%" + spaceLen + "s", "").toString();
+                    for (int i = 1, len = consoleHead.length; i < len; ++i) {
+                        targetElement = stackTrace[i + stackIndex];
+                        consoleHead[i] = new Formatter()
+                                .format("%s%s.%s(%s:%d)",
+                                        space,
+                                        targetElement.getClassName(),
+                                        targetElement.getMethodName(),
+                                        getFileName(targetElement),
+                                        targetElement.getLineNumber())
+                                .toString();
+                    }
+                    return new TagHead(tag, consoleHead, fileHead);
+                }
+            }
+        }
+        return new TagHead(tag, null, ": ");
+    }
+
+    private static String getFileName(final StackTraceElement targetElement) {
+        String fileName = targetElement.getFileName();
+        if (fileName != null) return fileName;
+        // If name of file is null, should add
+        // "-keepattributes SourceFile,LineNumberTable" in proguard file.
+        String className = targetElement.getClassName();
+        String[] classNameInfo = className.split("\\.");
+        if (classNameInfo.length > 0) {
+            className = classNameInfo[classNameInfo.length - 1];
+        }
+        int index = className.indexOf('$');
+        if (index != -1) {
+            className = className.substring(0, index);
+        }
+        return className + ".java";
+    }
+
+    private static String processBody(final int type, final Object... contents) {
+        String body = NULL;
+        if (contents != null) {
+            if (contents.length == 1) {
+                body = formatObject(type, contents[0]);
+            } else {
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0, len = contents.length; i < len; ++i) {
+                    Object content = contents[i];
+                    sb.append(ARGS)
+                            .append("[")
+                            .append(i)
+                            .append("]")
+                            .append(" = ")
+                            .append(formatObject(content))
+                            .append(LINE_SEP);
+                }
+                body = sb.toString();
+            }
+        }
+        return body.length() == 0 ? NOTHING : body;
+    }
+
+    private static String formatObject(int type, Object object) {
+        if (object == null) return NULL;
+        if (type == JSON) return LogFormatter.object2String(object, JSON);
+        if (type == XML) return LogFormatter.object2String(object, XML);
+        return formatObject(object);
+    }
+
+    private static String formatObject(Object object) {
+        if (object == null) return NULL;
+        if (!I_FORMATTER_MAP.isEmpty()) {
+            IFormatter iFormatter = I_FORMATTER_MAP.get(getClassFromObject(object));
+            if (iFormatter != null) {
+                //noinspection unchecked
+                return iFormatter.format(object);
+            }
+        }
+        return LogFormatter.object2String(object);
+    }
+
+    private static void print2Console(final int type,
+                                      final String tag,
+                                      final String[] head,
+                                      final String msg) {
+        if (CONFIG.isSingleTagSwitch()) {
+            printSingleTagMsg(type, tag, processSingleTagMsg(type, tag, head, msg));
+        } else {
+            printBorder(type, tag, true);
+            printHead(type, tag, head);
+            printMsg(type, tag, msg);
+            printBorder(type, tag, false);
+        }
+    }
+
+    private static void printBorder(final int type, final String tag, boolean isTop) {
+        if (CONFIG.isLogBorderSwitch()) {
+            print2Console(type, tag, isTop ? TOP_BORDER : BOTTOM_BORDER);
+        }
+    }
+
+    private static void printHead(final int type, final String tag, final String[] head) {
+        if (head != null) {
+            for (String aHead : head) {
+                print2Console(type, tag, CONFIG.isLogBorderSwitch() ? LEFT_BORDER + aHead : aHead);
+            }
+            if (CONFIG.isLogBorderSwitch()) print2Console(type, tag, MIDDLE_BORDER);
+        }
+    }
+
+    private static void printMsg(final int type, final String tag, final String msg) {
+        int len = msg.length();
+        int countOfSub = len / MAX_LEN;
+        if (countOfSub > 0) {
+            int index = 0;
+            for (int i = 0; i < countOfSub; i++) {
+                printSubMsg(type, tag, msg.substring(index, index + MAX_LEN));
+                index += MAX_LEN;
+            }
+            if (index != len) {
+                printSubMsg(type, tag, msg.substring(index, len));
+            }
+        } else {
+            printSubMsg(type, tag, msg);
+        }
+    }
+
+    private static void printSubMsg(final int type, final String tag, final String msg) {
+        if (!CONFIG.isLogBorderSwitch()) {
+            print2Console(type, tag, msg);
+            return;
+        }
+        StringBuilder sb = new StringBuilder();
+        String[] lines = msg.split(LINE_SEP);
+        for (String line : lines) {
+            print2Console(type, tag, LEFT_BORDER + line);
+        }
+    }
+
+    private static String processSingleTagMsg(final int type,
+                                              final String tag,
+                                              final String[] head,
+                                              final String msg) {
+        StringBuilder sb = new StringBuilder();
+        if (CONFIG.isLogBorderSwitch()) {
+            sb.append(PLACEHOLDER).append(LINE_SEP);
+            sb.append(TOP_BORDER).append(LINE_SEP);
+            if (head != null) {
+                for (String aHead : head) {
+                    sb.append(LEFT_BORDER).append(aHead).append(LINE_SEP);
+                }
+                sb.append(MIDDLE_BORDER).append(LINE_SEP);
+            }
+            for (String line : msg.split(LINE_SEP)) {
+                sb.append(LEFT_BORDER).append(line).append(LINE_SEP);
+            }
+            sb.append(BOTTOM_BORDER);
+        } else {
+            if (head != null) {
+                sb.append(PLACEHOLDER).append(LINE_SEP);
+                for (String aHead : head) {
+                    sb.append(aHead).append(LINE_SEP);
+                }
+            }
+            sb.append(msg);
+        }
+        return sb.toString();
+    }
+
+    private static void printSingleTagMsg(final int type, final String tag, final String msg) {
+        int len = msg.length();
+        int countOfSub = CONFIG.isLogBorderSwitch() ? (len - BOTTOM_BORDER.length()) / MAX_LEN : len / MAX_LEN;
+        if (countOfSub > 0) {
+            if (CONFIG.isLogBorderSwitch()) {
+                print2Console(type, tag, msg.substring(0, MAX_LEN) + LINE_SEP + BOTTOM_BORDER);
+                int index = MAX_LEN;
+                for (int i = 1; i < countOfSub; i++) {
+                    print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP
+                            + LEFT_BORDER + msg.substring(index, index + MAX_LEN)
+                            + LINE_SEP + BOTTOM_BORDER);
+                    index += MAX_LEN;
+                }
+                if (index != len - BOTTOM_BORDER.length()) {
+                    print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP
+                            + LEFT_BORDER + msg.substring(index, len));
+                }
+            } else {
+                print2Console(type, tag, msg.substring(0, MAX_LEN));
+                int index = MAX_LEN;
+                for (int i = 1; i < countOfSub; i++) {
+                    print2Console(type, tag,
+                            PLACEHOLDER + LINE_SEP + msg.substring(index, index + MAX_LEN));
+                    index += MAX_LEN;
+                }
+                if (index != len) {
+                    print2Console(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, len));
+                }
+            }
+        } else {
+            print2Console(type, tag, msg);
+        }
+    }
+
+    private static void print2Console(int type, String tag, String msg) {
+        Log.println(type, tag, msg);
+        if (CONFIG.mOnConsoleOutputListener != null) {
+            CONFIG.mOnConsoleOutputListener.onConsoleOutput(type, tag, msg);
+        }
+    }
+
+    private static void print2File(final int type, final String tag, final String msg) {
+        Date d = new Date();
+        String format = getSdf().format(d);
+        String date = format.substring(0, 10);
+        String currentLogFilePath = getCurrentLogFilePath(d);
+        if (!createOrExistsFile(currentLogFilePath, date)) {
+            Log.e("LogUtils", "create " + currentLogFilePath + " failed!");
+            return;
+        }
+        String time = format.substring(11);
+        final String content = time +
+                T[type - V] +
+                "/" +
+                tag +
+                msg +
+                LINE_SEP;
+        input2File(currentLogFilePath, content);
+    }
+
+    private static String getCurrentLogFilePath(Date d) {
+        String format = getSdf().format(d);
+        String date = format.substring(0, 10);
+        return CONFIG.getDir() + CONFIG.getFilePrefix() + "_"
+                + date + "_" +
+                CONFIG.getProcessName() + CONFIG.getFileExtension();
+    }
+
+
+    private static SimpleDateFormat getSdf() {
+        if (simpleDateFormat == null) {
+            simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd HH:mm:ss.SSS ", Locale.getDefault());
+        }
+        return simpleDateFormat;
+    }
+
+    private static boolean createOrExistsFile(final String filePath, final String date) {
+        File file = new File(filePath);
+        if (file.exists()) return file.isFile();
+        if (!UtilsBridge.createOrExistsDir(file.getParentFile())) return false;
+        try {
+            deleteDueLogs(filePath, date);
+            boolean isCreate = file.createNewFile();
+            if (isCreate) {
+                printDeviceInfo(filePath, date);
+            }
+            return isCreate;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private static void deleteDueLogs(final String filePath, final String date) {
+        if (CONFIG.getSaveDays() <= 0) return;
+        File file = new File(filePath);
+        File parentFile = file.getParentFile();
+        File[] files = parentFile.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return isMatchLogFileName(name);
+            }
+        });
+        if (files == null || files.length <= 0) return;
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd", Locale.getDefault());
+        try {
+            long dueMillis = sdf.parse(date).getTime() - CONFIG.getSaveDays() * 86400000L;
+            for (final File aFile : files) {
+                String name = aFile.getName();
+                int l = name.length();
+                String logDay = findDate(name);
+                if (sdf.parse(logDay).getTime() <= dueMillis) {
+                    EXECUTOR.execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            boolean delete = aFile.delete();
+                            if (!delete) {
+                                Log.e("LogUtils", "delete " + aFile + " failed!");
+                            }
+                        }
+                    });
+                }
+            }
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static boolean isMatchLogFileName(String name) {
+        return name.matches("^" + CONFIG.getFilePrefix() + "_[0-9]{4}_[0-9]{2}_[0-9]{2}_.*$");
+    }
+
+    private static String findDate(String str) {
+        Pattern pattern = Pattern.compile("[0-9]{4}_[0-9]{2}_[0-9]{2}");
+        Matcher matcher = pattern.matcher(str);
+        if (matcher.find()) {
+            return matcher.group();
+        }
+        return "";
+    }
+
+    private static void printDeviceInfo(final String filePath, final String date) {
+        CONFIG.mFileHead.addFirst("Date of Log", date);
+        input2File(filePath, CONFIG.mFileHead.toString());
+    }
+
+    private static void input2File(final String filePath, final String input) {
+        if (CONFIG.mFileWriter == null) {
+            UtilsBridge.writeFileFromString(filePath, input, true);
+        } else {
+            CONFIG.mFileWriter.write(filePath, input);
+        }
+        if (CONFIG.mOnFileOutputListener != null) {
+            CONFIG.mOnFileOutputListener.onFileOutput(filePath, input);
+        }
+    }
+
+    public static final class Config {
+        private String                  mDefaultDir;                // The default storage directory of log.
+        private String                  mDir;                       // The storage directory of log.
+        private String                  mFilePrefix        = "util";// The file prefix of log.
+        private String                  mFileExtension     = ".txt";// The file extension of log.
+        private boolean                 mLogSwitch         = true;  // The switch of log.
+        private boolean                 mLog2ConsoleSwitch = true;  // The logcat's switch of log.
+        private String                  mGlobalTag         = "";    // The global tag of log.
+        private boolean                 mTagIsSpace        = true;  // The global tag is space.
+        private boolean                 mLogHeadSwitch     = true;  // The head's switch of log.
+        private boolean                 mLog2FileSwitch    = false; // The file's switch of log.
+        private boolean                 mLogBorderSwitch   = true;  // The border's switch of log.
+        private boolean                 mSingleTagSwitch   = true;  // The single tag of log.
+        private int                     mConsoleFilter     = V;     // The console's filter of log.
+        private int                     mFileFilter        = V;     // The file's filter of log.
+        private int                     mStackDeep         = 1;     // The stack's deep of log.
+        private int                     mStackOffset       = 0;     // The stack's offset of log.
+        private int                     mSaveDays          = -1;    // The save days of log.
+        private String                  mProcessName       = UtilsBridge.getCurrentProcessName();
+        private IFileWriter             mFileWriter;
+        private OnConsoleOutputListener mOnConsoleOutputListener;
+        private OnFileOutputListener    mOnFileOutputListener;
+        private UtilsBridge.FileHead    mFileHead          = new UtilsBridge.FileHead("Log");
+
+        private Config() {
+            if (UtilsBridge.isSDCardEnableByEnvironment()
+                    && Utils.getApp().getExternalFilesDir(null) != null)
+                mDefaultDir = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + "log" + FILE_SEP;
+            else {
+                mDefaultDir = Utils.getApp().getFilesDir() + FILE_SEP + "log" + FILE_SEP;
+            }
+        }
+
+        public final Config setLogSwitch(final boolean logSwitch) {
+            mLogSwitch = logSwitch;
+            return this;
+        }
+
+        public final Config setConsoleSwitch(final boolean consoleSwitch) {
+            mLog2ConsoleSwitch = consoleSwitch;
+            return this;
+        }
+
+        public final Config setGlobalTag(final String tag) {
+            if (UtilsBridge.isSpace(tag)) {
+                mGlobalTag = "";
+                mTagIsSpace = true;
+            } else {
+                mGlobalTag = tag;
+                mTagIsSpace = false;
+            }
+            return this;
+        }
+
+        public final Config setLogHeadSwitch(final boolean logHeadSwitch) {
+            mLogHeadSwitch = logHeadSwitch;
+            return this;
+        }
+
+        public final Config setLog2FileSwitch(final boolean log2FileSwitch) {
+            mLog2FileSwitch = log2FileSwitch;
+            return this;
+        }
+
+        public final Config setDir(final String dir) {
+            if (UtilsBridge.isSpace(dir)) {
+                mDir = null;
+            } else {
+                mDir = dir.endsWith(FILE_SEP) ? dir : dir + FILE_SEP;
+            }
+            return this;
+        }
+
+        public final Config setDir(final File dir) {
+            mDir = dir == null ? null : (dir.getAbsolutePath() + FILE_SEP);
+            return this;
+        }
+
+        public final Config setFilePrefix(final String filePrefix) {
+            if (UtilsBridge.isSpace(filePrefix)) {
+                mFilePrefix = "util";
+            } else {
+                mFilePrefix = filePrefix;
+            }
+            return this;
+        }
+
+        public final Config setFileExtension(final String fileExtension) {
+            if (UtilsBridge.isSpace(fileExtension)) {
+                mFileExtension = ".txt";
+            } else {
+                if (fileExtension.startsWith(".")) {
+                    mFileExtension = fileExtension;
+                } else {
+                    mFileExtension = "." + fileExtension;
+                }
+            }
+            return this;
+        }
+
+        public final Config setBorderSwitch(final boolean borderSwitch) {
+            mLogBorderSwitch = borderSwitch;
+            return this;
+        }
+
+        public final Config setSingleTagSwitch(final boolean singleTagSwitch) {
+            mSingleTagSwitch = singleTagSwitch;
+            return this;
+        }
+
+        public final Config setConsoleFilter(@TYPE final int consoleFilter) {
+            mConsoleFilter = consoleFilter;
+            return this;
+        }
+
+        public final Config setFileFilter(@TYPE final int fileFilter) {
+            mFileFilter = fileFilter;
+            return this;
+        }
+
+        public final Config setStackDeep(@IntRange(from = 1) final int stackDeep) {
+            mStackDeep = stackDeep;
+            return this;
+        }
+
+        public final Config setStackOffset(@IntRange(from = 0) final int stackOffset) {
+            mStackOffset = stackOffset;
+            return this;
+        }
+
+        public final Config setSaveDays(@IntRange(from = 1) final int saveDays) {
+            mSaveDays = saveDays;
+            return this;
+        }
+
+        public final <T> Config addFormatter(final IFormatter<T> iFormatter) {
+            if (iFormatter != null) {
+                I_FORMATTER_MAP.put(getTypeClassFromParadigm(iFormatter), iFormatter);
+            }
+            return this;
+        }
+
+        public final Config setFileWriter(final IFileWriter fileWriter) {
+            mFileWriter = fileWriter;
+            return this;
+        }
+
+        public final Config setOnConsoleOutputListener(final OnConsoleOutputListener listener) {
+            mOnConsoleOutputListener = listener;
+            return this;
+        }
+
+        public final Config setOnFileOutputListener(final OnFileOutputListener listener) {
+            mOnFileOutputListener = listener;
+            return this;
+        }
+
+        public final Config addFileExtraHead(final Map<String, String> fileExtraHead) {
+            mFileHead.append(fileExtraHead);
+            return this;
+        }
+
+        public final Config addFileExtraHead(final String key, final String value) {
+            mFileHead.append(key, value);
+            return this;
+        }
+
+        public final String getProcessName() {
+            if (mProcessName == null) return "";
+            return mProcessName.replace(":", "_");
+        }
+
+        public final String getDefaultDir() {
+            return mDefaultDir;
+        }
+
+        public final String getDir() {
+            return mDir == null ? mDefaultDir : mDir;
+        }
+
+        public final String getFilePrefix() {
+            return mFilePrefix;
+        }
+
+        public final String getFileExtension() {
+            return mFileExtension;
+        }
+
+        public final boolean isLogSwitch() {
+            return mLogSwitch;
+        }
+
+        public final boolean isLog2ConsoleSwitch() {
+            return mLog2ConsoleSwitch;
+        }
+
+        public final String getGlobalTag() {
+            if (UtilsBridge.isSpace(mGlobalTag)) return "";
+            return mGlobalTag;
+        }
+
+        public final boolean isLogHeadSwitch() {
+            return mLogHeadSwitch;
+        }
+
+        public final boolean isLog2FileSwitch() {
+            return mLog2FileSwitch;
+        }
+
+        public final boolean isLogBorderSwitch() {
+            return mLogBorderSwitch;
+        }
+
+        public final boolean isSingleTagSwitch() {
+            return mSingleTagSwitch;
+        }
+
+        public final char getConsoleFilter() {
+            return T[mConsoleFilter - V];
+        }
+
+        public final char getFileFilter() {
+            return T[mFileFilter - V];
+        }
+
+        public final int getStackDeep() {
+            return mStackDeep;
+        }
+
+        public final int getStackOffset() {
+            return mStackOffset;
+        }
+
+        public final int getSaveDays() {
+            return mSaveDays;
+        }
+
+        public final boolean haveSetOnConsoleOutputListener() {
+            return mOnConsoleOutputListener != null;
+        }
+
+        public final boolean haveSetOnFileOutputListener() {
+            return mOnFileOutputListener != null;
+        }
+
+        @Override
+        public String toString() {
+            return "process: " + getProcessName()
+                    + LINE_SEP + "logSwitch: " + isLogSwitch()
+                    + LINE_SEP + "consoleSwitch: " + isLog2ConsoleSwitch()
+                    + LINE_SEP + "tag: " + (getGlobalTag().equals("") ? "null" : getGlobalTag())
+                    + LINE_SEP + "headSwitch: " + isLogHeadSwitch()
+                    + LINE_SEP + "fileSwitch: " + isLog2FileSwitch()
+                    + LINE_SEP + "dir: " + getDir()
+                    + LINE_SEP + "filePrefix: " + getFilePrefix()
+                    + LINE_SEP + "borderSwitch: " + isLogBorderSwitch()
+                    + LINE_SEP + "singleTagSwitch: " + isSingleTagSwitch()
+                    + LINE_SEP + "consoleFilter: " + getConsoleFilter()
+                    + LINE_SEP + "fileFilter: " + getFileFilter()
+                    + LINE_SEP + "stackDeep: " + getStackDeep()
+                    + LINE_SEP + "stackOffset: " + getStackOffset()
+                    + LINE_SEP + "saveDays: " + getSaveDays()
+                    + LINE_SEP + "formatter: " + I_FORMATTER_MAP
+                    + LINE_SEP + "fileWriter: " + mFileWriter
+                    + LINE_SEP + "onConsoleOutputListener: " + mOnConsoleOutputListener
+                    + LINE_SEP + "onFileOutputListener: " + mOnFileOutputListener
+                    + LINE_SEP + "fileExtraHeader: " + mFileHead.getAppended();
+        }
+    }
+
+    public abstract static class IFormatter<T> {
+        public abstract String format(T t);
+    }
+
+    public interface IFileWriter {
+        void write(String file, String content);
+    }
+
+    public interface OnConsoleOutputListener {
+        void onConsoleOutput(@TYPE int type, String tag, String content);
+    }
+
+    public interface OnFileOutputListener {
+        void onFileOutput(String filePath, String content);
+    }
+
+    private final static class TagHead {
+        String   tag;
+        String[] consoleHead;
+        String   fileHead;
+
+        TagHead(String tag, String[] consoleHead, String fileHead) {
+            this.tag = tag;
+            this.consoleHead = consoleHead;
+            this.fileHead = fileHead;
+        }
+    }
+
+    private final static class LogFormatter {
+
+        static String object2String(Object object) {
+            return object2String(object, -1);
+        }
+
+        static String object2String(Object object, int type) {
+            if (object.getClass().isArray()) return array2String(object);
+            if (object instanceof Throwable)
+                return UtilsBridge.getFullStackTrace((Throwable) object);
+            if (object instanceof Bundle) return bundle2String((Bundle) object);
+            if (object instanceof Intent) return intent2String((Intent) object);
+            if (type == JSON) {
+                return object2Json(object);
+            } else if (type == XML) {
+                return formatXml(object.toString());
+            }
+            return object.toString();
+        }
+
+        private static String bundle2String(Bundle bundle) {
+            Iterator<String> iterator = bundle.keySet().iterator();
+            if (!iterator.hasNext()) {
+                return "Bundle {}";
+            }
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Bundle { ");
+            for (; ; ) {
+                String key = iterator.next();
+                Object value = bundle.get(key);
+                sb.append(key).append('=');
+                if (value instanceof Bundle) {
+                    sb.append(value == bundle ? "(this Bundle)" : bundle2String((Bundle) value));
+                } else {
+                    sb.append(formatObject(value));
+                }
+                if (!iterator.hasNext()) return sb.append(" }").toString();
+                sb.append(',').append(' ');
+            }
+        }
+
+        private static String intent2String(Intent intent) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Intent { ");
+            boolean first = true;
+            String mAction = intent.getAction();
+            if (mAction != null) {
+                sb.append("act=").append(mAction);
+                first = false;
+            }
+            Set<String> mCategories = intent.getCategories();
+            if (mCategories != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("cat=[");
+                boolean firstCategory = true;
+                for (String c : mCategories) {
+                    if (!firstCategory) {
+                        sb.append(',');
+                    }
+                    sb.append(c);
+                    firstCategory = false;
+                }
+                sb.append("]");
+            }
+            Uri mData = intent.getData();
+            if (mData != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("dat=").append(mData);
+            }
+            String mType = intent.getType();
+            if (mType != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("typ=").append(mType);
+            }
+            int mFlags = intent.getFlags();
+            if (mFlags != 0) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("flg=0x").append(Integer.toHexString(mFlags));
+            }
+            String mPackage = intent.getPackage();
+            if (mPackage != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("pkg=").append(mPackage);
+            }
+            ComponentName mComponent = intent.getComponent();
+            if (mComponent != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("cmp=").append(mComponent.flattenToShortString());
+            }
+            Rect mSourceBounds = intent.getSourceBounds();
+            if (mSourceBounds != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("bnds=").append(mSourceBounds.toShortString());
+            }
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                ClipData mClipData = intent.getClipData();
+                if (mClipData != null) {
+                    if (!first) {
+                        sb.append(' ');
+                    }
+                    first = false;
+                    clipData2String(mClipData, sb);
+                }
+            }
+            Bundle mExtras = intent.getExtras();
+            if (mExtras != null) {
+                if (!first) {
+                    sb.append(' ');
+                }
+                first = false;
+                sb.append("extras={");
+                sb.append(bundle2String(mExtras));
+                sb.append('}');
+            }
+            if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+                Intent mSelector = intent.getSelector();
+                if (mSelector != null) {
+                    if (!first) {
+                        sb.append(' ');
+                    }
+                    first = false;
+                    sb.append("sel={");
+                    sb.append(mSelector == intent ? "(this Intent)" : intent2String(mSelector));
+                    sb.append("}");
+                }
+            }
+            sb.append(" }");
+            return sb.toString();
+        }
+
+        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+        private static void clipData2String(ClipData clipData, StringBuilder sb) {
+            ClipData.Item item = clipData.getItemAt(0);
+            if (item == null) {
+                sb.append("ClipData.Item {}");
+                return;
+            }
+            sb.append("ClipData.Item { ");
+            String mHtmlText = item.getHtmlText();
+            if (mHtmlText != null) {
+                sb.append("H:");
+                sb.append(mHtmlText);
+                sb.append("}");
+                return;
+            }
+            CharSequence mText = item.getText();
+            if (mText != null) {
+                sb.append("T:");
+                sb.append(mText);
+                sb.append("}");
+                return;
+            }
+            Uri uri = item.getUri();
+            if (uri != null) {
+                sb.append("U:").append(uri);
+                sb.append("}");
+                return;
+            }
+            Intent intent = item.getIntent();
+            if (intent != null) {
+                sb.append("I:");
+                sb.append(intent2String(intent));
+                sb.append("}");
+                return;
+            }
+            sb.append("NULL");
+            sb.append("}");
+        }
+
+        private static String object2Json(Object object) {
+            if (object instanceof CharSequence) {
+                return UtilsBridge.formatJson(object.toString());
+            }
+            try {
+                return UtilsBridge.getGson4LogUtils().toJson(object);
+            } catch (Throwable t) {
+                return object.toString();
+            }
+        }
+
+        private static String formatJson(String json) {
+            try {
+                for (int i = 0, len = json.length(); i < len; i++) {
+                    char c = json.charAt(i);
+                    if (c == '{') {
+                        return new JSONObject(json).toString(2);
+                    } else if (c == '[') {
+                        return new JSONArray(json).toString(2);
+                    } else if (!Character.isWhitespace(c)) {
+                        return json;
+                    }
+                }
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+            return json;
+        }
+
+        private static String formatXml(String xml) {
+            try {
+                Source xmlInput = new StreamSource(new StringReader(xml));
+                StreamResult xmlOutput = new StreamResult(new StringWriter());
+                Transformer transformer = TransformerFactory.newInstance().newTransformer();
+                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+                transformer.transform(xmlInput, xmlOutput);
+                xml = xmlOutput.getWriter().toString().replaceFirst(">", ">" + LINE_SEP);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return xml;
+        }
+
+        private static String array2String(Object object) {
+            if (object instanceof Object[]) {
+                return Arrays.deepToString((Object[]) object);
+            } else if (object instanceof boolean[]) {
+                return Arrays.toString((boolean[]) object);
+            } else if (object instanceof byte[]) {
+                return Arrays.toString((byte[]) object);
+            } else if (object instanceof char[]) {
+                return Arrays.toString((char[]) object);
+            } else if (object instanceof double[]) {
+                return Arrays.toString((double[]) object);
+            } else if (object instanceof float[]) {
+                return Arrays.toString((float[]) object);
+            } else if (object instanceof int[]) {
+                return Arrays.toString((int[]) object);
+            } else if (object instanceof long[]) {
+                return Arrays.toString((long[]) object);
+            } else if (object instanceof short[]) {
+                return Arrays.toString((short[]) object);
+            }
+            throw new IllegalArgumentException("Array has incompatible type: " + object.getClass());
+        }
+    }
+
+    private static <T> Class getTypeClassFromParadigm(final IFormatter<T> formatter) {
+        Type[] genericInterfaces = formatter.getClass().getGenericInterfaces();
+        Type type;
+        if (genericInterfaces.length == 1) {
+            type = genericInterfaces[0];
+        } else {
+            type = formatter.getClass().getGenericSuperclass();
+        }
+        type = ((ParameterizedType) type).getActualTypeArguments()[0];
+        while (type instanceof ParameterizedType) {
+            type = ((ParameterizedType) type).getRawType();
+        }
+        String className = type.toString();
+        if (className.startsWith("class ")) {
+            className = className.substring(6);
+        } else if (className.startsWith("interface ")) {
+            className = className.substring(10);
+        }
+        try {
+            return Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private static Class getClassFromObject(final Object obj) {
+        Class objClass = obj.getClass();
+        if (objClass.isAnonymousClass() || objClass.isSynthetic()) {
+            Type[] genericInterfaces = objClass.getGenericInterfaces();
+            String className;
+            if (genericInterfaces.length == 1) {// interface
+                Type type = genericInterfaces[0];
+                while (type instanceof ParameterizedType) {
+                    type = ((ParameterizedType) type).getRawType();
+                }
+                className = type.toString();
+            } else {// abstract class or lambda
+                Type type = objClass.getGenericSuperclass();
+                while (type instanceof ParameterizedType) {
+                    type = ((ParameterizedType) type).getRawType();
+                }
+                className = type.toString();
+            }
+
+            if (className.startsWith("class ")) {
+                className = className.substring(6);
+            } else if (className.startsWith("interface ")) {
+                className = className.substring(10);
+            }
+            try {
+                return Class.forName(className);
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        return objClass;
+    }
+}

+ 191 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/MapUtils.java

@@ -0,0 +1,191 @@
+package com.cooleshow.base.utils;
+
+import android.util.Pair;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * <pre>
+ *     author: blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/08/12
+ *     desc  : utils about map
+ * </pre>
+ */
+public class MapUtils {
+
+    private MapUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Returns a new read-only map with the specified contents, given as a list of pairs
+     * where the first value is the key and the second is the value.
+     *
+     * @param pairs a list of pairs
+     * @return a new read-only map with the specified contents
+     */
+    @SafeVarargs
+    public static <K, V> Map<K, V> newUnmodifiableMap(final Pair<K, V>... pairs) {
+        return Collections.unmodifiableMap(newHashMap(pairs));
+    }
+
+    @SafeVarargs
+    public static <K, V> HashMap<K, V> newHashMap(final Pair<K, V>... pairs) {
+        HashMap<K, V> map = new HashMap<>();
+        if (pairs == null || pairs.length == 0) {
+            return map;
+        }
+        for (Pair<K, V> pair : pairs) {
+            if (pair == null) continue;
+            map.put(pair.first, pair.second);
+        }
+        return map;
+    }
+
+    @SafeVarargs
+    public static <K, V> LinkedHashMap<K, V> newLinkedHashMap(final Pair<K, V>... pairs) {
+        LinkedHashMap<K, V> map = new LinkedHashMap<>();
+        if (pairs == null || pairs.length == 0) {
+            return map;
+        }
+        for (Pair<K, V> pair : pairs) {
+            if (pair == null) continue;
+            map.put(pair.first, pair.second);
+        }
+        return map;
+    }
+
+    @SafeVarargs
+    public static <K, V> TreeMap<K, V> newTreeMap(final Comparator<K> comparator,
+                                                  final Pair<K, V>... pairs) {
+        if (comparator == null) {
+            throw new IllegalArgumentException("comparator must not be null");
+        }
+        TreeMap<K, V> map = new TreeMap<>(comparator);
+        if (pairs == null || pairs.length == 0) {
+            return map;
+        }
+        for (Pair<K, V> pair : pairs) {
+            if (pair == null) continue;
+            map.put(pair.first, pair.second);
+        }
+        return map;
+    }
+
+    @SafeVarargs
+    public static <K, V> Hashtable<K, V> newHashTable(final Pair<K, V>... pairs) {
+        Hashtable<K, V> map = new Hashtable<>();
+        if (pairs == null || pairs.length == 0) {
+            return map;
+        }
+        for (Pair<K, V> pair : pairs) {
+            if (pair == null) continue;
+            map.put(pair.first, pair.second);
+        }
+        return map;
+    }
+
+    /**
+     * Null-safe check if the specified map is empty.
+     * <p>
+     * Null returns true.
+     *
+     * @param map the map to check, may be null
+     * @return true if empty or null
+     */
+    public static boolean isEmpty(Map map) {
+        return map == null || map.size() == 0;
+    }
+
+    /**
+     * Null-safe check if the specified map is not empty.
+     * <p>
+     * Null returns false.
+     *
+     * @param map the map to check, may be null
+     * @return true if non-null and non-empty
+     */
+    public static boolean isNotEmpty(Map map) {
+        return !isEmpty(map);
+    }
+
+    /**
+     * Gets the size of the map specified.
+     *
+     * @param map The map.
+     * @return the size of the map specified
+     */
+    public static int size(Map map) {
+        if (map == null) return 0;
+        return map.size();
+    }
+
+    /**
+     * Executes the given closure on each element in the collection.
+     * <p>
+     * If the input collection or closure is null, there is no change made.
+     *
+     * @param map     the map to get the input from, may be null
+     * @param closure the closure to perform, may be null
+     */
+    public static <K, V> void forAllDo(Map<K, V> map, Closure<K, V> closure) {
+        if (map == null || closure == null) return;
+        for (Map.Entry<K, V> kvEntry : map.entrySet()) {
+            closure.execute(kvEntry.getKey(), kvEntry.getValue());
+        }
+    }
+
+    /**
+     * Transform the map by applying a Transformer to each element.
+     * <p>
+     * If the input map or transformer is null, there is no change made.
+     *
+     * @param map         the map to get the input from, may be null
+     * @param transformer the transformer to perform, may be null
+     */
+    public static <K1, V1, K2, V2> Map<K2, V2> transform(Map<K1, V1> map, final Transformer<K1, V1, K2, V2> transformer) {
+        if (map == null || transformer == null) return null;
+        try {
+            final Map<K2, V2> transMap = map.getClass().newInstance();
+            forAllDo(map, new Closure<K1, V1>() {
+                @Override
+                public void execute(K1 key, V1 value) {
+                    Pair<K2, V2> pair = transformer.transform(key, value);
+                    transMap.put(pair.first, pair.second);
+                }
+            });
+            return transMap;
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Return the string of map.
+     *
+     * @param map The map.
+     * @return the string of map
+     */
+    public static String toString(Map map) {
+        if (map == null) return "null";
+        return map.toString();
+    }
+
+    public interface Closure<K, V> {
+        void execute(K key, V value);
+    }
+
+    public interface Transformer<K1, V1, K2, V2> {
+        Pair<K2, V2> transform(K1 k1, V1 v1);
+    }
+}

+ 355 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/MessengerUtils.java

@@ -0,0 +1,355 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/07/10
+ *     desc  : utils about messenger
+ * </pre>
+ */
+public class MessengerUtils {
+
+    private static ConcurrentHashMap<String, MessageCallback> subscribers = new ConcurrentHashMap<>();
+
+    private static Map<String, Client> sClientMap = new HashMap<>();
+    private static Client              sLocalClient;
+
+    private static final int    WHAT_SUBSCRIBE   = 0x00;
+    private static final int    WHAT_UNSUBSCRIBE = 0x01;
+    private static final int    WHAT_SEND        = 0x02;
+    private static final String KEY_STRING       = "MESSENGER_UTILS";
+
+    public static void register() {
+        if (UtilsBridge.isMainProcess()) {
+            if (UtilsBridge.isServiceRunning(ServerService.class.getName())) {
+                Log.i("MessengerUtils", "Server service is running.");
+                return;
+            }
+            startServiceCompat(new Intent(Utils.getApp(), ServerService.class));
+            return;
+        }
+        if (sLocalClient == null) {
+            Client client = new Client(null);
+            if (client.bind()) {
+                sLocalClient = client;
+            } else {
+                Log.e("MessengerUtils", "Bind service failed.");
+            }
+        } else {
+            Log.i("MessengerUtils", "The client have been bind.");
+        }
+    }
+
+    public static void unregister() {
+        if (UtilsBridge.isMainProcess()) {
+            if (!UtilsBridge.isServiceRunning(ServerService.class.getName())) {
+                Log.i("MessengerUtils", "Server service isn't running.");
+                return;
+            }
+            Intent intent = new Intent(Utils.getApp(), ServerService.class);
+            Utils.getApp().stopService(intent);
+        }
+        if (sLocalClient != null) {
+            sLocalClient.unbind();
+        }
+    }
+
+    public static void register(final String pkgName) {
+        if (sClientMap.containsKey(pkgName)) {
+            Log.i("MessengerUtils", "register: client registered: " + pkgName);
+            return;
+        }
+        Client client = new Client(pkgName);
+        if (client.bind()) {
+            sClientMap.put(pkgName, client);
+        } else {
+            Log.e("MessengerUtils", "register: client bind failed: " + pkgName);
+        }
+    }
+
+    public static void unregister(final String pkgName) {
+        if (!sClientMap.containsKey(pkgName)) {
+            Log.i("MessengerUtils", "unregister: client didn't register: " + pkgName);
+            return;
+        }
+        Client client = sClientMap.get(pkgName);
+        sClientMap.remove(pkgName);
+        if (client != null) {
+            client.unbind();
+        }
+    }
+
+    public static void subscribe(@NonNull final String key, @NonNull final MessageCallback callback) {
+        subscribers.put(key, callback);
+    }
+
+    public static void unsubscribe(@NonNull final String key) {
+        subscribers.remove(key);
+    }
+
+    public static void post(@NonNull String key, @NonNull Bundle data) {
+        data.putString(KEY_STRING, key);
+        if (sLocalClient != null) {
+            sLocalClient.sendMsg2Server(data);
+        } else {
+            Intent intent = new Intent(Utils.getApp(), ServerService.class);
+            intent.putExtras(data);
+            startServiceCompat(intent);
+        }
+        for (Client client : sClientMap.values()) {
+            client.sendMsg2Server(data);
+        }
+    }
+
+    private static void startServiceCompat(Intent intent) {
+        try {
+            intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                Utils.getApp().startForegroundService(intent);
+            } else {
+                Utils.getApp().startService(intent);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    static class Client {
+
+        String             mPkgName;
+        Messenger          mServer;
+        LinkedList<Bundle> mCached = new LinkedList<>();
+        @SuppressLint("HandlerLeak")
+        Handler mReceiveServeMsgHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                Bundle data = msg.getData();
+                data.setClassLoader(MessengerUtils.class.getClassLoader());
+                String key = data.getString(KEY_STRING);
+                if (key != null) {
+                    MessageCallback callback = subscribers.get(key);
+                    if (callback != null) {
+                        callback.messageCall(data);
+                    }
+                }
+            }
+        };
+        Messenger         mClient = new Messenger(mReceiveServeMsgHandler);
+        ServiceConnection mConn   = new ServiceConnection() {
+
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Log.d("MessengerUtils", "client service connected " + name);
+                mServer = new Messenger(service);
+                int key = UtilsBridge.getCurrentProcessName().hashCode();
+                Message msg = Message.obtain(mReceiveServeMsgHandler, WHAT_SUBSCRIBE, key, 0);
+                msg.getData().setClassLoader(MessengerUtils.class.getClassLoader());
+                msg.replyTo = mClient;
+                try {
+                    mServer.send(msg);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                sendCachedMsg2Server();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                Log.w("MessengerUtils", "client service disconnected:" + name);
+                mServer = null;
+                if (!bind()) {
+                    Log.e("MessengerUtils", "client service rebind failed: " + name);
+                }
+            }
+        };
+
+        Client(String pkgName) {
+            this.mPkgName = pkgName;
+        }
+
+        boolean bind() {
+            if (TextUtils.isEmpty(mPkgName)) {
+                Intent intent = new Intent(Utils.getApp(), ServerService.class);
+                return Utils.getApp().bindService(intent, mConn, Context.BIND_AUTO_CREATE);
+            }
+            if (UtilsBridge.isAppInstalled(mPkgName)) {
+                if (UtilsBridge.isAppRunning(mPkgName)) {
+                    Intent intent = new Intent(mPkgName + ".messenger");
+                    intent.setPackage(mPkgName);
+                    return Utils.getApp().bindService(intent, mConn, Context.BIND_AUTO_CREATE);
+                } else {
+                    Log.e("MessengerUtils", "bind: the app is not running -> " + mPkgName);
+                    return false;
+                }
+            } else {
+                Log.e("MessengerUtils", "bind: the app is not installed -> " + mPkgName);
+                return false;
+            }
+        }
+
+        void unbind() {
+            int key = UtilsBridge.getCurrentProcessName().hashCode();
+            Message msg = Message.obtain(mReceiveServeMsgHandler, WHAT_UNSUBSCRIBE, key, 0);
+            msg.replyTo = mClient;
+            try {
+                mServer.send(msg);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            try {
+                Utils.getApp().unbindService(mConn);
+            } catch (Exception ignore) {/*ignore*/}
+        }
+
+        void sendMsg2Server(Bundle bundle) {
+            if (mServer == null) {
+                mCached.addFirst(bundle);
+                Log.i("MessengerUtils", "save the bundle " + bundle);
+            } else {
+                sendCachedMsg2Server();
+                if (!send2Server(bundle)) {
+                    mCached.addFirst(bundle);
+                }
+            }
+        }
+
+        private void sendCachedMsg2Server() {
+            if (mCached.isEmpty()) return;
+            for (int i = mCached.size() - 1; i >= 0; --i) {
+                if (send2Server(mCached.get(i))) {
+                    mCached.remove(i);
+                }
+            }
+        }
+
+        private boolean send2Server(Bundle bundle) {
+            Message msg = Message.obtain(mReceiveServeMsgHandler, WHAT_SEND);
+            bundle.setClassLoader(MessengerUtils.class.getClassLoader());
+            msg.setData(bundle);
+            msg.replyTo = mClient;
+            try {
+                mServer.send(msg);
+                return true;
+            } catch (RemoteException e) {
+                e.printStackTrace();
+                return false;
+            }
+        }
+    }
+
+    public static class ServerService extends Service {
+
+        private final ConcurrentHashMap<Integer, Messenger> mClientMap = new ConcurrentHashMap<>();
+
+        @SuppressLint("HandlerLeak")
+        private final Handler mReceiveClientMsgHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case WHAT_SUBSCRIBE:
+                        mClientMap.put(msg.arg1, msg.replyTo);
+                        break;
+                    case WHAT_SEND:
+                        sendMsg2Client(msg);
+                        consumeServerProcessCallback(msg);
+                        break;
+                    case WHAT_UNSUBSCRIBE:
+                        mClientMap.remove(msg.arg1);
+                        break;
+                    default:
+                        super.handleMessage(msg);
+                }
+            }
+        };
+
+        private final Messenger messenger = new Messenger(mReceiveClientMsgHandler);
+
+        @Nullable
+        @Override
+        public IBinder onBind(Intent intent) {
+            return messenger.getBinder();
+        }
+
+        @Override
+        public int onStartCommand(Intent intent, int flags, int startId) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                Notification notification = UtilsBridge.getNotification(
+                        NotificationUtils.ChannelConfig.DEFAULT_CHANNEL_CONFIG, null
+                );
+                startForeground(1, notification);
+            }
+            if (intent != null) {
+                Bundle extras = intent.getExtras();
+                if (extras != null) {
+                    Message msg = Message.obtain(mReceiveClientMsgHandler, WHAT_SEND);
+                    msg.replyTo = messenger;
+                    msg.setData(extras);
+                    sendMsg2Client(msg);
+                    consumeServerProcessCallback(msg);
+                }
+            }
+            return START_NOT_STICKY;
+        }
+
+        private void sendMsg2Client(final Message msg) {
+           final Message obtain = Message.obtain(msg); //Copy the original
+            for (Messenger client : mClientMap.values()) {
+                try {
+                    if (client != null) {
+                        client.send(Message.obtain(obtain));
+                    }
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+            obtain.recycle(); //Recycled copy
+        }
+
+        private void consumeServerProcessCallback(final Message msg) {
+            Bundle data = msg.getData();
+            if (data != null) {
+                String key = data.getString(KEY_STRING);
+                if (key != null) {
+                    MessageCallback callback = subscribers.get(key);
+                    if (callback != null) {
+                        callback.messageCall(data);
+                    }
+                }
+            }
+        }
+    }
+
+    public interface MessageCallback {
+        void messageCall(Bundle data);
+    }
+}

+ 144 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/MetaDataUtils.java

@@ -0,0 +1,144 @@
+package com.cooleshow.base.utils;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/05/15
+ *     desc  : utils about meta-data
+ * </pre>
+ */
+public final class MetaDataUtils {
+
+    private MetaDataUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the value of meta-data in application.
+     *
+     * @param key The key of meta-data.
+     * @return the value of meta-data in application
+     */
+    public static String getMetaDataInApp(@NonNull final String key) {
+        String value = "";
+        PackageManager pm = Utils.getApp().getPackageManager();
+        String packageName = Utils.getApp().getPackageName();
+        try {
+            ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+            value = String.valueOf(ai.metaData.get(key));
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return value;
+    }
+
+    /**
+     * Return the value of meta-data in activity.
+     *
+     * @param activity The activity.
+     * @param key      The key of meta-data.
+     * @return the value of meta-data in activity
+     */
+    public static String getMetaDataInActivity(@NonNull final Activity activity,
+                                               @NonNull final String key) {
+        return getMetaDataInActivity(activity.getClass(), key);
+    }
+
+    /**
+     * Return the value of meta-data in activity.
+     *
+     * @param clz The activity class.
+     * @param key The key of meta-data.
+     * @return the value of meta-data in activity
+     */
+    public static String getMetaDataInActivity(@NonNull final Class<? extends Activity> clz,
+                                               @NonNull final String key) {
+        String value = "";
+        PackageManager pm = Utils.getApp().getPackageManager();
+        ComponentName componentName = new ComponentName(Utils.getApp(), clz);
+        try {
+            ActivityInfo ai = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
+            value = String.valueOf(ai.metaData.get(key));
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return value;
+    }
+
+    /**
+     * Return the value of meta-data in service.
+     *
+     * @param service The service.
+     * @param key     The key of meta-data.
+     * @return the value of meta-data in service
+     */
+    public static String getMetaDataInService(@NonNull final Service service,
+                                              @NonNull final String key) {
+        return getMetaDataInService(service.getClass(), key);
+    }
+
+    /**
+     * Return the value of meta-data in service.
+     *
+     * @param clz The service class.
+     * @param key The key of meta-data.
+     * @return the value of meta-data in service
+     */
+    public static String getMetaDataInService(@NonNull final Class<? extends Service> clz,
+                                              @NonNull final String key) {
+        String value = "";
+        PackageManager pm = Utils.getApp().getPackageManager();
+        ComponentName componentName = new ComponentName(Utils.getApp(), clz);
+        try {
+            ServiceInfo info = pm.getServiceInfo(componentName, PackageManager.GET_META_DATA);
+            value = String.valueOf(info.metaData.get(key));
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return value;
+    }
+
+    /**
+     * Return the value of meta-data in receiver.
+     *
+     * @param receiver The receiver.
+     * @param key      The key of meta-data.
+     * @return the value of meta-data in receiver
+     */
+    public static String getMetaDataInReceiver(@NonNull final BroadcastReceiver receiver,
+                                               @NonNull final String key) {
+        return getMetaDataInReceiver(receiver.getClass(), key);
+    }
+
+    /**
+     * Return the value of meta-data in receiver.
+     *
+     * @param clz The receiver class.
+     * @param key The key of meta-data.
+     * @return the value of meta-data in receiver
+     */
+    public static String getMetaDataInReceiver(@NonNull final Class<? extends BroadcastReceiver> clz,
+                                               @NonNull final String key) {
+        String value = "";
+        PackageManager pm = Utils.getApp().getPackageManager();
+        ComponentName componentName = new ComponentName(Utils.getApp(), clz);
+        try {
+            ActivityInfo info = pm.getReceiverInfo(componentName, PackageManager.GET_META_DATA);
+            value = String.valueOf(info.metaData.get(key));
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return value;
+    }
+}

+ 1063 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/NetworkUtils.java

@@ -0,0 +1,1063 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.Formatter;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+import static android.Manifest.permission.INTERNET;
+import static android.content.Context.WIFI_SERVICE;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about network
+ * </pre>
+ */
+public final class NetworkUtils {
+
+    private NetworkUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    public enum NetworkType {
+        NETWORK_ETHERNET,
+        NETWORK_WIFI,
+        NETWORK_5G,
+        NETWORK_4G,
+        NETWORK_3G,
+        NETWORK_2G,
+        NETWORK_UNKNOWN,
+        NETWORK_NO
+    }
+
+    /**
+     * Open the settings of wireless.
+     */
+    public static void openWirelessSettings() {
+        Utils.getApp().startActivity(
+                new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        );
+    }
+
+    /**
+     * Return whether network is connected.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return {@code true}: connected<br>{@code false}: disconnected
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static boolean isConnected() {
+        NetworkInfo info = getActiveNetworkInfo();
+        return info != null && info.isConnected();
+    }
+
+    /**
+     * Return whether network is available.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param consumer The consumer.
+     * @return the task
+     */
+    @RequiresPermission(INTERNET)
+    public static Utils.Task<Boolean> isAvailableAsync(@NonNull final Utils.Consumer<Boolean> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {
+            @RequiresPermission(INTERNET)
+            @Override
+            public Boolean doInBackground() {
+                return isAvailable();
+            }
+        });
+    }
+
+    /**
+     * Return whether network is available.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(INTERNET)
+    public static boolean isAvailable() {
+        return isAvailableByDns() || isAvailableByPing(null);
+    }
+
+    /**
+     * Return whether network is available using ping.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     * <p>The default ping ip: 223.5.5.5</p>
+     *
+     * @param consumer The consumer.
+     */
+    @RequiresPermission(INTERNET)
+    public static void isAvailableByPingAsync(final Utils.Consumer<Boolean> consumer) {
+        isAvailableByPingAsync("", consumer);
+    }
+
+    /**
+     * Return whether network is available using ping.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param ip       The ip address.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    @RequiresPermission(INTERNET)
+    public static Utils.Task<Boolean> isAvailableByPingAsync(final String ip,
+                                                             @NonNull final Utils.Consumer<Boolean> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {
+            @RequiresPermission(INTERNET)
+            @Override
+            public Boolean doInBackground() {
+                return isAvailableByPing(ip);
+            }
+        });
+    }
+
+    /**
+     * Return whether network is available using ping.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     * <p>The default ping ip: 223.5.5.5</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(INTERNET)
+    public static boolean isAvailableByPing() {
+        return isAvailableByPing("");
+    }
+
+    /**
+     * Return whether network is available using ping.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param ip The ip address.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(INTERNET)
+    public static boolean isAvailableByPing(final String ip) {
+        final String realIp = TextUtils.isEmpty(ip) ? "223.5.5.5" : ip;
+        ShellUtils.CommandResult result = ShellUtils.execCmd(String.format("ping -c 1 %s", realIp), false);
+        return result.result == 0;
+    }
+
+    /**
+     * Return whether network is available using domain.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param consumer The consumer.
+     */
+    @RequiresPermission(INTERNET)
+    public static void isAvailableByDnsAsync(final Utils.Consumer<Boolean> consumer) {
+        isAvailableByDnsAsync("", consumer);
+    }
+
+    /**
+     * Return whether network is available using domain.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param domain   The name of domain.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    @RequiresPermission(INTERNET)
+    public static Utils.Task isAvailableByDnsAsync(final String domain,
+                                                   @NonNull final Utils.Consumer<Boolean> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {
+            @RequiresPermission(INTERNET)
+            @Override
+            public Boolean doInBackground() {
+                return isAvailableByDns(domain);
+            }
+        });
+    }
+
+    /**
+     * Return whether network is available using domain.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(INTERNET)
+    public static boolean isAvailableByDns() {
+        return isAvailableByDns("");
+    }
+
+    /**
+     * Return whether network is available using domain.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param domain The name of domain.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(INTERNET)
+    public static boolean isAvailableByDns(final String domain) {
+        final String realDomain = TextUtils.isEmpty(domain) ? "www.baidu.com" : domain;
+        InetAddress inetAddress;
+        try {
+            inetAddress = InetAddress.getByName(realDomain);
+            return inetAddress != null;
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Return whether mobile data is enabled.
+     *
+     * @return {@code true}: enabled<br>{@code false}: disabled
+     */
+    public static boolean getMobileDataEnabled() {
+        try {
+            TelephonyManager tm =
+                    (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
+            if (tm == null) return false;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                return tm.isDataEnabled();
+            }
+            @SuppressLint("PrivateApi")
+            Method getMobileDataEnabledMethod =
+                    tm.getClass().getDeclaredMethod("getDataEnabled");
+            if (null != getMobileDataEnabledMethod) {
+                return (boolean) getMobileDataEnabledMethod.invoke(tm);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+    
+     /**
+     * Returns true if device is connecting to the internet via a proxy, works for both Wi-Fi and Mobile Data.
+     *
+     * @return true if using proxy to connect to the internet.
+     */
+    public static boolean isBehindProxy(){
+        return !(System.getProperty("http.proxyHost") == null || System.getProperty("http.proxyPort") == null);
+    }
+
+    /**
+     * Returns true if device is connecting to the internet via a VPN.
+     *
+     * @return true if using VPN to conncet to the internet.
+     */
+    public static boolean isUsingVPN(){
+        ConnectivityManager cm = (ConnectivityManager) com.cooleshow.base.utils.Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            return cm.getNetworkInfo(ConnectivityManager.TYPE_VPN).isConnectedOrConnecting();
+        } else {
+            return cm.getNetworkInfo(NetworkCapabilities.TRANSPORT_VPN).isConnectedOrConnecting();
+        }
+    }
+
+    
+    
+    
+    
+
+    /**
+     * Return whether using mobile data.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static boolean isMobileData() {
+        NetworkInfo info = getActiveNetworkInfo();
+        return null != info
+                && info.isAvailable()
+                && info.getType() == ConnectivityManager.TYPE_MOBILE;
+    }
+
+    /**
+     * Return whether using 4G.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static boolean is4G() {
+        NetworkInfo info = getActiveNetworkInfo();
+        return info != null
+                && info.isAvailable()
+                && info.getSubtype() == TelephonyManager.NETWORK_TYPE_LTE;
+    }
+
+    /**
+     * Return whether using 4G.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static boolean is5G() {
+        NetworkInfo info = getActiveNetworkInfo();
+        return info != null
+                && info.isAvailable()
+                && info.getSubtype() == TelephonyManager.NETWORK_TYPE_NR;
+    }
+
+    /**
+     * Return whether wifi is enabled.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
+     *
+     * @return {@code true}: enabled<br>{@code false}: disabled
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public static boolean getWifiEnabled() {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
+        if (manager == null) return false;
+        return manager.isWifiEnabled();
+    }
+
+    /**
+     * Enable or disable wifi.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
+     *
+     * @param enabled True to enabled, false otherwise.
+     */
+    @RequiresPermission(CHANGE_WIFI_STATE)
+    public static void setWifiEnabled(final boolean enabled) {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
+        if (manager == null) return;
+        if (enabled == manager.isWifiEnabled()) return;
+        manager.setWifiEnabled(enabled);
+    }
+
+    /**
+     * Return whether wifi is connected.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return {@code true}: connected<br>{@code false}: disconnected
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static boolean isWifiConnected() {
+        ConnectivityManager cm =
+                (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (cm == null) return false;
+        NetworkInfo ni = cm.getActiveNetworkInfo();
+        return ni != null && ni.getType() == ConnectivityManager.TYPE_WIFI;
+    }
+
+    /**
+     * Return whether wifi is available.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />},
+     * {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @return {@code true}: available<br>{@code false}: unavailable
+     */
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})
+    public static boolean isWifiAvailable() {
+        return getWifiEnabled() && isAvailable();
+    }
+
+    /**
+     * Return whether wifi is available.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />},
+     * {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param consumer The consumer.
+     * @return the task
+     */
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})
+    public static Utils.Task<Boolean> isWifiAvailableAsync(@NonNull final Utils.Consumer<Boolean> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {
+            @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})
+            @Override
+            public Boolean doInBackground() {
+                return isWifiAvailable();
+            }
+        });
+    }
+
+    /**
+     * Return the name of network operate.
+     *
+     * @return the name of network operate
+     */
+    public static String getNetworkOperatorName() {
+        TelephonyManager tm =
+                (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm == null) return "";
+        return tm.getNetworkOperatorName();
+    }
+
+    /**
+     * Return type of network.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return type of network
+     * <ul>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_ETHERNET} </li>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_WIFI    } </li>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_4G      } </li>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_3G      } </li>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_2G      } </li>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_UNKNOWN } </li>
+     * <li>{@link NetworkUtils.NetworkType#NETWORK_NO      } </li>
+     * </ul>
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static NetworkType getNetworkType() {
+        if (isEthernet()) {
+            return NetworkType.NETWORK_ETHERNET;
+        }
+        NetworkInfo info = getActiveNetworkInfo();
+        if (info != null && info.isAvailable()) {
+            if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+                return NetworkType.NETWORK_WIFI;
+            } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
+                switch (info.getSubtype()) {
+                    case TelephonyManager.NETWORK_TYPE_GSM:
+                    case TelephonyManager.NETWORK_TYPE_GPRS:
+                    case TelephonyManager.NETWORK_TYPE_CDMA:
+                    case TelephonyManager.NETWORK_TYPE_EDGE:
+                    case TelephonyManager.NETWORK_TYPE_1xRTT:
+                    case TelephonyManager.NETWORK_TYPE_IDEN:
+                        return NetworkType.NETWORK_2G;
+
+                    case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+                    case TelephonyManager.NETWORK_TYPE_EVDO_A:
+                    case TelephonyManager.NETWORK_TYPE_UMTS:
+                    case TelephonyManager.NETWORK_TYPE_EVDO_0:
+                    case TelephonyManager.NETWORK_TYPE_HSDPA:
+                    case TelephonyManager.NETWORK_TYPE_HSUPA:
+                    case TelephonyManager.NETWORK_TYPE_HSPA:
+                    case TelephonyManager.NETWORK_TYPE_EVDO_B:
+                    case TelephonyManager.NETWORK_TYPE_EHRPD:
+                    case TelephonyManager.NETWORK_TYPE_HSPAP:
+                        return NetworkType.NETWORK_3G;
+
+                    case TelephonyManager.NETWORK_TYPE_IWLAN:
+                    case TelephonyManager.NETWORK_TYPE_LTE:
+                        return NetworkType.NETWORK_4G;
+
+                    case TelephonyManager.NETWORK_TYPE_NR:
+                        return NetworkType.NETWORK_5G;
+                    default:
+                        String subtypeName = info.getSubtypeName();
+                        if (subtypeName.equalsIgnoreCase("TD-SCDMA")
+                                || subtypeName.equalsIgnoreCase("WCDMA")
+                                || subtypeName.equalsIgnoreCase("CDMA2000")) {
+                            return NetworkType.NETWORK_3G;
+                        } else {
+                            return NetworkType.NETWORK_UNKNOWN;
+                        }
+                }
+            } else {
+                return NetworkType.NETWORK_UNKNOWN;
+            }
+        }
+        return NetworkType.NETWORK_NO;
+    }
+
+    /**
+     * Return whether using ethernet.
+     * <p>Must hold
+     * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    private static boolean isEthernet() {
+        final ConnectivityManager cm =
+                (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (cm == null) return false;
+        final NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);
+        if (info == null) return false;
+        NetworkInfo.State state = info.getState();
+        if (null == state) return false;
+        return state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING;
+    }
+
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    private static NetworkInfo getActiveNetworkInfo() {
+        ConnectivityManager cm =
+                (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (cm == null) return null;
+        return cm.getActiveNetworkInfo();
+    }
+
+    /**
+     * Return the ip address.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param useIPv4  True to use ipv4, false otherwise.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    public static Utils.Task<String> getIPAddressAsync(final boolean useIPv4,
+                                                       @NonNull final Utils.Consumer<String> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<String>(consumer) {
+            @RequiresPermission(INTERNET)
+            @Override
+            public String doInBackground() {
+                return getIPAddress(useIPv4);
+            }
+        });
+    }
+
+    /**
+     * Return the ip address.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param useIPv4 True to use ipv4, false otherwise.
+     * @return the ip address
+     */
+    @RequiresPermission(INTERNET)
+    public static String getIPAddress(final boolean useIPv4) {
+        try {
+            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
+            LinkedList<InetAddress> adds = new LinkedList<>();
+            while (nis.hasMoreElements()) {
+                NetworkInterface ni = nis.nextElement();
+                // To prevent phone of xiaomi return "10.0.2.15"
+                if (!ni.isUp() || ni.isLoopback()) continue;
+                Enumeration<InetAddress> addresses = ni.getInetAddresses();
+                while (addresses.hasMoreElements()) {
+                    adds.addFirst(addresses.nextElement());
+                }
+            }
+            for (InetAddress add : adds) {
+                if (!add.isLoopbackAddress()) {
+                    String hostAddress = add.getHostAddress();
+                    boolean isIPv4 = hostAddress.indexOf(':') < 0;
+                    if (useIPv4) {
+                        if (isIPv4) return hostAddress;
+                    } else {
+                        if (!isIPv4) {
+                            int index = hostAddress.indexOf('%');
+                            return index < 0
+                                    ? hostAddress.toUpperCase()
+                                    : hostAddress.substring(0, index).toUpperCase();
+                        }
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * Return the ip address of broadcast.
+     *
+     * @return the ip address of broadcast
+     */
+    public static String getBroadcastIpAddress() {
+        try {
+            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
+            LinkedList<InetAddress> adds = new LinkedList<>();
+            while (nis.hasMoreElements()) {
+                NetworkInterface ni = nis.nextElement();
+                if (!ni.isUp() || ni.isLoopback()) continue;
+                List<InterfaceAddress> ias = ni.getInterfaceAddresses();
+                for (int i = 0, size = ias.size(); i < size; i++) {
+                    InterfaceAddress ia = ias.get(i);
+                    InetAddress broadcast = ia.getBroadcast();
+                    if (broadcast != null) {
+                        return broadcast.getHostAddress();
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * Return the domain address.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param domain   The name of domain.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    @RequiresPermission(INTERNET)
+    public static Utils.Task<String> getDomainAddressAsync(final String domain,
+                                                           @NonNull final Utils.Consumer<String> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<String>(consumer) {
+            @RequiresPermission(INTERNET)
+            @Override
+            public String doInBackground() {
+                return getDomainAddress(domain);
+            }
+        });
+    }
+
+    /**
+     * Return the domain address.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
+     *
+     * @param domain The name of domain.
+     * @return the domain address
+     */
+    @RequiresPermission(INTERNET)
+    public static String getDomainAddress(final String domain) {
+        InetAddress inetAddress;
+        try {
+            inetAddress = InetAddress.getByName(domain);
+            return inetAddress.getHostAddress();
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Return the ip address by wifi.
+     *
+     * @return the ip address by wifi
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public static String getIpAddressByWifi() {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
+        if (wm == null) return "";
+        return Formatter.formatIpAddress(wm.getDhcpInfo().ipAddress);
+    }
+
+    /**
+     * Return the gate way by wifi.
+     *
+     * @return the gate way by wifi
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public static String getGatewayByWifi() {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
+        if (wm == null) return "";
+        return Formatter.formatIpAddress(wm.getDhcpInfo().gateway);
+    }
+
+    /**
+     * Return the net mask by wifi.
+     *
+     * @return the net mask by wifi
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public static String getNetMaskByWifi() {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
+        if (wm == null) return "";
+        return Formatter.formatIpAddress(wm.getDhcpInfo().netmask);
+    }
+
+    /**
+     * Return the server address by wifi.
+     *
+     * @return the server address by wifi
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public static String getServerAddressByWifi() {
+        @SuppressLint("WifiManagerLeak")
+        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
+        if (wm == null) return "";
+        return Formatter.formatIpAddress(wm.getDhcpInfo().serverAddress);
+    }
+
+    /**
+     * Return the ssid.
+     *
+     * @return the ssid.
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public static String getSSID() {
+        WifiManager wm = (WifiManager) Utils.getApp().getApplicationContext().getSystemService(WIFI_SERVICE);
+        if (wm == null) return "";
+        WifiInfo wi = wm.getConnectionInfo();
+        if (wi == null) return "";
+        String ssid = wi.getSSID();
+        if (TextUtils.isEmpty(ssid)) {
+            return "";
+        }
+        if (ssid.length() > 2 && ssid.charAt(0) == '"' && ssid.charAt(ssid.length() - 1) == '"') {
+            return ssid.substring(1, ssid.length() - 1);
+        }
+        return ssid;
+    }
+
+    /**
+     * Register the status of network changed listener.
+     *
+     * @param listener The status of network changed listener
+     */
+    @RequiresPermission(ACCESS_NETWORK_STATE)
+    public static void registerNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {
+        NetworkChangedReceiver.getInstance().registerListener(listener);
+    }
+
+    /**
+     * Return whether the status of network changed listener has been registered.
+     *
+     * @param listener The listener
+     * @return true to registered, false otherwise.
+     */
+    public static boolean isRegisteredNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {
+        return NetworkChangedReceiver.getInstance().isRegistered(listener);
+    }
+
+    /**
+     * Unregister the status of network changed listener.
+     *
+     * @param listener The status of network changed listener.
+     */
+    public static void unregisterNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {
+        NetworkChangedReceiver.getInstance().unregisterListener(listener);
+    }
+
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, ACCESS_COARSE_LOCATION})
+    public static WifiScanResults getWifiScanResult() {
+        WifiScanResults result = new WifiScanResults();
+        if (!getWifiEnabled()) return result;
+        @SuppressLint("WifiManagerLeak")
+        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
+        //noinspection ConstantConditions
+        List<ScanResult> results = wm.getScanResults();
+        if (results != null) {
+            result.setAllResults(results);
+        }
+        return result;
+    }
+
+    private static final long                                 SCAN_PERIOD_MILLIS    = 3000;
+    private static final Set<Utils.Consumer<WifiScanResults>> SCAN_RESULT_CONSUMERS = new CopyOnWriteArraySet<>();
+    private static       Timer                                sScanWifiTimer;
+    private static       WifiScanResults                      sPreWifiScanResults;
+
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, ACCESS_COARSE_LOCATION})
+    public static void addOnWifiChangedConsumer(final Utils.Consumer<WifiScanResults> consumer) {
+        if (consumer == null) return;
+        UtilsBridge.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (SCAN_RESULT_CONSUMERS.isEmpty()) {
+                    SCAN_RESULT_CONSUMERS.add(consumer);
+                    startScanWifi();
+                    return;
+                }
+                consumer.accept(sPreWifiScanResults);
+                SCAN_RESULT_CONSUMERS.add(consumer);
+            }
+        });
+    }
+
+    private static void startScanWifi() {
+        sPreWifiScanResults = new WifiScanResults();
+        sScanWifiTimer = new Timer();
+        sScanWifiTimer.schedule(new TimerTask() {
+            @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, ACCESS_COARSE_LOCATION})
+            @Override
+            public void run() {
+                startScanWifiIfEnabled();
+                WifiScanResults scanResults = getWifiScanResult();
+                if (isSameScanResults(sPreWifiScanResults.allResults, scanResults.allResults)) {
+                    return;
+                }
+                sPreWifiScanResults = scanResults;
+                UtilsBridge.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        for (Utils.Consumer<WifiScanResults> consumer : SCAN_RESULT_CONSUMERS) {
+                            consumer.accept(sPreWifiScanResults);
+                        }
+                    }
+                });
+            }
+        }, 0, SCAN_PERIOD_MILLIS);
+    }
+
+    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE})
+    private static void startScanWifiIfEnabled() {
+        if (!getWifiEnabled()) return;
+        @SuppressLint("WifiManagerLeak")
+        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
+        //noinspection ConstantConditions
+        wm.startScan();
+    }
+
+    public static void removeOnWifiChangedConsumer(final Utils.Consumer<WifiScanResults> consumer) {
+        if (consumer == null) return;
+        UtilsBridge.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                SCAN_RESULT_CONSUMERS.remove(consumer);
+                if (SCAN_RESULT_CONSUMERS.isEmpty()) {
+                    stopScanWifi();
+                }
+            }
+        });
+    }
+
+    private static void stopScanWifi() {
+        if (sScanWifiTimer != null) {
+            sScanWifiTimer.cancel();
+            sScanWifiTimer = null;
+        }
+    }
+
+    private static boolean isSameScanResults(List<ScanResult> l1, List<ScanResult> l2) {
+        if (l1 == null && l2 == null) {
+            return true;
+        }
+        if (l1 == null || l2 == null) {
+            return false;
+        }
+        if (l1.size() != l2.size()) {
+            return false;
+        }
+        for (int i = 0; i < l1.size(); i++) {
+            ScanResult r1 = l1.get(i);
+            ScanResult r2 = l2.get(i);
+            if (!isSameScanResultContent(r1, r2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isSameScanResultContent(ScanResult r1, ScanResult r2) {
+        return r1 != null && r2 != null && UtilsBridge.equals(r1.BSSID, r2.BSSID)
+                && UtilsBridge.equals(r1.SSID, r2.SSID)
+                && UtilsBridge.equals(r1.capabilities, r2.capabilities)
+                && r1.level == r2.level;
+    }
+
+    public static final class NetworkChangedReceiver extends BroadcastReceiver {
+
+        private static NetworkChangedReceiver getInstance() {
+            return LazyHolder.INSTANCE;
+        }
+
+        private NetworkType                         mType;
+        private Set<OnNetworkStatusChangedListener> mListeners = new HashSet<>();
+
+        @RequiresPermission(ACCESS_NETWORK_STATE)
+        void registerListener(final OnNetworkStatusChangedListener listener) {
+            if (listener == null) return;
+            UtilsBridge.runOnUiThread(new Runnable() {
+                @Override
+                @RequiresPermission(ACCESS_NETWORK_STATE)
+                public void run() {
+                    int preSize = mListeners.size();
+                    mListeners.add(listener);
+                    if (preSize == 0 && mListeners.size() == 1) {
+                        mType = getNetworkType();
+                        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+                        Utils.getApp().registerReceiver(NetworkChangedReceiver.getInstance(), intentFilter);
+                    }
+                }
+            });
+        }
+
+        boolean isRegistered(final OnNetworkStatusChangedListener listener) {
+            if (listener == null) return false;
+            return mListeners.contains(listener);
+        }
+
+        void unregisterListener(final OnNetworkStatusChangedListener listener) {
+            if (listener == null) return;
+            UtilsBridge.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    int preSize = mListeners.size();
+                    mListeners.remove(listener);
+                    if (preSize == 1 && mListeners.size() == 0) {
+                        Utils.getApp().unregisterReceiver(NetworkChangedReceiver.getInstance());
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
+                // debouncing
+                UtilsBridge.runOnUiThreadDelayed(new Runnable() {
+                    @Override
+                    @RequiresPermission(ACCESS_NETWORK_STATE)
+                    public void run() {
+                        NetworkType networkType = NetworkUtils.getNetworkType();
+                        if (mType == networkType) return;
+                        mType = networkType;
+                        if (networkType == NetworkType.NETWORK_NO) {
+                            for (OnNetworkStatusChangedListener listener : mListeners) {
+                                listener.onDisconnected();
+                            }
+                        } else {
+                            for (OnNetworkStatusChangedListener listener : mListeners) {
+                                listener.onConnected(networkType);
+                            }
+                        }
+                    }
+                }, 1000);
+            }
+        }
+
+        private static class LazyHolder {
+            private static final NetworkChangedReceiver INSTANCE = new NetworkChangedReceiver();
+        }
+    }
+
+//    /**
+//     * Register the status of network changed listener.
+//     */
+//    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+//    @RequiresPermission(ACCESS_NETWORK_STATE)
+//    public static void registerNetworkStatusChangedListener() {
+//        ConnectivityManager cm = (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
+//        if (cm == null) return;
+//        NetworkCallbackImpl networkCallback = NetworkCallbackImpl.LazyHolder.INSTANCE;
+//        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+//        NetworkRequest request = builder.build();
+//        cm.registerNetworkCallback(new NetworkRequest.Builder().build(), networkCallback);
+//    }
+//
+//
+//    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+//    public static final class NetworkCallbackImpl extends ConnectivityManager.NetworkCallback {
+//
+//        @Override
+//        public void onAvailable(@NonNull Network network) {
+//            super.onAvailable(network);
+//            LogUtils.d(TAG, "onAvailable: " + network);
+//        }
+//
+//        @Override
+//        public void onLosing(@NonNull Network network, int maxMsToLive) {
+//            super.onLosing(network, maxMsToLive);
+//            LogUtils.d(TAG, "onLosing: " + network);
+//        }
+//
+//        @Override
+//        public void onLost(@NonNull Network network) {
+//            super.onLost(network);
+//            LogUtils.e(TAG, "onLost: " + network);
+//        }
+//
+//        @Override
+//        public void onUnavailable() {
+//            super.onUnavailable();
+//            LogUtils.e(TAG, "onUnavailable");
+//        }
+//
+//        @Override
+//        public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities cap) {
+//            super.onCapabilitiesChanged(network, cap);
+//            if (cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
+//                if (cap.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+//                    LogUtils.d(TAG, "onCapabilitiesChanged: 网络类型为wifi");
+//                } else if (cap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+//                    LogUtils.d(TAG, "onCapabilitiesChanged: 蜂窝网络");
+//                } else {
+//                    LogUtils.d(TAG, "onCapabilitiesChanged: 其他网络");
+//                }
+//                LogUtils.d(TAG, "onCapabilitiesChanged: " + network + ", " + cap);
+//            }
+//        }
+//
+//        @Override
+//        public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkProperties lp) {
+//            super.onLinkPropertiesChanged(network, lp);
+//            LogUtils.d(TAG, "onLinkPropertiesChanged: " + network + ", " + lp);
+//        }
+//
+//        @Override
+//        public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {
+//            super.onBlockedStatusChanged(network, blocked);
+//            LogUtils.d(TAG, "onBlockedStatusChanged: " + network + ", " + blocked);
+//        }
+//
+//        private static class LazyHolder {
+//            private static final NetworkCallbackImpl INSTANCE = new NetworkCallbackImpl();
+//        }
+//    }
+
+    public interface OnNetworkStatusChangedListener {
+        void onDisconnected();
+
+        void onConnected(NetworkType networkType);
+    }
+
+    public static final class WifiScanResults {
+
+        private List<ScanResult> allResults    = new ArrayList<>();
+        private List<ScanResult> filterResults = new ArrayList<>();
+
+        public WifiScanResults() {
+        }
+
+        public List<ScanResult> getAllResults() {
+            return allResults;
+        }
+
+        public List<ScanResult> getFilterResults() {
+            return filterResults;
+        }
+
+        public void setAllResults(List<ScanResult> allResults) {
+            this.allResults = allResults;
+            filterResults = filterScanResult(allResults);
+        }
+
+        private static List<ScanResult> filterScanResult(final List<ScanResult> results) {
+            if (results == null || results.isEmpty()) {
+                return new ArrayList<>();
+            }
+            LinkedHashMap<String, ScanResult> map = new LinkedHashMap<>(results.size());
+            for (ScanResult result : results) {
+                if (TextUtils.isEmpty(result.SSID)) {
+                    continue;
+                }
+                ScanResult resultInMap = map.get(result.SSID);
+                if (resultInMap != null && resultInMap.level >= result.level) {
+                    continue;
+                }
+                map.put(result.SSID, result);
+            }
+            return new ArrayList<>(map.values());
+        }
+
+    }
+}

+ 338 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/NotificationUtils.java

@@ -0,0 +1,338 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RequiresPermission;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+
+import static android.Manifest.permission.EXPAND_STATUS_BAR;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/10/20
+ *     desc  : utils about notification
+ * </pre>
+ */
+public class NotificationUtils {
+
+    public static final int IMPORTANCE_UNSPECIFIED = -1000;
+    public static final int IMPORTANCE_NONE        = 0;
+    public static final int IMPORTANCE_MIN         = 1;
+    public static final int IMPORTANCE_LOW         = 2;
+    public static final int IMPORTANCE_DEFAULT     = 3;
+    public static final int IMPORTANCE_HIGH        = 4;
+
+    @IntDef({IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE, IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Importance {
+    }
+
+    /**
+     * Return whether the notifications enabled.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean areNotificationsEnabled() {
+        return NotificationManagerCompat.from(Utils.getApp()).areNotificationsEnabled();
+    }
+
+    /**
+     * Post a notification to be shown in the status bar.
+     *
+     * @param id       An identifier for this notification.
+     * @param consumer The consumer of create the builder of notification.
+     */
+    public static void notify(int id, Utils.Consumer<NotificationCompat.Builder> consumer) {
+        notify(null, id, ChannelConfig.DEFAULT_CHANNEL_CONFIG, consumer);
+    }
+
+    /**
+     * Post a notification to be shown in the status bar.
+     *
+     * @param tag      A string identifier for this notification.  May be {@code null}.
+     * @param id       An identifier for this notification.
+     * @param consumer The consumer of create the builder of notification.
+     */
+    public static void notify(String tag, int id, Utils.Consumer<NotificationCompat.Builder> consumer) {
+        notify(tag, id, ChannelConfig.DEFAULT_CHANNEL_CONFIG, consumer);
+    }
+
+    /**
+     * Post a notification to be shown in the status bar.
+     *
+     * @param id            An identifier for this notification.
+     * @param channelConfig The notification channel of config.
+     * @param consumer      The consumer of create the builder of notification.
+     */
+    public static void notify(int id, ChannelConfig channelConfig, Utils.Consumer<NotificationCompat.Builder> consumer) {
+        notify(null, id, channelConfig, consumer);
+    }
+
+    /**
+     * Post a notification to be shown in the status bar.
+     *
+     * @param tag           A string identifier for this notification.  May be {@code null}.
+     * @param id            An identifier for this notification.
+     * @param channelConfig The notification channel of config.
+     * @param consumer      The consumer of create the builder of notification.
+     */
+    public static void notify(String tag, int id, ChannelConfig channelConfig, Utils.Consumer<NotificationCompat.Builder> consumer) {
+        NotificationManagerCompat.from(Utils.getApp()).notify(tag, id, getNotification(channelConfig, consumer));
+    }
+
+
+    public static Notification getNotification(ChannelConfig channelConfig, Utils.Consumer<NotificationCompat.Builder> consumer) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            NotificationManager nm = (NotificationManager) Utils.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
+            //noinspection ConstantConditions
+            nm.createNotificationChannel(channelConfig.getNotificationChannel());
+        }
+
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(Utils.getApp());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            builder.setChannelId(channelConfig.mNotificationChannel.getId());
+        }
+        if (consumer != null) {
+            consumer.accept(builder);
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Cancel The notification.
+     *
+     * @param tag The tag for the notification will be cancelled.
+     * @param id  The identifier for the notification will be cancelled.
+     */
+    public static void cancel(String tag, final int id) {
+        NotificationManagerCompat.from(Utils.getApp()).cancel(tag, id);
+    }
+
+    /**
+     * Cancel The notification.
+     *
+     * @param id The identifier for the notification will be cancelled.
+     */
+    public static void cancel(final int id) {
+        NotificationManagerCompat.from(Utils.getApp()).cancel(id);
+    }
+
+    /**
+     * Cancel all of the notifications.
+     */
+    public static void cancelAll() {
+        NotificationManagerCompat.from(Utils.getApp()).cancelAll();
+    }
+
+    /**
+     * Set the notification bar's visibility.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />}</p>
+     *
+     * @param isVisible True to set notification bar visible, false otherwise.
+     */
+    @RequiresPermission(EXPAND_STATUS_BAR)
+    public static void setNotificationBarVisibility(final boolean isVisible) {
+        String methodName;
+        if (isVisible) {
+            methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
+        } else {
+            methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
+        }
+        invokePanels(methodName);
+    }
+
+    private static void invokePanels(final String methodName) {
+        try {
+            @SuppressLint("WrongConstant")
+            Object service = Utils.getApp().getSystemService("statusbar");
+            @SuppressLint("PrivateApi")
+            Class<?> statusBarManager = Class.forName("android.app.StatusBarManager");
+            Method expand = statusBarManager.getMethod(methodName);
+            expand.invoke(service);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static class ChannelConfig {
+
+        public static final ChannelConfig DEFAULT_CHANNEL_CONFIG = new ChannelConfig(
+                Utils.getApp().getPackageName(), Utils.getApp().getPackageName(), IMPORTANCE_DEFAULT
+        );
+
+        private NotificationChannel mNotificationChannel;
+
+        public ChannelConfig(String id, CharSequence name, @Importance int importance) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel = new NotificationChannel(id, name, importance);
+            }
+        }
+
+        public NotificationChannel getNotificationChannel() {
+            return mNotificationChannel;
+        }
+
+        /**
+         * Sets whether or not notifications posted to this channel can interrupt the user in
+         * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
+         * <p>
+         * Only modifiable by the system and notification ranker.
+         */
+        public ChannelConfig setBypassDnd(boolean bypassDnd) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setBypassDnd(bypassDnd);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the user visible description of this channel.
+         *
+         * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
+         * long.
+         */
+        public ChannelConfig setDescription(String description) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setDescription(description);
+            }
+            return this;
+        }
+
+        /**
+         * Sets what group this channel belongs to.
+         * <p>
+         * Group information is only used for presentation, not for behavior.
+         * <p>
+         * Only modifiable before the channel is submitted to
+         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the
+         * channel is not currently part of a group.
+         *
+         * @param groupId the id of a group created by
+         *                {@link NotificationManager#createNotificationChannelGroup)}.
+         */
+        public ChannelConfig setGroup(String groupId) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setGroup(groupId);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the level of interruption of this notification channel.
+         * <p>
+         * Only modifiable before the channel is submitted to
+         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+         *
+         * @param importance the amount the user should be interrupted by
+         *                   notifications from this channel.
+         */
+        public ChannelConfig setImportance(@Importance int importance) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setImportance(importance);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the notification light color for notifications posted to this channel, if lights are
+         * {@link NotificationChannel#enableLights(boolean) enabled} on this channel and the device supports that feature.
+         * <p>
+         * Only modifiable before the channel is submitted to
+         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+         */
+        public ChannelConfig setLightColor(int argb) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setLightColor(argb);
+            }
+            return this;
+        }
+
+        /**
+         * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
+         * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+         * <p>
+         * Only modifiable by the system and notification ranker.
+         */
+        public ChannelConfig setLockscreenVisibility(int lockscreenVisibility) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setLockscreenVisibility(lockscreenVisibility);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the user visible name of this channel.
+         *
+         * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too
+         * long.
+         */
+        public ChannelConfig setName(CharSequence name) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setName(name);
+            }
+            return this;
+        }
+
+        /**
+         * Sets whether notifications posted to this channel can appear as application icon badges
+         * in a Launcher.
+         * <p>
+         * Only modifiable before the channel is submitted to
+         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+         *
+         * @param showBadge true if badges should be allowed to be shown.
+         */
+        public ChannelConfig setShowBadge(boolean showBadge) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setShowBadge(showBadge);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the sound that should be played for notifications posted to this channel and its
+         * audio attributes. Notification channels with an {@link NotificationChannel#getImportance() importance} of at
+         * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
+         * <p>
+         * Only modifiable before the channel is submitted to
+         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+         */
+        public ChannelConfig setSound(Uri sound, AudioAttributes audioAttributes) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setSound(sound, audioAttributes);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the vibration pattern for notifications posted to this channel. If the provided
+         * pattern is valid (non-null, non-empty), will {@link NotificationChannel#enableVibration(boolean)} enable
+         * vibration} as well. Otherwise, vibration will be disabled.
+         * <p>
+         * Only modifiable before the channel is submitted to
+         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+         */
+        public ChannelConfig setVibrationPattern(long[] vibrationPattern) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                mNotificationChannel.setVibrationPattern(vibrationPattern);
+            }
+            return this;
+        }
+    }
+}

+ 198 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/NumberUtils.java

@@ -0,0 +1,198 @@
+package com.cooleshow.base.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * <pre>
+ *     author: blankj
+ *     blog  : http://blankj.com
+ *     time  : 2020/04/12
+ *     desc  : utils about number
+ * </pre>
+ */
+public final class NumberUtils {
+
+    private static final ThreadLocal<DecimalFormat> DF_THREAD_LOCAL = new ThreadLocal<DecimalFormat>() {
+        @Override
+        protected DecimalFormat initialValue() {
+            return (DecimalFormat) NumberFormat.getInstance();
+        }
+    };
+
+    public static DecimalFormat getSafeDecimalFormat() {
+        return DF_THREAD_LOCAL.get();
+    }
+
+    private NumberUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @return the format value
+     */
+    public static String format(float value, int fractionDigits) {
+        return format(value, false, 1, fractionDigits, true);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp       True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(float value, int fractionDigits, boolean isHalfUp) {
+        return format(value, false, 1, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value            The value.
+     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.
+     * @param fractionDigits   The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp         True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(float value, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {
+        return format(value, false, minIntegerDigits, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param isGrouping     True to set grouping will be used in this format, false otherwise.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @return the format value
+     */
+    public static String format(float value, boolean isGrouping, int fractionDigits) {
+        return format(value, isGrouping, 1, fractionDigits, true);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param isGrouping     True to set grouping will be used in this format, false otherwise.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp       True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(float value, boolean isGrouping, int fractionDigits, boolean isHalfUp) {
+        return format(value, isGrouping, 1, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value            The value.
+     * @param isGrouping       True to set grouping will be used in this format, false otherwise.
+     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.
+     * @param fractionDigits   The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp         True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(float value, boolean isGrouping, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {
+        return format(float2Double(value), isGrouping, minIntegerDigits, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @return the format value
+     */
+    public static String format(double value, int fractionDigits) {
+        return format(value, false, 1, fractionDigits, true);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp       True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(double value, int fractionDigits, boolean isHalfUp) {
+        return format(value, false, 1, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value            The value.
+     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.
+     * @param fractionDigits   The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp         True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(double value, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {
+        return format(value, false, minIntegerDigits, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param isGrouping     True to set grouping will be used in this format, false otherwise.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @return the format value
+     */
+    public static String format(double value, boolean isGrouping, int fractionDigits) {
+        return format(value, isGrouping, 1, fractionDigits, true);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value          The value.
+     * @param isGrouping     True to set grouping will be used in this format, false otherwise.
+     * @param fractionDigits The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp       True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(double value, boolean isGrouping, int fractionDigits, boolean isHalfUp) {
+        return format(value, isGrouping, 1, fractionDigits, isHalfUp);
+    }
+
+    /**
+     * Format the value.
+     *
+     * @param value            The value.
+     * @param isGrouping       True to set grouping will be used in this format, false otherwise.
+     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.
+     * @param fractionDigits   The number of digits allowed in the fraction portion of value.
+     * @param isHalfUp         True to rounded towards the nearest neighbor.
+     * @return the format value
+     */
+    public static String format(double value, boolean isGrouping, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {
+        DecimalFormat nf = getSafeDecimalFormat();
+        nf.setGroupingUsed(isGrouping);
+        nf.setRoundingMode(isHalfUp ? RoundingMode.HALF_UP : RoundingMode.DOWN);
+        nf.setMinimumIntegerDigits(minIntegerDigits);
+        nf.setMinimumFractionDigits(fractionDigits);
+        nf.setMaximumFractionDigits(fractionDigits);
+        return nf.format(value);
+    }
+
+    /**
+     * Float to double.
+     *
+     * @param value The value.
+     * @return the number of double
+     */
+    public static double float2Double(float value) {
+        return new BigDecimal(String.valueOf(value)).doubleValue();
+    }
+}

+ 279 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ObjectUtils.java

@@ -0,0 +1,279 @@
+package com.cooleshow.base.utils;
+
+import android.os.Build;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.collection.LongSparseArray;
+import androidx.collection.SimpleArrayMap;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/12/24
+ *     desc  : utils about object
+ * </pre>
+ */
+public final class ObjectUtils {
+
+    private ObjectUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether object is empty.
+     *
+     * @param obj The object.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isEmpty(final Object obj) {
+        if (obj == null) {
+            return true;
+        }
+        if (obj.getClass().isArray() && Array.getLength(obj) == 0) {
+            return true;
+        }
+        if (obj instanceof CharSequence && obj.toString().length() == 0) {
+            return true;
+        }
+        if (obj instanceof Collection && ((Collection) obj).isEmpty()) {
+            return true;
+        }
+        if (obj instanceof Map && ((Map) obj).isEmpty()) {
+            return true;
+        }
+        if (obj instanceof SimpleArrayMap && ((SimpleArrayMap) obj).isEmpty()) {
+            return true;
+        }
+        if (obj instanceof SparseArray && ((SparseArray) obj).size() == 0) {
+            return true;
+        }
+        if (obj instanceof SparseBooleanArray && ((SparseBooleanArray) obj).size() == 0) {
+            return true;
+        }
+        if (obj instanceof SparseIntArray && ((SparseIntArray) obj).size() == 0) {
+            return true;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            if (obj instanceof SparseLongArray && ((SparseLongArray) obj).size() == 0) {
+                return true;
+            }
+        }
+        if (obj instanceof LongSparseArray && ((LongSparseArray) obj).size() == 0) {
+            return true;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            if (obj instanceof android.util.LongSparseArray
+                    && ((android.util.LongSparseArray) obj).size() == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isEmpty(final CharSequence obj) {
+        return obj == null || obj.toString().length() == 0;
+    }
+
+    public static boolean isEmpty(final Collection obj) {
+        return obj == null || obj.isEmpty();
+    }
+
+    public static boolean isEmpty(final Map obj) {
+        return obj == null || obj.isEmpty();
+    }
+
+    public static boolean isEmpty(final SimpleArrayMap obj) {
+        return obj == null || obj.isEmpty();
+    }
+
+    public static boolean isEmpty(final SparseArray obj) {
+        return obj == null || obj.size() == 0;
+    }
+
+    public static boolean isEmpty(final SparseBooleanArray obj) {
+        return obj == null || obj.size() == 0;
+    }
+
+    public static boolean isEmpty(final SparseIntArray obj) {
+        return obj == null || obj.size() == 0;
+    }
+
+    public static boolean isEmpty(final LongSparseArray obj) {
+        return obj == null || obj.size() == 0;
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+    public static boolean isEmpty(final SparseLongArray obj) {
+        return obj == null || obj.size() == 0;
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public static boolean isEmpty(final android.util.LongSparseArray obj) {
+        return obj == null || obj.size() == 0;
+    }
+
+    /**
+     * Return whether object is not empty.
+     *
+     * @param obj The object.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNotEmpty(final Object obj) {
+        return !isEmpty(obj);
+    }
+
+
+    public static boolean isNotEmpty(final CharSequence obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final Collection obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final Map obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final SimpleArrayMap obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final SparseArray obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final SparseBooleanArray obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final SparseIntArray obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isNotEmpty(final LongSparseArray obj) {
+        return !isEmpty(obj);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+    public static boolean isNotEmpty(final SparseLongArray obj) {
+        return !isEmpty(obj);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public static boolean isNotEmpty(final android.util.LongSparseArray obj) {
+        return !isEmpty(obj);
+    }
+
+    /**
+     * Return whether object1 is equals to object2.
+     *
+     * @param o1 The first object.
+     * @param o2 The second object.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean equals(final Object o1, final Object o2) {
+        return o1 == o2 || (o1 != null && o1.equals(o2));
+    }
+
+    /**
+     * Returns 0 if the arguments are identical and {@code
+     * c.compare(a, b)} otherwise.
+     * Consequently, if both arguments are {@code null} 0
+     * is returned.
+     */
+    public static <T> int compare(T a, T b, @NonNull Comparator<? super T> c) {
+        return (a == b) ? 0 : c.compare(a, b);
+    }
+
+    /**
+     * Checks that the specified object reference is not {@code null}.
+     */
+    public static <T> T requireNonNull(T obj) {
+        if (obj == null) throw new NullPointerException();
+        return obj;
+    }
+
+    /**
+     * Checks that the specified object reference is not {@code null} and
+     * throws a customized {@link NullPointerException} if it is.
+     */
+    public static <T> T requireNonNull(T obj, String ifNullTip) {
+        if (obj == null) throw new NullPointerException(ifNullTip);
+        return obj;
+    }
+
+    /**
+     * Require the objects are not null.
+     *
+     * @param objects The object.
+     * @throws NullPointerException if any object is null in objects
+     */
+    public static void requireNonNulls(final Object... objects) {
+        if (objects == null) throw new NullPointerException();
+        for (Object object : objects) {
+            if (object == null) throw new NullPointerException();
+        }
+    }
+
+    /**
+     * Return the nonnull object or default object.
+     *
+     * @param object        The object.
+     * @param defaultObject The default object to use with the object is null.
+     * @param <T>           The value type.
+     * @return the nonnull object or default object
+     */
+    public static <T> T getOrDefault(final T object, final T defaultObject) {
+        if (object == null) {
+            return defaultObject;
+        }
+        return object;
+    }
+
+    /**
+     * Returns the result of calling {@code toString} for a non-{@code
+     * null} argument and {@code "null"} for a {@code null} argument.
+     */
+    public static String toString(Object obj) {
+        return String.valueOf(obj);
+    }
+
+    /**
+     * Returns the result of calling {@code toString} on the first
+     * argument if the first argument is not {@code null} and returns
+     * the second argument otherwise.
+     */
+    public static String toString(Object o, String nullDefault) {
+        return (o != null) ? o.toString() : nullDefault;
+    }
+
+    /**
+     * Return the hash code of object.
+     *
+     * @param o The object.
+     * @return the hash code of object
+     */
+    public static int hashCode(final Object o) {
+        return o != null ? o.hashCode() : 0;
+    }
+
+    /**
+     * Return the hash code of objects.
+     */
+    public static int hashCodes(Object... values) {
+        return Arrays.hashCode(values);
+    }
+}

+ 471 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/PathUtils.java

@@ -0,0 +1,471 @@
+package com.cooleshow.base.utils;
+
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import java.io.File;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/04/15
+ *     desc  : utils about path
+ * </pre>
+ */
+public final class PathUtils {
+
+    private static final char SEP = File.separatorChar;
+
+    private PathUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Join the path.
+     *
+     * @param parent The parent of path.
+     * @param child  The child path.
+     * @return the path
+     */
+    public static String join(String parent, String child) {
+        if (TextUtils.isEmpty(child)) return parent;
+        if (parent == null) {
+            parent = "";
+        }
+        int len = parent.length();
+        String legalSegment = getLegalSegment(child);
+        String newPath;
+        if (len == 0) {
+            newPath = SEP + legalSegment;
+        } else if (parent.charAt(len - 1) == SEP) {
+            newPath = parent + legalSegment;
+        } else {
+            newPath = parent + SEP + legalSegment;
+        }
+        return newPath;
+    }
+
+    private static String getLegalSegment(String segment) {
+        int st = -1, end = -1;
+        char[] charArray = segment.toCharArray();
+        for (int i = 0; i < charArray.length; i++) {
+            char c = charArray[i];
+            if (c != SEP) {
+                if (st == -1) {
+                    st = i;
+                }
+                end = i;
+            }
+        }
+        if (st >= 0 && end >= st) {
+            return segment.substring(st, end + 1);
+        }
+        throw new IllegalArgumentException("segment of <" + segment + "> is illegal");
+    }
+
+    /**
+     * Return the path of /system.
+     *
+     * @return the path of /system
+     */
+    public static String getRootPath() {
+        return getAbsolutePath(Environment.getRootDirectory());
+    }
+
+    /**
+     * Return the path of /data.
+     *
+     * @return the path of /data
+     */
+    public static String getDataPath() {
+        return getAbsolutePath(Environment.getDataDirectory());
+    }
+
+    /**
+     * Return the path of /cache.
+     *
+     * @return the path of /cache
+     */
+    public static String getDownloadCachePath() {
+        return getAbsolutePath(Environment.getDownloadCacheDirectory());
+    }
+
+    /**
+     * Return the path of /data/data/package.
+     *
+     * @return the path of /data/data/package
+     */
+    public static String getInternalAppDataPath() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            return Utils.getApp().getApplicationInfo().dataDir;
+        }
+        return getAbsolutePath(Utils.getApp().getDataDir());
+    }
+
+    /**
+     * Return the path of /data/data/package/code_cache.
+     *
+     * @return the path of /data/data/package/code_cache
+     */
+    public static String getInternalAppCodeCacheDir() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            return Utils.getApp().getApplicationInfo().dataDir + "/code_cache";
+        }
+        return getAbsolutePath(Utils.getApp().getCodeCacheDir());
+    }
+
+    /**
+     * Return the path of /data/data/package/cache.
+     *
+     * @return the path of /data/data/package/cache
+     */
+    public static String getInternalAppCachePath() {
+        return getAbsolutePath(Utils.getApp().getCacheDir());
+    }
+
+    /**
+     * Return the path of /data/data/package/databases.
+     *
+     * @return the path of /data/data/package/databases
+     */
+    public static String getInternalAppDbsPath() {
+        return Utils.getApp().getApplicationInfo().dataDir + "/databases";
+    }
+
+    /**
+     * Return the path of /data/data/package/databases/name.
+     *
+     * @param name The name of database.
+     * @return the path of /data/data/package/databases/name
+     */
+    public static String getInternalAppDbPath(String name) {
+        return getAbsolutePath(Utils.getApp().getDatabasePath(name));
+    }
+
+    /**
+     * Return the path of /data/data/package/files.
+     *
+     * @return the path of /data/data/package/files
+     */
+    public static String getInternalAppFilesPath() {
+        return getAbsolutePath(Utils.getApp().getFilesDir());
+    }
+
+    /**
+     * Return the path of /data/data/package/shared_prefs.
+     *
+     * @return the path of /data/data/package/shared_prefs
+     */
+    public static String getInternalAppSpPath() {
+        return Utils.getApp().getApplicationInfo().dataDir + "/shared_prefs";
+    }
+
+    /**
+     * Return the path of /data/data/package/no_backup.
+     *
+     * @return the path of /data/data/package/no_backup
+     */
+    public static String getInternalAppNoBackupFilesPath() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            return Utils.getApp().getApplicationInfo().dataDir + "/no_backup";
+        }
+        return getAbsolutePath(Utils.getApp().getNoBackupFilesDir());
+    }
+
+    /**
+     * Return the path of /storage/emulated/0.
+     *
+     * @return the path of /storage/emulated/0
+     */
+    public static String getExternalStoragePath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStorageDirectory());
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Music.
+     *
+     * @return the path of /storage/emulated/0/Music
+     */
+    public static String getExternalMusicPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Podcasts.
+     *
+     * @return the path of /storage/emulated/0/Podcasts
+     */
+    public static String getExternalPodcastsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Ringtones.
+     *
+     * @return the path of /storage/emulated/0/Ringtones
+     */
+    public static String getExternalRingtonesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Alarms.
+     *
+     * @return the path of /storage/emulated/0/Alarms
+     */
+    public static String getExternalAlarmsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Notifications.
+     *
+     * @return the path of /storage/emulated/0/Notifications
+     */
+    public static String getExternalNotificationsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Pictures.
+     *
+     * @return the path of /storage/emulated/0/Pictures
+     */
+    public static String getExternalPicturesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Movies.
+     *
+     * @return the path of /storage/emulated/0/Movies
+     */
+    public static String getExternalMoviesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Download.
+     *
+     * @return the path of /storage/emulated/0/Download
+     */
+    public static String getExternalDownloadsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/DCIM.
+     *
+     * @return the path of /storage/emulated/0/DCIM
+     */
+    public static String getExternalDcimPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Documents.
+     *
+     * @return the path of /storage/emulated/0/Documents
+     */
+    public static String getExternalDocumentsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+            return getAbsolutePath(Environment.getExternalStorageDirectory()) + "/Documents";
+        }
+        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package
+     */
+    public static String getExternalAppDataPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        File externalCacheDir = Utils.getApp().getExternalCacheDir();
+        if (externalCacheDir == null) return "";
+        return getAbsolutePath(externalCacheDir.getParentFile());
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/cache.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/cache
+     */
+    public static String getExternalAppCachePath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalCacheDir());
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files
+     */
+    public static String getExternalAppFilesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(null));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Music.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Music
+     */
+    public static String getExternalAppMusicPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_MUSIC));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Podcasts.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Podcasts
+     */
+    public static String getExternalAppPodcastsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_PODCASTS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Ringtones.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Ringtones
+     */
+    public static String getExternalAppRingtonesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_RINGTONES));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Alarms.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Alarms
+     */
+    public static String getExternalAppAlarmsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_ALARMS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Notifications.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Notifications
+     */
+    public static String getExternalAppNotificationsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Pictures.
+     *
+     * @return path of /storage/emulated/0/Android/data/package/files/Pictures
+     */
+    public static String getExternalAppPicturesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_PICTURES));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Movies.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Movies
+     */
+    public static String getExternalAppMoviesPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_MOVIES));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Download.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Download
+     */
+    public static String getExternalAppDownloadPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/DCIM.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/DCIM
+     */
+    public static String getExternalAppDcimPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DCIM));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/data/package/files/Documents.
+     *
+     * @return the path of /storage/emulated/0/Android/data/package/files/Documents
+     */
+    public static String getExternalAppDocumentsPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+            return getAbsolutePath(Utils.getApp().getExternalFilesDir(null)) + "/Documents";
+        }
+        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS));
+    }
+
+    /**
+     * Return the path of /storage/emulated/0/Android/obb/package.
+     *
+     * @return the path of /storage/emulated/0/Android/obb/package
+     */
+    public static String getExternalAppObbPath() {
+        if (!UtilsBridge.isSDCardEnableByEnvironment()) return "";
+        return getAbsolutePath(Utils.getApp().getObbDir());
+    }
+
+    public static String getRootPathExternalFirst() {
+        String rootPath = getExternalStoragePath();
+        if (TextUtils.isEmpty(rootPath)) {
+            rootPath = getRootPath();
+        }
+        return rootPath;
+    }
+
+    public static String getAppDataPathExternalFirst() {
+        String appDataPath = getExternalAppDataPath();
+        if (TextUtils.isEmpty(appDataPath)) {
+            appDataPath = getInternalAppDataPath();
+        }
+        return appDataPath;
+    }
+
+    public static String getFilesPathExternalFirst() {
+        String filePath = getExternalAppFilesPath();
+        if (TextUtils.isEmpty(filePath)) {
+            filePath = getInternalAppFilesPath();
+        }
+        return filePath;
+    }
+
+    public static String getCachePathExternalFirst() {
+        String appPath = getExternalAppCachePath();
+        if (TextUtils.isEmpty(appPath)) {
+            appPath = getInternalAppCachePath();
+        }
+        return appPath;
+    }
+
+    private static String getAbsolutePath(final File file) {
+        if (file == null) return "";
+        return file.getAbsolutePath();
+    }
+}

+ 599 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/PermissionUtils.java

@@ -0,0 +1,599 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import com.cooleshow.base.constanst.PermissionConstants;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
+
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/12/29
+ *     desc  : utils about permission
+ * </pre>
+ */
+public final class PermissionUtils {
+
+    private static PermissionUtils sInstance;
+
+    private String[]            mPermissionsParam;
+    private OnExplainListener   mOnExplainListener;
+    private OnRationaleListener mOnRationaleListener;
+    private SingleCallback      mSingleCallback;
+    private SimpleCallback      mSimpleCallback;
+    private FullCallback        mFullCallback;
+    private ThemeCallback       mThemeCallback;
+    private Set<String>         mPermissions;
+    private List<String>        mPermissionsRequest;
+    private List<String>        mPermissionsGranted;
+    private List<String>        mPermissionsDenied;
+    private List<String>        mPermissionsDeniedForever;
+
+    private static SimpleCallback sSimpleCallback4WriteSettings;
+    private static SimpleCallback sSimpleCallback4DrawOverlays;
+
+    /**
+     * Return the permissions used in application.
+     *
+     * @return the permissions used in application
+     */
+    public static List<String> getPermissions() {
+        return getPermissions(Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the permissions used in application.
+     *
+     * @param packageName The name of the package.
+     * @return the permissions used in application
+     */
+    public static List<String> getPermissions(final String packageName) {
+        PackageManager pm = Utils.getApp().getPackageManager();
+        try {
+            String[] permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS).requestedPermissions;
+            if (permissions == null) return Collections.emptyList();
+            return Arrays.asList(permissions);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Return whether <em>you</em> have been granted the permissions.
+     *
+     * @param permissions The permissions.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isGranted(final String... permissions) {
+        Pair<List<String>, List<String>> requestAndDeniedPermissions = getRequestAndDeniedPermissions(permissions);
+        List<String> deniedPermissions = requestAndDeniedPermissions.second;
+        if (!deniedPermissions.isEmpty()) {
+            return false;
+        }
+        List<String> requestPermissions = requestAndDeniedPermissions.first;
+        for (String permission : requestPermissions) {
+            if (!isGranted(permission)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static Pair<List<String>, List<String>> getRequestAndDeniedPermissions(final String... permissionsParam) {
+        List<String> requestPermissions = new ArrayList<>();
+        List<String> deniedPermissions = new ArrayList<>();
+        List<String> appPermissions = getPermissions();
+        for (String param : permissionsParam) {
+            boolean isIncludeInManifest = false;
+            String[] permissions = PermissionConstants.getPermissions(param);
+            for (String permission : permissions) {
+                if (appPermissions.contains(permission)) {
+                    requestPermissions.add(permission);
+                    isIncludeInManifest = true;
+                }
+            }
+            if (!isIncludeInManifest) {
+                deniedPermissions.add(param);
+                Log.e("PermissionUtils", "U should add the permission of " + param + " in manifest.");
+            }
+        }
+        return Pair.create(requestPermissions, deniedPermissions);
+    }
+
+    private static boolean isGranted(final String permission) {
+        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+                || PackageManager.PERMISSION_GRANTED
+                == ContextCompat.checkSelfPermission(Utils.getApp(), permission);
+    }
+
+    /**
+     * Return whether the app can modify system settings.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    public static boolean isGrantedWriteSettings() {
+        return Settings.System.canWrite(Utils.getApp());
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    public static void requestWriteSettings(final SimpleCallback callback) {
+        if (isGrantedWriteSettings()) {
+            if (callback != null) callback.onGranted();
+            return;
+        }
+        sSimpleCallback4WriteSettings = callback;
+        PermissionActivityImpl.start(PermissionActivityImpl.TYPE_WRITE_SETTINGS);
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static void startWriteSettingsActivity(final Activity activity, final int requestCode) {
+        Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
+        intent.setData(Uri.parse("package:" + Utils.getApp().getPackageName()));
+        if (!UtilsBridge.isIntentAvailable(intent)) {
+            launchAppDetailsSettings();
+            return;
+        }
+        activity.startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Return whether the app can draw on top of other apps.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    public static boolean isGrantedDrawOverlays() {
+        return Settings.canDrawOverlays(Utils.getApp());
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    public static void requestDrawOverlays(final SimpleCallback callback) {
+        if (isGrantedDrawOverlays()) {
+            if (callback != null) callback.onGranted();
+            return;
+        }
+        sSimpleCallback4DrawOverlays = callback;
+        PermissionActivityImpl.start(PermissionActivityImpl.TYPE_DRAW_OVERLAYS);
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static void startOverlayPermissionActivity(final Activity activity, final int requestCode) {
+        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+        intent.setData(Uri.parse("package:" + Utils.getApp().getPackageName()));
+        if (!UtilsBridge.isIntentAvailable(intent)) {
+            launchAppDetailsSettings();
+            return;
+        }
+        activity.startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Launch the application's details settings.
+     */
+    public static void launchAppDetailsSettings() {
+        Intent intent = UtilsBridge.getLaunchAppDetailsSettingsIntent(Utils.getApp().getPackageName(), true);
+        if (!UtilsBridge.isIntentAvailable(intent)) return;
+        Utils.getApp().startActivity(intent);
+    }
+
+    /**
+     * Set the permissions.
+     *
+     * @param permissions The permissions.
+     * @return the single {@link PermissionUtils} instance
+     */
+    public static PermissionUtils permissionGroup(@PermissionConstants.PermissionGroup final String... permissions) {
+        return permission(permissions);
+    }
+
+    /**
+     * Set the permissions.
+     *
+     * @param permissions The permissions.
+     * @return the single {@link PermissionUtils} instance
+     */
+    public static PermissionUtils permission(final String... permissions) {
+        return new PermissionUtils(permissions);
+    }
+
+    private PermissionUtils(final String... permissions) {
+        mPermissionsParam = permissions;
+        sInstance = this;
+    }
+
+    /**
+     * Set explain listener.
+     *
+     * @param listener The explain listener.
+     * @return the single {@link PermissionUtils} instance
+     */
+    public PermissionUtils explain(final OnExplainListener listener) {
+        mOnExplainListener = listener;
+        return this;
+    }
+
+    /**
+     * Set rationale listener.
+     *
+     * @param listener The rationale listener.
+     * @return the single {@link PermissionUtils} instance
+     */
+    public PermissionUtils rationale(final OnRationaleListener listener) {
+        mOnRationaleListener = listener;
+        return this;
+    }
+
+    /**
+     * Set the simple call back.
+     *
+     * @param callback the single call back
+     * @return the single {@link PermissionUtils} instance
+     */
+    public PermissionUtils callback(final SingleCallback callback) {
+        mSingleCallback = callback;
+        return this;
+    }
+
+    /**
+     * Set the simple call back.
+     *
+     * @param callback the simple call back
+     * @return the single {@link PermissionUtils} instance
+     */
+    public PermissionUtils callback(final SimpleCallback callback) {
+        mSimpleCallback = callback;
+        return this;
+    }
+
+    /**
+     * Set the full call back.
+     *
+     * @param callback the full call back
+     * @return the single {@link PermissionUtils} instance
+     */
+    public PermissionUtils callback(final FullCallback callback) {
+        mFullCallback = callback;
+        return this;
+    }
+
+    /**
+     * Set the theme callback.
+     *
+     * @param callback The theme callback.
+     * @return the single {@link PermissionUtils} instance
+     */
+    public PermissionUtils theme(final ThemeCallback callback) {
+        mThemeCallback = callback;
+        return this;
+    }
+
+    /**
+     * Start request.
+     */
+    public void request() {
+        if (mPermissionsParam == null || mPermissionsParam.length <= 0) {
+            Log.w("PermissionUtils", "No permissions to request.");
+            return;
+        }
+
+        mPermissions = new LinkedHashSet<>();
+        mPermissionsRequest = new ArrayList<>();
+        mPermissionsGranted = new ArrayList<>();
+        mPermissionsDenied = new ArrayList<>();
+        mPermissionsDeniedForever = new ArrayList<>();
+
+        Pair<List<String>, List<String>> requestAndDeniedPermissions = getRequestAndDeniedPermissions(mPermissionsParam);
+        mPermissions.addAll(requestAndDeniedPermissions.first);
+        mPermissionsDenied.addAll(requestAndDeniedPermissions.second);
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            mPermissionsGranted.addAll(mPermissions);
+            requestCallback();
+        } else {
+            for (String permission : mPermissions) {
+                if (isGranted(permission)) {
+                    mPermissionsGranted.add(permission);
+                } else {
+                    mPermissionsRequest.add(permission);
+                }
+            }
+            if (mPermissionsRequest.isEmpty()) {
+                requestCallback();
+            } else {
+                startPermissionActivity();
+            }
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    private void startPermissionActivity() {
+        PermissionActivityImpl.start(PermissionActivityImpl.TYPE_RUNTIME);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    private boolean shouldRationale(final UtilsTransActivity activity,
+                                    final Runnable againRunnable) {
+        boolean isRationale = false;
+        if (mOnRationaleListener != null) {
+            for (String permission : mPermissionsRequest) {
+                if (activity.shouldShowRequestPermissionRationale(permission)) {
+                    rationalInner(activity, againRunnable);
+                    isRationale = true;
+                    break;
+                }
+            }
+            mOnRationaleListener = null;
+        }
+        return isRationale;
+    }
+
+    private void rationalInner(final UtilsTransActivity activity, final Runnable againRunnable) {
+        getPermissionsStatus(activity);
+        mOnRationaleListener.rationale(activity, new OnRationaleListener.ShouldRequest() {
+            @Override
+            public void again(boolean again) {
+                if (again) {
+                    mPermissionsDenied = new ArrayList<>();
+                    mPermissionsDeniedForever = new ArrayList<>();
+                    againRunnable.run();
+                } else {
+                    activity.finish();
+                    requestCallback();
+                }
+            }
+        });
+    }
+
+    private void getPermissionsStatus(final Activity activity) {
+        for (String permission : mPermissionsRequest) {
+            if (isGranted(permission)) {
+                mPermissionsGranted.add(permission);
+            } else {
+                mPermissionsDenied.add(permission);
+                if (!activity.shouldShowRequestPermissionRationale(permission)) {
+                    mPermissionsDeniedForever.add(permission);
+                }
+            }
+        }
+    }
+
+    private void requestCallback() {
+        if (mSingleCallback != null) {
+            mSingleCallback.callback(mPermissionsDenied.isEmpty(),
+                    mPermissionsGranted, mPermissionsDeniedForever, mPermissionsDenied);
+            mSingleCallback = null;
+        }
+        if (mSimpleCallback != null) {
+            if (mPermissionsDenied.isEmpty()) {
+                mSimpleCallback.onGranted();
+            } else {
+                mSimpleCallback.onDenied();
+            }
+            mSimpleCallback = null;
+        }
+        if (mFullCallback != null) {
+            if (mPermissionsRequest.size() == 0
+                    || mPermissionsGranted.size() > 0) {
+                mFullCallback.onGranted(mPermissionsGranted);
+            }
+            if (!mPermissionsDenied.isEmpty()) {
+                mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied);
+            }
+            mFullCallback = null;
+        }
+        mOnRationaleListener = null;
+        mThemeCallback = null;
+    }
+
+    private void onRequestPermissionsResult(final Activity activity) {
+        getPermissionsStatus(activity);
+        requestCallback();
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    static final class PermissionActivityImpl extends UtilsTransActivity.TransActivityDelegate {
+
+        private static final String TYPE                = "TYPE";
+        private static final int    TYPE_RUNTIME        = 0x01;
+        private static final int    TYPE_WRITE_SETTINGS = 0x02;
+        private static final int    TYPE_DRAW_OVERLAYS  = 0x03;
+
+        private static int currentRequestCode = -1;
+
+        private static PermissionActivityImpl INSTANCE = new PermissionActivityImpl();
+
+        public static void start(final int type) {
+            UtilsTransActivity.start(new Utils.Consumer<Intent>() {
+                @Override
+                public void accept(Intent data) {
+                    data.putExtra(TYPE, type);
+                }
+            }, INSTANCE);
+        }
+
+        @Override
+        public void onCreated(@NonNull final UtilsTransActivity activity, @Nullable Bundle savedInstanceState) {
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
+            int type = activity.getIntent().getIntExtra(TYPE, -1);
+            if (type == TYPE_RUNTIME) {
+                if (sInstance == null) {
+                    Log.e("PermissionUtils", "sInstance is null.");
+                    activity.finish();
+                    return;
+                }
+                if (sInstance.mPermissionsRequest == null) {
+                    Log.e("PermissionUtils", "mPermissionsRequest is null.");
+                    activity.finish();
+                    return;
+                }
+                if (sInstance.mPermissionsRequest.size() <= 0) {
+                    Log.e("PermissionUtils", "mPermissionsRequest's size is no more than 0.");
+                    activity.finish();
+                    return;
+                }
+                if (sInstance.mThemeCallback != null) {
+                    sInstance.mThemeCallback.onActivityCreate(activity);
+                }
+                if (sInstance.mOnExplainListener != null) {
+                    sInstance.mOnExplainListener.explain(activity, sInstance.mPermissionsRequest, new OnExplainListener.ShouldRequest() {
+                        @Override
+                        public void start(boolean start) {
+                            if (!start) {
+                                activity.finish();
+                            } else {
+                                requestPermissions(activity);
+                            }
+                        }
+                    });
+                    sInstance.mOnExplainListener = null;
+                    return;
+                }
+                requestPermissions(activity);
+            } else if (type == TYPE_WRITE_SETTINGS) {
+                currentRequestCode = TYPE_WRITE_SETTINGS;
+                startWriteSettingsActivity(activity, TYPE_WRITE_SETTINGS);
+            } else if (type == TYPE_DRAW_OVERLAYS) {
+                currentRequestCode = TYPE_DRAW_OVERLAYS;
+                startOverlayPermissionActivity(activity, TYPE_DRAW_OVERLAYS);
+            } else {
+                activity.finish();
+                Log.e("PermissionUtils", "type is wrong.");
+            }
+        }
+
+        private void requestPermissions(final UtilsTransActivity activity) {
+            if (sInstance.shouldRationale(activity, new Runnable() {
+                @Override
+                public void run() {
+                    activity.requestPermissions(sInstance.mPermissionsRequest.toArray(new String[0]), 1);
+                }
+            })) {
+                return;
+            }
+            activity.requestPermissions(sInstance.mPermissionsRequest.toArray(new String[0]), 1);
+        }
+
+        @Override
+        public void onRequestPermissionsResult(@NonNull UtilsTransActivity activity,
+                                               int requestCode,
+                                               @NonNull String[] permissions,
+                                               @NonNull int[] grantResults) {
+            activity.finish();
+            if (sInstance != null && sInstance.mPermissionsRequest != null) {
+                sInstance.onRequestPermissionsResult(activity);
+            }
+        }
+
+
+        @Override
+        public boolean dispatchTouchEvent(@NonNull UtilsTransActivity activity, MotionEvent ev) {
+            activity.finish();
+            return true;
+        }
+
+        @Override
+        public void onDestroy(@NonNull final UtilsTransActivity activity) {
+            if (currentRequestCode != -1) {
+                checkRequestCallback(currentRequestCode);
+                currentRequestCode = -1;
+            }
+            super.onDestroy(activity);
+        }
+
+        @Override
+        public void onActivityResult(@NonNull UtilsTransActivity activity, int requestCode, int resultCode, Intent data) {
+            activity.finish();
+        }
+
+        private void checkRequestCallback(int requestCode) {
+            if (requestCode == TYPE_WRITE_SETTINGS) {
+                if (sSimpleCallback4WriteSettings == null) return;
+                if (isGrantedWriteSettings()) {
+                    sSimpleCallback4WriteSettings.onGranted();
+                } else {
+                    sSimpleCallback4WriteSettings.onDenied();
+                }
+                sSimpleCallback4WriteSettings = null;
+            } else if (requestCode == TYPE_DRAW_OVERLAYS) {
+                if (sSimpleCallback4DrawOverlays == null) return;
+                if (isGrantedDrawOverlays()) {
+                    sSimpleCallback4DrawOverlays.onGranted();
+                } else {
+                    sSimpleCallback4DrawOverlays.onDenied();
+                }
+                sSimpleCallback4DrawOverlays = null;
+            }
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // interface
+    ///////////////////////////////////////////////////////////////////////////
+
+    public interface OnExplainListener {
+
+        void explain(@NonNull UtilsTransActivity activity, @NonNull List<String> denied, @NonNull ShouldRequest shouldRequest);
+
+        interface ShouldRequest {
+            void start(boolean start);
+        }
+    }
+
+    public interface OnRationaleListener {
+
+        void rationale(@NonNull UtilsTransActivity activity, @NonNull ShouldRequest shouldRequest);
+
+        interface ShouldRequest {
+            void again(boolean again);
+        }
+    }
+
+    public interface SingleCallback {
+        void callback(boolean isAllGranted, @NonNull List<String> granted,
+                      @NonNull List<String> deniedForever, @NonNull List<String> denied);
+    }
+
+
+    public interface SimpleCallback {
+        void onGranted();
+
+        void onDenied();
+    }
+
+    public interface FullCallback {
+        void onGranted(@NonNull List<String> granted);
+
+        void onDenied(@NonNull List<String> deniedForever, @NonNull List<String> denied);
+    }
+
+    public interface ThemeCallback {
+        void onActivityCreate(@NonNull Activity activity);
+    }
+}

+ 321 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/PhoneUtils.java

@@ -0,0 +1,321 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresPermission;
+
+import static android.Manifest.permission.CALL_PHONE;
+import static android.Manifest.permission.READ_PHONE_STATE;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about phone
+ * </pre>
+ */
+public final class PhoneUtils {
+
+    private PhoneUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether the device is phone.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPhone() {
+        TelephonyManager tm = getTelephonyManager();
+        return tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
+    }
+
+    /**
+     * Return the unique device id.
+     * <p>If the version of SDK is greater than 28, it will return an empty string.</p>
+     * <p>Must hold {@code <uses-permission android:name="android.permission.READ_PHONE_STATE" />}</p>
+     *
+     * @return the unique device id
+     */
+    @SuppressLint("HardwareIds")
+    @RequiresPermission(READ_PHONE_STATE)
+    public static String getDeviceId() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            return "";
+        }
+        TelephonyManager tm = getTelephonyManager();
+        String deviceId = tm.getDeviceId();
+        if (!TextUtils.isEmpty(deviceId)) return deviceId;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            String imei = tm.getImei();
+            if (!TextUtils.isEmpty(imei)) return imei;
+            String meid = tm.getMeid();
+            return TextUtils.isEmpty(meid) ? "" : meid;
+        }
+        return "";
+    }
+
+    /**
+     * Return the serial of device.
+     *
+     * @return the serial of device
+     */
+    @SuppressLint("HardwareIds")
+    @RequiresPermission(READ_PHONE_STATE)
+    public static String getSerial() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            try {
+                return Build.getSerial();
+            } catch (SecurityException e) {
+                e.printStackTrace();
+                return "";
+            }
+        }
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.getSerial() : Build.SERIAL;
+    }
+
+    /**
+     * Return the IMEI.
+     * <p>If the version of SDK is greater than 28, it will return an empty string.</p>
+     * <p>Must hold {@code <uses-permission android:name="android.permission.READ_PHONE_STATE" />}</p>
+     *
+     * @return the IMEI
+     */
+    @RequiresPermission(READ_PHONE_STATE)
+    public static String getIMEI() {
+        return getImeiOrMeid(true);
+    }
+
+    /**
+     * Return the MEID.
+     * <p>If the version of SDK is greater than 28, it will return an empty string.</p>
+     * <p>Must hold {@code <uses-permission android:name="android.permission.READ_PHONE_STATE" />}</p>
+     *
+     * @return the MEID
+     */
+    @RequiresPermission(READ_PHONE_STATE)
+    public static String getMEID() {
+        return getImeiOrMeid(false);
+    }
+
+    @SuppressLint("HardwareIds")
+    @RequiresPermission(READ_PHONE_STATE)
+    public static String getImeiOrMeid(boolean isImei) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            return "";
+        }
+        TelephonyManager tm = getTelephonyManager();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            if (isImei) {
+                return getMinOne(tm.getImei(0), tm.getImei(1));
+            } else {
+                return getMinOne(tm.getMeid(0), tm.getMeid(1));
+            }
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            String ids = getSystemPropertyByReflect(isImei ? "ril.gsm.imei" : "ril.cdma.meid");
+            if (!TextUtils.isEmpty(ids)) {
+                String[] idArr = ids.split(",");
+                if (idArr.length == 2) {
+                    return getMinOne(idArr[0], idArr[1]);
+                } else {
+                    return idArr[0];
+                }
+            }
+
+            String id0 = tm.getDeviceId();
+            String id1 = "";
+            try {
+                Method method = tm.getClass().getMethod("getDeviceId", int.class);
+                id1 = (String) method.invoke(tm,
+                        isImei ? TelephonyManager.PHONE_TYPE_GSM
+                                : TelephonyManager.PHONE_TYPE_CDMA);
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+            if (isImei) {
+                if (id0 != null && id0.length() < 15) {
+                    id0 = "";
+                }
+                if (id1 != null && id1.length() < 15) {
+                    id1 = "";
+                }
+            } else {
+                if (id0 != null && id0.length() == 14) {
+                    id0 = "";
+                }
+                if (id1 != null && id1.length() == 14) {
+                    id1 = "";
+                }
+            }
+            return getMinOne(id0, id1);
+        } else {
+            String deviceId = tm.getDeviceId();
+            if (isImei) {
+                if (deviceId != null && deviceId.length() >= 15) {
+                    return deviceId;
+                }
+            } else {
+                if (deviceId != null && deviceId.length() == 14) {
+                    return deviceId;
+                }
+            }
+        }
+        return "";
+    }
+
+    private static String getMinOne(String s0, String s1) {
+        boolean empty0 = TextUtils.isEmpty(s0);
+        boolean empty1 = TextUtils.isEmpty(s1);
+        if (empty0 && empty1) return "";
+        if (!empty0 && !empty1) {
+            if (s0.compareTo(s1) <= 0) {
+                return s0;
+            } else {
+                return s1;
+            }
+        }
+        if (!empty0) return s0;
+        return s1;
+    }
+
+    private static String getSystemPropertyByReflect(String key) {
+        try {
+            @SuppressLint("PrivateApi")
+            Class<?> clz = Class.forName("android.os.SystemProperties");
+            Method getMethod = clz.getMethod("get", String.class, String.class);
+            return (String) getMethod.invoke(clz, key, "");
+        } catch (Exception e) {/**/}
+        return "";
+    }
+
+    /**
+     * Return the IMSI.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.READ_PHONE_STATE" />}</p>
+     *
+     * @return the IMSI
+     */
+    @SuppressLint("HardwareIds")
+    @RequiresPermission(READ_PHONE_STATE)
+    public static String getIMSI() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            try {
+                getTelephonyManager().getSubscriberId();
+            } catch (SecurityException e) {
+                e.printStackTrace();
+                return "";
+            }
+        }
+        return getTelephonyManager().getSubscriberId();
+    }
+
+    /**
+     * Returns the current phone type.
+     *
+     * @return the current phone type
+     * <ul>
+     * <li>{@link TelephonyManager#PHONE_TYPE_NONE}</li>
+     * <li>{@link TelephonyManager#PHONE_TYPE_GSM }</li>
+     * <li>{@link TelephonyManager#PHONE_TYPE_CDMA}</li>
+     * <li>{@link TelephonyManager#PHONE_TYPE_SIP }</li>
+     * </ul>
+     */
+    public static int getPhoneType() {
+        TelephonyManager tm = getTelephonyManager();
+        return tm.getPhoneType();
+    }
+
+    /**
+     * Return whether sim card state is ready.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSimCardReady() {
+        TelephonyManager tm = getTelephonyManager();
+        return tm.getSimState() == TelephonyManager.SIM_STATE_READY;
+    }
+
+    /**
+     * Return the sim operator name.
+     *
+     * @return the sim operator name
+     */
+    public static String getSimOperatorName() {
+        TelephonyManager tm = getTelephonyManager();
+        return tm.getSimOperatorName();
+    }
+
+    /**
+     * Return the sim operator using mnc.
+     *
+     * @return the sim operator
+     */
+    public static String getSimOperatorByMnc() {
+        TelephonyManager tm = getTelephonyManager();
+        String operator = tm.getSimOperator();
+        if (operator == null) return "";
+        switch (operator) {
+            case "46000":
+            case "46002":
+            case "46007":
+            case "46020":
+                return "中国移动";
+            case "46001":
+            case "46006":
+            case "46009":
+                return "中国联通";
+            case "46003":
+            case "46005":
+            case "46011":
+                return "中国电信";
+            default:
+                return operator;
+        }
+    }
+
+    /**
+     * Skip to dial.
+     *
+     * @param phoneNumber The phone number.
+     */
+    public static void dial(@NonNull final String phoneNumber) {
+        Utils.getApp().startActivity(UtilsBridge.getDialIntent(phoneNumber));
+    }
+
+    /**
+     * Make a phone call.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.CALL_PHONE" />}</p>
+     *
+     * @param phoneNumber The phone number.
+     */
+    @RequiresPermission(CALL_PHONE)
+    public static void call(@NonNull final String phoneNumber) {
+        Utils.getApp().startActivity(UtilsBridge.getCallIntent(phoneNumber));
+    }
+
+    /**
+     * Send sms.
+     *
+     * @param phoneNumber The phone number.
+     * @param content     The content.
+     */
+    public static void sendSms(@NonNull final String phoneNumber, final String content) {
+        Utils.getApp().startActivity(UtilsBridge.getSendSmsIntent(phoneNumber, content));
+    }
+
+    private static TelephonyManager getTelephonyManager() {
+        return (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
+    }
+}

+ 273 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ProcessUtils.java

@@ -0,0 +1,273 @@
+package com.cooleshow.base.utils;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.Application;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresPermission;
+
+import static android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/10/18
+ *     desc  : utils about process
+ * </pre>
+ */
+public final class ProcessUtils {
+
+    private ProcessUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the foreground process name.
+     * <p>Target APIs greater than 21 must hold
+     * {@code <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />}</p>
+     *
+     * @return the foreground process name
+     */
+    public static String getForegroundProcessName() {
+        ActivityManager am =
+                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        //noinspection ConstantConditions
+        List<ActivityManager.RunningAppProcessInfo> pInfo = am.getRunningAppProcesses();
+        if (pInfo != null && pInfo.size() > 0) {
+            for (ActivityManager.RunningAppProcessInfo aInfo : pInfo) {
+                if (aInfo.importance
+                        == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                    return aInfo.processName;
+                }
+            }
+        }
+        if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) {
+            PackageManager pm = Utils.getApp().getPackageManager();
+            Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
+            List<ResolveInfo> list =
+                    pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+            Log.i("ProcessUtils", list.toString());
+            if (list.size() <= 0) {
+                Log.i("ProcessUtils",
+                        "getForegroundProcessName: noun of access to usage information.");
+                return "";
+            }
+            try {// Access to usage information.
+                ApplicationInfo info =
+                        pm.getApplicationInfo(Utils.getApp().getPackageName(), 0);
+                AppOpsManager aom =
+                        (AppOpsManager) Utils.getApp().getSystemService(Context.APP_OPS_SERVICE);
+                if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
+                        info.uid,
+                        info.packageName) != AppOpsManager.MODE_ALLOWED) {
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    Utils.getApp().startActivity(intent);
+                }
+                if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
+                        info.uid,
+                        info.packageName) != AppOpsManager.MODE_ALLOWED) {
+                    Log.i("ProcessUtils",
+                            "getForegroundProcessName: refuse to device usage stats.");
+                    return "";
+                }
+                UsageStatsManager usageStatsManager = (UsageStatsManager) Utils.getApp()
+                        .getSystemService(Context.USAGE_STATS_SERVICE);
+                List<UsageStats> usageStatsList = null;
+                if (usageStatsManager != null) {
+                    long endTime = System.currentTimeMillis();
+                    long beginTime = endTime - 86400000 * 7;
+                    usageStatsList = usageStatsManager
+                            .queryUsageStats(UsageStatsManager.INTERVAL_BEST,
+                                    beginTime, endTime);
+                }
+                if (usageStatsList == null || usageStatsList.isEmpty()) return "";
+                UsageStats recentStats = null;
+                for (UsageStats usageStats : usageStatsList) {
+                    if (recentStats == null
+                            || usageStats.getLastTimeUsed() > recentStats.getLastTimeUsed()) {
+                        recentStats = usageStats;
+                    }
+                }
+                return recentStats == null ? null : recentStats.getPackageName();
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Return all background processes.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />}</p>
+     *
+     * @return all background processes
+     */
+    @RequiresPermission(KILL_BACKGROUND_PROCESSES)
+    public static Set<String> getAllBackgroundProcesses() {
+        ActivityManager am =
+                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
+        Set<String> set = new HashSet<>();
+        if (info != null) {
+            for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+                Collections.addAll(set, aInfo.pkgList);
+            }
+        }
+        return set;
+    }
+
+    /**
+     * Kill all background processes.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />}</p>
+     *
+     * @return background processes were killed
+     */
+    @RequiresPermission(KILL_BACKGROUND_PROCESSES)
+    public static Set<String> killAllBackgroundProcesses() {
+        ActivityManager am =
+                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
+        Set<String> set = new HashSet<>();
+        if (info == null) return set;
+        for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+            for (String pkg : aInfo.pkgList) {
+                am.killBackgroundProcesses(pkg);
+                set.add(pkg);
+            }
+        }
+        info = am.getRunningAppProcesses();
+        for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+            for (String pkg : aInfo.pkgList) {
+                set.remove(pkg);
+            }
+        }
+        return set;
+    }
+
+    /**
+     * Kill background processes.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />}</p>
+     *
+     * @param packageName The name of the package.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    @RequiresPermission(KILL_BACKGROUND_PROCESSES)
+    public static boolean killBackgroundProcesses(@NonNull final String packageName) {
+        ActivityManager am =
+                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
+        if (info == null || info.size() == 0) return true;
+        for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+            if (Arrays.asList(aInfo.pkgList).contains(packageName)) {
+                am.killBackgroundProcesses(packageName);
+            }
+        }
+        info = am.getRunningAppProcesses();
+        if (info == null || info.size() == 0) return true;
+        for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+            if (Arrays.asList(aInfo.pkgList).contains(packageName)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return whether app running in the main process.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMainProcess() {
+        return Utils.getApp().getPackageName().equals(getCurrentProcessName());
+    }
+
+    /**
+     * Return the name of current process.
+     *
+     * @return the name of current process
+     */
+    public static String getCurrentProcessName() {
+        String name = getCurrentProcessNameByFile();
+        if (!TextUtils.isEmpty(name)) return name;
+        name = getCurrentProcessNameByAms();
+        if (!TextUtils.isEmpty(name)) return name;
+        name = getCurrentProcessNameByReflect();
+        return name;
+    }
+
+    private static String getCurrentProcessNameByFile() {
+        try {
+            File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
+            BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
+            String processName = mBufferedReader.readLine().trim();
+            mBufferedReader.close();
+            return processName;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    private static String getCurrentProcessNameByAms() {
+        try {
+            ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+            if (am == null) return "";
+            List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
+            if (info == null || info.size() == 0) return "";
+            int pid = android.os.Process.myPid();
+            for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+                if (aInfo.pid == pid) {
+                    if (aInfo.processName != null) {
+                        return aInfo.processName;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            return "";
+        }
+        return "";
+    }
+
+    private static String getCurrentProcessNameByReflect() {
+        String processName = "";
+        try {
+            Application app = Utils.getApp();
+            Field loadedApkField = app.getClass().getField("mLoadedApk");
+            loadedApkField.setAccessible(true);
+            Object loadedApk = loadedApkField.get(app);
+
+            Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread");
+            activityThreadField.setAccessible(true);
+            Object activityThread = activityThreadField.get(loadedApk);
+
+            Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName");
+            processName = (String) getProcessName.invoke(activityThread);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return processName;
+    }
+}

+ 556 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ReflectUtils.java

@@ -0,0 +1,556 @@
+package com.cooleshow.base.utils;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/12/15
+ *     desc  : utils about reflect
+ * </pre>
+ */
+public final class ReflectUtils {
+
+    private final Class<?> type;
+
+    private final Object object;
+
+    private ReflectUtils(final Class<?> type) {
+        this(type, type);
+    }
+
+    private ReflectUtils(final Class<?> type, Object object) {
+        this.type = type;
+        this.object = object;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // reflect
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Reflect the class.
+     *
+     * @param className The name of class.
+     * @return the single {@link ReflectUtils} instance
+     * @throws ReflectException if reflect unsuccessfully
+     */
+    public static ReflectUtils reflect(final String className)
+            throws ReflectException {
+        return reflect(forName(className));
+    }
+
+    /**
+     * Reflect the class.
+     *
+     * @param className   The name of class.
+     * @param classLoader The loader of class.
+     * @return the single {@link ReflectUtils} instance
+     * @throws ReflectException if reflect unsuccessfully
+     */
+    public static ReflectUtils reflect(final String className, final ClassLoader classLoader)
+            throws ReflectException {
+        return reflect(forName(className, classLoader));
+    }
+
+    /**
+     * Reflect the class.
+     *
+     * @param clazz The class.
+     * @return the single {@link ReflectUtils} instance
+     * @throws ReflectException if reflect unsuccessfully
+     */
+    public static ReflectUtils reflect(final Class<?> clazz)
+            throws ReflectException {
+        return new ReflectUtils(clazz);
+    }
+
+    /**
+     * Reflect the class.
+     *
+     * @param object The object.
+     * @return the single {@link ReflectUtils} instance
+     * @throws ReflectException if reflect unsuccessfully
+     */
+    public static ReflectUtils reflect(final Object object)
+            throws ReflectException {
+        return new ReflectUtils(object == null ? Object.class : object.getClass(), object);
+    }
+
+    private static Class<?> forName(String className) {
+        try {
+            return Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            throw new ReflectException(e);
+        }
+    }
+
+    private static Class<?> forName(String name, ClassLoader classLoader) {
+        try {
+            return Class.forName(name, true, classLoader);
+        } catch (ClassNotFoundException e) {
+            throw new ReflectException(e);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // newInstance
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Create and initialize a new instance.
+     *
+     * @return the single {@link ReflectUtils} instance
+     */
+    public ReflectUtils newInstance() {
+        return newInstance(new Object[0]);
+    }
+
+    /**
+     * Create and initialize a new instance.
+     *
+     * @param args The args.
+     * @return the single {@link ReflectUtils} instance
+     */
+    public ReflectUtils newInstance(Object... args) {
+        Class<?>[] types = getArgsType(args);
+        try {
+            Constructor<?> constructor = type().getDeclaredConstructor(types);
+            return newInstance(constructor, args);
+        } catch (NoSuchMethodException e) {
+            List<Constructor<?>> list = new ArrayList<>();
+            for (Constructor<?> constructor : type().getDeclaredConstructors()) {
+                if (match(constructor.getParameterTypes(), types)) {
+                    list.add(constructor);
+                }
+            }
+            if (list.isEmpty()) {
+                throw new ReflectException(e);
+            } else {
+                sortConstructors(list);
+                return newInstance(list.get(0), args);
+            }
+        }
+    }
+
+    private Class<?>[] getArgsType(final Object... args) {
+        if (args == null) return new Class[0];
+        Class<?>[] result = new Class[args.length];
+        for (int i = 0; i < args.length; i++) {
+            Object value = args[i];
+            result[i] = value == null ? NULL.class : value.getClass();
+        }
+        return result;
+    }
+
+    private void sortConstructors(List<Constructor<?>> list) {
+        Collections.sort(list, new Comparator<Constructor<?>>() {
+            @Override
+            public int compare(Constructor<?> o1, Constructor<?> o2) {
+                Class<?>[] types1 = o1.getParameterTypes();
+                Class<?>[] types2 = o2.getParameterTypes();
+                int len = types1.length;
+                for (int i = 0; i < len; i++) {
+                    if (!types1[i].equals(types2[i])) {
+                        if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) {
+                            return 1;
+                        } else {
+                            return -1;
+                        }
+                    }
+                }
+                return 0;
+            }
+        });
+    }
+
+    private ReflectUtils newInstance(final Constructor<?> constructor, final Object... args) {
+        try {
+            return new ReflectUtils(
+                    constructor.getDeclaringClass(),
+                    accessible(constructor).newInstance(args)
+            );
+        } catch (Exception e) {
+            throw new ReflectException(e);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // field
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Get the field.
+     *
+     * @param name The name of field.
+     * @return the single {@link ReflectUtils} instance
+     */
+    public ReflectUtils field(final String name) {
+        try {
+            Field field = getField(name);
+            return new ReflectUtils(field.getType(), field.get(object));
+        } catch (IllegalAccessException e) {
+            throw new ReflectException(e);
+        }
+    }
+
+    /**
+     * Set the field.
+     *
+     * @param name  The name of field.
+     * @param value The value.
+     * @return the single {@link ReflectUtils} instance
+     */
+    public ReflectUtils field(String name, Object value) {
+        try {
+            Field field = getField(name);
+            field.set(object, unwrap(value));
+            return this;
+        } catch (Exception e) {
+            throw new ReflectException(e);
+        }
+    }
+
+    private Field getField(String name) throws IllegalAccessException {
+        Field field = getAccessibleField(name);
+        if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+            try {
+                Field modifiersField = Field.class.getDeclaredField("modifiers");
+                modifiersField.setAccessible(true);
+                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+            } catch (NoSuchFieldException ignore) {
+                // runs in android will happen
+                field.setAccessible(true);
+            }
+        }
+        return field;
+    }
+
+    private Field getAccessibleField(String name) {
+        Class<?> type = type();
+        try {
+            return accessible(type.getField(name));
+        } catch (NoSuchFieldException e) {
+            do {
+                try {
+                    return accessible(type.getDeclaredField(name));
+                } catch (NoSuchFieldException ignore) {
+                }
+                type = type.getSuperclass();
+            } while (type != null);
+            throw new ReflectException(e);
+        }
+    }
+
+    private Object unwrap(Object object) {
+        if (object instanceof ReflectUtils) {
+            return ((ReflectUtils) object).get();
+        }
+        return object;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // method
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Invoke the method.
+     *
+     * @param name The name of method.
+     * @return the single {@link ReflectUtils} instance
+     * @throws ReflectException if reflect unsuccessfully
+     */
+    public ReflectUtils method(final String name) throws ReflectException {
+        return method(name, new Object[0]);
+    }
+
+    /**
+     * Invoke the method.
+     *
+     * @param name The name of method.
+     * @param args The args.
+     * @return the single {@link ReflectUtils} instance
+     * @throws ReflectException if reflect unsuccessfully
+     */
+    public ReflectUtils method(final String name, final Object... args) throws ReflectException {
+        Class<?>[] types = getArgsType(args);
+        try {
+            Method method = exactMethod(name, types);
+            return method(method, object, args);
+        } catch (NoSuchMethodException e) {
+            try {
+                Method method = similarMethod(name, types);
+                return method(method, object, args);
+            } catch (NoSuchMethodException e1) {
+                throw new ReflectException(e1);
+            }
+        }
+    }
+
+    private ReflectUtils method(final Method method, final Object obj, final Object... args) {
+        try {
+            accessible(method);
+            if (method.getReturnType() == void.class) {
+                method.invoke(obj, args);
+                return reflect(obj);
+            } else {
+                return reflect(method.invoke(obj, args));
+            }
+        } catch (Exception e) {
+            throw new ReflectException(e);
+        }
+    }
+
+    private Method exactMethod(final String name, final Class<?>[] types)
+            throws NoSuchMethodException {
+        Class<?> type = type();
+        try {
+            return type.getMethod(name, types);
+        } catch (NoSuchMethodException e) {
+            do {
+                try {
+                    return type.getDeclaredMethod(name, types);
+                } catch (NoSuchMethodException ignore) {
+                }
+                type = type.getSuperclass();
+            } while (type != null);
+            throw new NoSuchMethodException();
+        }
+    }
+
+    private Method similarMethod(final String name, final Class<?>[] types)
+            throws NoSuchMethodException {
+        Class<?> type = type();
+        List<Method> methods = new ArrayList<>();
+        for (Method method : type.getMethods()) {
+            if (isSimilarSignature(method, name, types)) {
+                methods.add(method);
+            }
+        }
+        if (!methods.isEmpty()) {
+            sortMethods(methods);
+            return methods.get(0);
+        }
+        do {
+            for (Method method : type.getDeclaredMethods()) {
+                if (isSimilarSignature(method, name, types)) {
+                    methods.add(method);
+                }
+            }
+            if (!methods.isEmpty()) {
+                sortMethods(methods);
+                return methods.get(0);
+            }
+            type = type.getSuperclass();
+        } while (type != null);
+
+        throw new NoSuchMethodException("No similar method " + name + " with params "
+                + Arrays.toString(types) + " could be found on type " + type() + ".");
+    }
+
+    private void sortMethods(final List<Method> methods) {
+        Collections.sort(methods, new Comparator<Method>() {
+            @Override
+            public int compare(Method o1, Method o2) {
+                Class<?>[] types1 = o1.getParameterTypes();
+                Class<?>[] types2 = o2.getParameterTypes();
+                int len = types1.length;
+                for (int i = 0; i < len; i++) {
+                    if (!types1[i].equals(types2[i])) {
+                        if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) {
+                            return 1;
+                        } else {
+                            return -1;
+                        }
+                    }
+                }
+                return 0;
+            }
+        });
+    }
+
+    private boolean isSimilarSignature(final Method possiblyMatchingMethod,
+                                       final String desiredMethodName,
+                                       final Class<?>[] desiredParamTypes) {
+        return possiblyMatchingMethod.getName().equals(desiredMethodName)
+                && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
+    }
+
+    private boolean match(final Class<?>[] declaredTypes, final Class<?>[] actualTypes) {
+        if (declaredTypes.length == actualTypes.length) {
+            for (int i = 0; i < actualTypes.length; i++) {
+                if (actualTypes[i] == NULL.class
+                        || wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) {
+                    continue;
+                }
+                return false;
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private <T extends AccessibleObject> T accessible(T accessible) {
+        if (accessible == null) return null;
+        if (accessible instanceof Member) {
+            Member member = (Member) accessible;
+            if (Modifier.isPublic(member.getModifiers())
+                    && Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
+                return accessible;
+            }
+        }
+        if (!accessible.isAccessible()) accessible.setAccessible(true);
+        return accessible;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // proxy
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Create a proxy for the wrapped object allowing to typesafely invoke
+     * methods on it using a custom interface.
+     *
+     * @param proxyType The interface type that is implemented by the proxy.
+     * @return a proxy for the wrapped object
+     */
+    @SuppressWarnings("unchecked")
+    public <P> P proxy(final Class<P> proxyType) {
+        final boolean isMap = (object instanceof Map);
+        final InvocationHandler handler = new InvocationHandler() {
+            @Override
+            @SuppressWarnings("null")
+            public Object invoke(Object proxy, Method method, Object[] args) {
+                String name = method.getName();
+                try {
+                    return reflect(object).method(name, args).get();
+                } catch (ReflectException e) {
+                    if (isMap) {
+                        Map<String, Object> map = (Map<String, Object>) object;
+                        int length = (args == null ? 0 : args.length);
+
+                        if (length == 0 && name.startsWith("get")) {
+                            return map.get(property(name.substring(3)));
+                        } else if (length == 0 && name.startsWith("is")) {
+                            return map.get(property(name.substring(2)));
+                        } else if (length == 1 && name.startsWith("set")) {
+                            map.put(property(name.substring(3)), args[0]);
+                            return null;
+                        }
+                    }
+                    throw e;
+                }
+            }
+        };
+        return (P) Proxy.newProxyInstance(proxyType.getClassLoader(),
+                new Class[]{proxyType},
+                handler);
+    }
+
+    /**
+     * Get the POJO property name of an getter/setter
+     */
+    private static String property(String string) {
+        int length = string.length();
+
+        if (length == 0) {
+            return "";
+        } else if (length == 1) {
+            return string.toLowerCase();
+        } else {
+            return string.substring(0, 1).toLowerCase() + string.substring(1);
+        }
+    }
+
+    private Class<?> type() {
+        return type;
+    }
+
+    private Class<?> wrapper(final Class<?> type) {
+        if (type == null) {
+            return null;
+        } else if (type.isPrimitive()) {
+            if (boolean.class == type) {
+                return Boolean.class;
+            } else if (int.class == type) {
+                return Integer.class;
+            } else if (long.class == type) {
+                return Long.class;
+            } else if (short.class == type) {
+                return Short.class;
+            } else if (byte.class == type) {
+                return Byte.class;
+            } else if (double.class == type) {
+                return Double.class;
+            } else if (float.class == type) {
+                return Float.class;
+            } else if (char.class == type) {
+                return Character.class;
+            } else if (void.class == type) {
+                return Void.class;
+            }
+        }
+        return type;
+    }
+
+    /**
+     * Get the result.
+     *
+     * @param <T> The value type.
+     * @return the result
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T get() {
+        return (T) object;
+    }
+
+    @Override
+    public int hashCode() {
+        return object.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj instanceof ReflectUtils && object.equals(((ReflectUtils) obj).get());
+    }
+
+    @Override
+    public String toString() {
+        return object.toString();
+    }
+
+    private static class NULL {
+    }
+
+    public static class ReflectException extends RuntimeException {
+
+        private static final long serialVersionUID = 858774075258496016L;
+
+        public ReflectException(String message) {
+            super(message);
+        }
+
+        public ReflectException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        public ReflectException(Throwable cause) {
+            super(cause);
+        }
+    }
+}

+ 364 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/RegexUtils.java

@@ -0,0 +1,364 @@
+package com.cooleshow.base.utils;
+
+import com.cooleshow.base.constanst.RegexConstants;
+
+import androidx.collection.SimpleArrayMap;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about regex
+ * </pre>
+ */
+public final class RegexUtils {
+
+    private final static SimpleArrayMap<String, String> CITY_MAP = new SimpleArrayMap<>();
+
+    private RegexUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // If u want more please visit http://toutiao.com/i6231678548520731137
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return whether input matches regex of simple mobile.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMobileSimple(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_MOBILE_SIMPLE, input);
+    }
+
+    
+    /**
+     * Returns the domain part of a given Email address
+     *
+     * @param email The Email address. E.g Returns "protonmail.com" from the given Email "johnsmith@protonmail.com".
+     * @return the domain part of a given Email address.
+     */
+    public static String extractEmailProvider(String email) {
+        return email.substring(email.lastIndexOf("@") + 1);
+    }
+
+    /**
+     * Returns the username part of a given Email address. E.g. Returns "johnsmith" from the given Email "johnsmith@protonmail.com".
+     *
+     * @param email The Email address.
+     * @return the username part of a given Email address.
+     */
+    public static String extractEmailUsername(String email) {
+        return email.substring(0, email.lastIndexOf("@"));
+    }
+
+
+    /**
+     * Return whether a given Email address is on a specified Email provider. E.g. "johnsmith@protonmail.com" and "gmail.com" will return false.
+     *
+     * @param email The Email address.
+     * @param emailProvider The Email provider to testify against.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFromEmailProvider(String email, String emailProvider) {
+        return extractEmailProvider(email).equalsIgnoreCase(emailProvider);
+    }
+
+    /**
+     * Return whether a given Email address is on any of the specified Email providers list (array). E.g. Useful if you pass it a list of real Email provider services and check if the Email is a disposable Email or a real one.
+     *
+     * @param email The Email address.
+     * @param emailProviders The list of Email providers to testify against.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFromAnyOfEmailProviders(String email, String[] emailProviders) {
+        return com.cooleshow.base.utils.ArrayUtils.contains(emailProviders, extractEmailProvider(email));
+    }
+    
+    
+    
+    
+    
+    /**
+     * Return whether input matches regex of exact mobile.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMobileExact(final CharSequence input) {
+        return isMobileExact(input, null);
+    }
+
+    /**
+     * Return whether input matches regex of exact mobile.
+     *
+     * @param input       The input.
+     * @param newSegments The new segments of mobile number.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMobileExact(final CharSequence input, List<String> newSegments) {
+        boolean match = isMatch(RegexConstants.REGEX_MOBILE_EXACT, input);
+        if (match) return true;
+        if (newSegments == null) return false;
+        if (input == null || input.length() != 11) return false;
+        String content = input.toString();
+        for (char c : content.toCharArray()) {
+            if (!Character.isDigit(c)) {
+                return false;
+            }
+        }
+        for (String newSegment : newSegments) {
+            if (content.startsWith(newSegment)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return whether input matches regex of telephone number.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isTel(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_TEL, input);
+    }
+
+    /**
+     * Return whether input matches regex of id card number which length is 15.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isIDCard15(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_ID_CARD15, input);
+    }
+
+    /**
+     * Return whether input matches regex of id card number which length is 18.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isIDCard18(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_ID_CARD18, input);
+    }
+
+    /**
+     * Return whether input matches regex of exact id card number which length is 18.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isIDCard18Exact(final CharSequence input) {
+        if (isIDCard18(input)) {
+            int[] factor = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
+            char[] suffix = new char[]{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
+            if (CITY_MAP.isEmpty()) {
+                CITY_MAP.put("11", "北京");
+                CITY_MAP.put("12", "天津");
+                CITY_MAP.put("13", "河北");
+                CITY_MAP.put("14", "山西");
+                CITY_MAP.put("15", "内蒙古");
+
+                CITY_MAP.put("21", "辽宁");
+                CITY_MAP.put("22", "吉林");
+                CITY_MAP.put("23", "黑龙江");
+
+                CITY_MAP.put("31", "上海");
+                CITY_MAP.put("32", "江苏");
+                CITY_MAP.put("33", "浙江");
+                CITY_MAP.put("34", "安徽");
+                CITY_MAP.put("35", "福建");
+                CITY_MAP.put("36", "江西");
+                CITY_MAP.put("37", "山东");
+
+                CITY_MAP.put("41", "河南");
+                CITY_MAP.put("42", "湖北");
+                CITY_MAP.put("43", "湖南");
+                CITY_MAP.put("44", "广东");
+                CITY_MAP.put("45", "广西");
+                CITY_MAP.put("46", "海南");
+
+                CITY_MAP.put("50", "重庆");
+                CITY_MAP.put("51", "四川");
+                CITY_MAP.put("52", "贵州");
+                CITY_MAP.put("53", "云南");
+                CITY_MAP.put("54", "西藏");
+
+                CITY_MAP.put("61", "陕西");
+                CITY_MAP.put("62", "甘肃");
+                CITY_MAP.put("63", "青海");
+                CITY_MAP.put("64", "宁夏");
+                CITY_MAP.put("65", "新疆");
+
+                CITY_MAP.put("71", "台湾老");
+                CITY_MAP.put("81", "香港");
+                CITY_MAP.put("82", "澳门");
+                CITY_MAP.put("83", "台湾新");
+                CITY_MAP.put("91", "国外");
+            }
+            if (CITY_MAP.get(input.subSequence(0, 2).toString()) != null) {
+                int weightSum = 0;
+                for (int i = 0; i < 17; ++i) {
+                    weightSum += (input.charAt(i) - '0') * factor[i];
+                }
+                int idCardMod = weightSum % 11;
+                char idCardLast = input.charAt(17);
+                return idCardLast == suffix[idCardMod];
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return whether input matches regex of email.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isEmail(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_EMAIL, input);
+    }
+
+    /**
+     * Return whether input matches regex of url.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isURL(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_URL, input);
+    }
+
+    /**
+     * Return whether input matches regex of Chinese character.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isZh(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_ZH, input);
+    }
+
+    /**
+     * Return whether input matches regex of username.
+     * <p>scope for "a-z", "A-Z", "0-9", "_", "Chinese character"</p>
+     * <p>can't end with "_"</p>
+     * <p>length is between 6 to 20</p>.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isUsername(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_USERNAME, input);
+    }
+
+    /**
+     * Return whether input matches regex of date which pattern is "yyyy-MM-dd".
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isDate(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_DATE, input);
+    }
+
+    /**
+     * Return whether input matches regex of ip address.
+     *
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isIP(final CharSequence input) {
+        return isMatch(RegexConstants.REGEX_IP, input);
+    }
+
+    /**
+     * Return whether input matches the regex.
+     *
+     * @param regex The regex.
+     * @param input The input.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMatch(final String regex, final CharSequence input) {
+        return input != null && input.length() > 0 && Pattern.matches(regex, input);
+    }
+
+    /**
+     * Return the list of input matches the regex.
+     *
+     * @param regex The regex.
+     * @param input The input.
+     * @return the list of input matches the regex
+     */
+    public static List<String> getMatches(final String regex, final CharSequence input) {
+        if (input == null) return Collections.emptyList();
+        List<String> matches = new ArrayList<>();
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(input);
+        while (matcher.find()) {
+            matches.add(matcher.group());
+        }
+        return matches;
+    }
+
+    /**
+     * Splits input around matches of the regex.
+     *
+     * @param input The input.
+     * @param regex The regex.
+     * @return the array of strings computed by splitting input around matches of regex
+     */
+    public static String[] getSplits(final String input, final String regex) {
+        if (input == null) return new String[0];
+        return input.split(regex);
+    }
+
+    /**
+     * Replace the first subsequence of the input sequence that matches the
+     * regex with the given replacement string.
+     *
+     * @param input       The input.
+     * @param regex       The regex.
+     * @param replacement The replacement string.
+     * @return the string constructed by replacing the first matching
+     * subsequence by the replacement string, substituting captured
+     * subsequences as needed
+     */
+    public static String getReplaceFirst(final String input,
+                                         final String regex,
+                                         final String replacement) {
+        if (input == null) return "";
+        return Pattern.compile(regex).matcher(input).replaceFirst(replacement);
+    }
+
+    /**
+     * Replace every subsequence of the input sequence that matches the
+     * pattern with the given replacement string.
+     *
+     * @param input       The input.
+     * @param regex       The regex.
+     * @param replacement The replacement string.
+     * @return the string constructed by replacing each matching subsequence
+     * by the replacement string, substituting captured subsequences
+     * as needed
+     */
+    public static String getReplaceAll(final String input,
+                                       final String regex,
+                                       final String replacement) {
+        if (input == null) return "";
+        return Pattern.compile(regex).matcher(input).replaceAll(replacement);
+    }
+}

+ 303 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ResourceUtils.java

@@ -0,0 +1,303 @@
+package com.cooleshow.base.utils;
+
+import android.graphics.drawable.Drawable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.RawRes;
+import androidx.core.content.ContextCompat;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/05/07
+ *     desc  : utils about resource
+ * </pre>
+ */
+public final class ResourceUtils {
+
+    private static final int BUFFER_SIZE = 8192;
+
+    private ResourceUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the drawable by identifier.
+     *
+     * @param id The identifier.
+     * @return the drawable by identifier
+     */
+    public static Drawable getDrawable(@DrawableRes int id) {
+        return ContextCompat.getDrawable(Utils.getApp(), id);
+    }
+
+    /**
+     * Return the id identifier by name.
+     *
+     * @param name The name of id.
+     * @return the id identifier by name
+     */
+    public static int getIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "id", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the string identifier by name.
+     *
+     * @param name The name of string.
+     * @return the string identifier by name
+     */
+    public static int getStringIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "string", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the color identifier by name.
+     *
+     * @param name The name of color.
+     * @return the color identifier by name
+     */
+    public static int getColorIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "color", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the dimen identifier by name.
+     *
+     * @param name The name of dimen.
+     * @return the dimen identifier by name
+     */
+    public static int getDimenIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "dimen", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the drawable identifier by name.
+     *
+     * @param name The name of drawable.
+     * @return the drawable identifier by name
+     */
+    public static int getDrawableIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "drawable", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the mipmap identifier by name.
+     *
+     * @param name The name of mipmap.
+     * @return the mipmap identifier by name
+     */
+    public static int getMipmapIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "mipmap", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the layout identifier by name.
+     *
+     * @param name The name of layout.
+     * @return the layout identifier by name
+     */
+    public static int getLayoutIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "layout", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the style identifier by name.
+     *
+     * @param name The name of style.
+     * @return the style identifier by name
+     */
+    public static int getStyleIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "style", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the anim identifier by name.
+     *
+     * @param name The name of anim.
+     * @return the anim identifier by name
+     */
+    public static int getAnimIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "anim", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Return the menu identifier by name.
+     *
+     * @param name The name of menu.
+     * @return the menu identifier by name
+     */
+    public static int getMenuIdByName(String name) {
+        return Utils.getApp().getResources().getIdentifier(name, "menu", Utils.getApp().getPackageName());
+    }
+
+    /**
+     * Copy the file from assets.
+     *
+     * @param assetsFilePath The path of file in assets.
+     * @param destFilePath   The path of destination file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean copyFileFromAssets(final String assetsFilePath, final String destFilePath) {
+        boolean res = true;
+        try {
+            String[] assets = Utils.getApp().getAssets().list(assetsFilePath);
+            if (assets != null && assets.length > 0) {
+                for (String asset : assets) {
+                    res &= copyFileFromAssets(assetsFilePath + "/" + asset, destFilePath + "/" + asset);
+                }
+            } else {
+                res = UtilsBridge.writeFileFromIS(
+                        destFilePath,
+                        Utils.getApp().getAssets().open(assetsFilePath)
+                );
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            res = false;
+        }
+        return res;
+    }
+
+    /**
+     * Return the content of assets.
+     *
+     * @param assetsFilePath The path of file in assets.
+     * @return the content of assets
+     */
+    public static String readAssets2String(final String assetsFilePath) {
+        return readAssets2String(assetsFilePath, null);
+    }
+
+    /**
+     * Return the content of assets.
+     *
+     * @param assetsFilePath The path of file in assets.
+     * @param charsetName    The name of charset.
+     * @return the content of assets
+     */
+    public static String readAssets2String(final String assetsFilePath, final String charsetName) {
+        try {
+            InputStream is = Utils.getApp().getAssets().open(assetsFilePath);
+            byte[] bytes = UtilsBridge.inputStream2Bytes(is);
+            if (bytes == null) return "";
+            if (UtilsBridge.isSpace(charsetName)) {
+                return new String(bytes);
+            } else {
+                try {
+                    return new String(bytes, charsetName);
+                } catch (UnsupportedEncodingException e) {
+                    e.printStackTrace();
+                    return "";
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Return the content of file in assets.
+     *
+     * @param assetsPath The path of file in assets.
+     * @return the content of file in assets
+     */
+    public static List<String> readAssets2List(final String assetsPath) {
+        return readAssets2List(assetsPath, "");
+    }
+
+    /**
+     * Return the content of file in assets.
+     *
+     * @param assetsPath  The path of file in assets.
+     * @param charsetName The name of charset.
+     * @return the content of file in assets
+     */
+    public static List<String> readAssets2List(final String assetsPath,
+                                               final String charsetName) {
+        try {
+            return UtilsBridge.inputStream2Lines(Utils.getApp().getResources().getAssets().open(assetsPath), charsetName);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return Collections.emptyList();
+        }
+    }
+
+
+    /**
+     * Copy the file from raw.
+     *
+     * @param resId        The resource id.
+     * @param destFilePath The path of destination file.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean copyFileFromRaw(@RawRes final int resId, final String destFilePath) {
+        return UtilsBridge.writeFileFromIS(
+                destFilePath,
+                Utils.getApp().getResources().openRawResource(resId)
+        );
+    }
+
+    /**
+     * Return the content of resource in raw.
+     *
+     * @param resId The resource id.
+     * @return the content of resource in raw
+     */
+    public static String readRaw2String(@RawRes final int resId) {
+        return readRaw2String(resId, null);
+    }
+
+    /**
+     * Return the content of resource in raw.
+     *
+     * @param resId       The resource id.
+     * @param charsetName The name of charset.
+     * @return the content of resource in raw
+     */
+    public static String readRaw2String(@RawRes final int resId, final String charsetName) {
+        InputStream is = Utils.getApp().getResources().openRawResource(resId);
+        byte[] bytes = UtilsBridge.inputStream2Bytes(is);
+        if (bytes == null) return null;
+        if (UtilsBridge.isSpace(charsetName)) {
+            return new String(bytes);
+        } else {
+            try {
+                return new String(bytes, charsetName);
+            } catch (UnsupportedEncodingException e) {
+                e.printStackTrace();
+                return "";
+            }
+        }
+    }
+
+    /**
+     * Return the content of resource in raw.
+     *
+     * @param resId The resource id.
+     * @return the content of file in assets
+     */
+    public static List<String> readRaw2List(@RawRes final int resId) {
+        return readRaw2List(resId, "");
+    }
+
+    /**
+     * Return the content of resource in raw.
+     *
+     * @param resId       The resource id.
+     * @param charsetName The name of charset.
+     * @return the content of file in assets
+     */
+    public static List<String> readRaw2List(@RawRes final int resId,
+                                            final String charsetName) {
+        return UtilsBridge.inputStream2Lines(Utils.getApp().getResources().openRawResource(resId), charsetName);
+    }
+}

+ 456 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/RomUtils.java

@@ -0,0 +1,456 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.Properties;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/07/04
+ *     desc  : utils about rom
+ * </pre>
+ */
+public final class RomUtils {
+
+    private static final String[] ROM_HUAWEI    = {"huawei"};
+    private static final String[] ROM_VIVO      = {"vivo"};
+    private static final String[] ROM_XIAOMI    = {"xiaomi"};
+    private static final String[] ROM_OPPO      = {"oppo"};
+    private static final String[] ROM_LEECO     = {"leeco", "letv"};
+    private static final String[] ROM_360       = {"360", "qiku"};
+    private static final String[] ROM_ZTE       = {"zte"};
+    private static final String[] ROM_ONEPLUS   = {"oneplus"};
+    private static final String[] ROM_NUBIA     = {"nubia"};
+    private static final String[] ROM_COOLPAD   = {"coolpad", "yulong"};
+    private static final String[] ROM_LG        = {"lg", "lge"};
+    private static final String[] ROM_GOOGLE    = {"google"};
+    private static final String[] ROM_SAMSUNG   = {"samsung"};
+    private static final String[] ROM_MEIZU     = {"meizu"};
+    private static final String[] ROM_LENOVO    = {"lenovo"};
+    private static final String[] ROM_SMARTISAN = {"smartisan", "deltainno"};
+    private static final String[] ROM_HTC       = {"htc"};
+    private static final String[] ROM_SONY      = {"sony"};
+    private static final String[] ROM_GIONEE    = {"gionee", "amigo"};
+    private static final String[] ROM_MOTOROLA  = {"motorola"};
+
+    private static final String VERSION_PROPERTY_HUAWEI  = "ro.build.version.emui";
+    private static final String VERSION_PROPERTY_VIVO    = "ro.vivo.os.build.display.id";
+    private static final String VERSION_PROPERTY_XIAOMI  = "ro.build.version.incremental";
+    private static final String VERSION_PROPERTY_OPPO    = "ro.build.version.opporom";
+    private static final String VERSION_PROPERTY_LEECO   = "ro.letv.release.version";
+    private static final String VERSION_PROPERTY_360     = "ro.build.uiversion";
+    private static final String VERSION_PROPERTY_ZTE     = "ro.build.MiFavor_version";
+    private static final String VERSION_PROPERTY_ONEPLUS = "ro.rom.version";
+    private static final String VERSION_PROPERTY_NUBIA   = "ro.build.rom.id";
+    private final static String UNKNOWN                  = "unknown";
+
+    private static RomInfo bean = null;
+
+    private RomUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether the rom is made by huawei.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isHuawei() {
+        return ROM_HUAWEI[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by vivo.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isVivo() {
+        return ROM_VIVO[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by xiaomi.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isXiaomi() {
+        return ROM_XIAOMI[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by oppo.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isOppo() {
+        return ROM_OPPO[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by leeco.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLeeco() {
+        return ROM_LEECO[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by 360.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean is360() {
+        return ROM_360[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by zte.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isZte() {
+        return ROM_ZTE[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by oneplus.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isOneplus() {
+        return ROM_ONEPLUS[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by nubia.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNubia() {
+        return ROM_NUBIA[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by coolpad.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isCoolpad() {
+        return ROM_COOLPAD[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by lg.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLg() {
+        return ROM_LG[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by google.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isGoogle() {
+        return ROM_GOOGLE[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by samsung.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSamsung() {
+        return ROM_SAMSUNG[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by meizu.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMeizu() {
+        return ROM_MEIZU[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by lenovo.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLenovo() {
+        return ROM_LENOVO[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by smartisan.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSmartisan() {
+        return ROM_SMARTISAN[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by htc.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isHtc() {
+        return ROM_HTC[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by sony.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSony() {
+        return ROM_SONY[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by gionee.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isGionee() {
+        return ROM_GIONEE[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return whether the rom is made by motorola.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMotorola() {
+        return ROM_MOTOROLA[0].equals(getRomInfo().name);
+    }
+
+    /**
+     * Return the rom's information.
+     *
+     * @return the rom's information
+     */
+    public static RomInfo getRomInfo() {
+        if (bean != null) return bean;
+        bean = new RomInfo();
+        final String brand = getBrand();
+        final String manufacturer = getManufacturer();
+        if (isRightRom(brand, manufacturer, ROM_HUAWEI)) {
+            bean.name = ROM_HUAWEI[0];
+            String version = getRomVersion(VERSION_PROPERTY_HUAWEI);
+            String[] temp = version.split("_");
+            if (temp.length > 1) {
+                bean.version = temp[1];
+            } else {
+                bean.version = version;
+            }
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_VIVO)) {
+            bean.name = ROM_VIVO[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_VIVO);
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_XIAOMI)) {
+            bean.name = ROM_XIAOMI[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_XIAOMI);
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_OPPO)) {
+            bean.name = ROM_OPPO[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_OPPO);
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_LEECO)) {
+            bean.name = ROM_LEECO[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_LEECO);
+            return bean;
+        }
+
+        if (isRightRom(brand, manufacturer, ROM_360)) {
+            bean.name = ROM_360[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_360);
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_ZTE)) {
+            bean.name = ROM_ZTE[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_ZTE);
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_ONEPLUS)) {
+            bean.name = ROM_ONEPLUS[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_ONEPLUS);
+            return bean;
+        }
+        if (isRightRom(brand, manufacturer, ROM_NUBIA)) {
+            bean.name = ROM_NUBIA[0];
+            bean.version = getRomVersion(VERSION_PROPERTY_NUBIA);
+            return bean;
+        }
+
+        if (isRightRom(brand, manufacturer, ROM_COOLPAD)) {
+            bean.name = ROM_COOLPAD[0];
+        } else if (isRightRom(brand, manufacturer, ROM_LG)) {
+            bean.name = ROM_LG[0];
+        } else if (isRightRom(brand, manufacturer, ROM_GOOGLE)) {
+            bean.name = ROM_GOOGLE[0];
+        } else if (isRightRom(brand, manufacturer, ROM_SAMSUNG)) {
+            bean.name = ROM_SAMSUNG[0];
+        } else if (isRightRom(brand, manufacturer, ROM_MEIZU)) {
+            bean.name = ROM_MEIZU[0];
+        } else if (isRightRom(brand, manufacturer, ROM_LENOVO)) {
+            bean.name = ROM_LENOVO[0];
+        } else if (isRightRom(brand, manufacturer, ROM_SMARTISAN)) {
+            bean.name = ROM_SMARTISAN[0];
+        } else if (isRightRom(brand, manufacturer, ROM_HTC)) {
+            bean.name = ROM_HTC[0];
+        } else if (isRightRom(brand, manufacturer, ROM_SONY)) {
+            bean.name = ROM_SONY[0];
+        } else if (isRightRom(brand, manufacturer, ROM_GIONEE)) {
+            bean.name = ROM_GIONEE[0];
+        } else if (isRightRom(brand, manufacturer, ROM_MOTOROLA)) {
+            bean.name = ROM_MOTOROLA[0];
+        } else {
+            bean.name = manufacturer;
+        }
+        bean.version = getRomVersion("");
+        return bean;
+    }
+
+    private static boolean isRightRom(final String brand, final String manufacturer, final String... names) {
+        for (String name : names) {
+            if (brand.contains(name) || manufacturer.contains(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static String getManufacturer() {
+        try {
+            String manufacturer = Build.MANUFACTURER;
+            if (!TextUtils.isEmpty(manufacturer)) {
+                return manufacturer.toLowerCase();
+            }
+        } catch (Throwable ignore) {/**/}
+        return UNKNOWN;
+    }
+
+    private static String getBrand() {
+        try {
+            String brand = Build.BRAND;
+            if (!TextUtils.isEmpty(brand)) {
+                return brand.toLowerCase();
+            }
+        } catch (Throwable ignore) {/**/}
+        return UNKNOWN;
+    }
+
+    private static String getRomVersion(final String propertyName) {
+        String ret = "";
+        if (!TextUtils.isEmpty(propertyName)) {
+            ret = getSystemProperty(propertyName);
+        }
+        if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) {
+            try {
+                String display = Build.DISPLAY;
+                if (!TextUtils.isEmpty(display)) {
+                    ret = display.toLowerCase();
+                }
+            } catch (Throwable ignore) {/**/}
+        }
+        if (TextUtils.isEmpty(ret)) {
+            return UNKNOWN;
+        }
+        return ret;
+    }
+
+    private static String getSystemProperty(final String name) {
+        String prop = getSystemPropertyByShell(name);
+        if (!TextUtils.isEmpty(prop)) return prop;
+        prop = getSystemPropertyByStream(name);
+        if (!TextUtils.isEmpty(prop)) return prop;
+        if (Build.VERSION.SDK_INT < 28) {
+            return getSystemPropertyByReflect(name);
+        }
+        return prop;
+    }
+
+    private static String getSystemPropertyByShell(final String propName) {
+        String line;
+        BufferedReader input = null;
+        try {
+            Process p = Runtime.getRuntime().exec("getprop " + propName);
+            input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
+            String ret = input.readLine();
+            if (ret != null) {
+                return ret;
+            }
+        } catch (IOException ignore) {
+        } finally {
+            if (input != null) {
+                try {
+                    input.close();
+                } catch (IOException ignore) {/**/}
+            }
+        }
+        return "";
+    }
+
+    private static String getSystemPropertyByStream(final String key) {
+        try {
+            Properties prop = new Properties();
+            FileInputStream is = new FileInputStream(
+                    new File(Environment.getRootDirectory(), "build.prop")
+            );
+            prop.load(is);
+            return prop.getProperty(key, "");
+        } catch (Exception ignore) {/**/}
+        return "";
+    }
+
+    private static String getSystemPropertyByReflect(String key) {
+        try {
+            @SuppressLint("PrivateApi")
+            Class<?> clz = Class.forName("android.os.SystemProperties");
+            Method getMethod = clz.getMethod("get", String.class, String.class);
+            return (String) getMethod.invoke(clz, key, "");
+        } catch (Exception e) {/**/}
+        return "";
+    }
+
+    public static class RomInfo {
+        private String name;
+        private String version;
+
+        public String getName() {
+            return name;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        @Override
+        public String toString() {
+            return "RomInfo{name=" + name +
+                    ", version=" + version + "}";
+        }
+    }
+}

+ 212 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SDCardUtils.java

@@ -0,0 +1,212 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.text.format.Formatter;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/11
+ *     desc  : utils about sdcard
+ * </pre>
+ */
+public final class SDCardUtils {
+
+    private SDCardUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether sdcard is enabled by environment.
+     *
+     * @return {@code true}: enabled<br>{@code false}: disabled
+     */
+    public static boolean isSDCardEnableByEnvironment() {
+        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
+    }
+
+    /**
+     * Return the path of sdcard by environment.
+     *
+     * @return the path of sdcard by environment
+     */
+    public static String getSDCardPathByEnvironment() {
+        if (isSDCardEnableByEnvironment()) {
+            return Environment.getExternalStorageDirectory().getAbsolutePath();
+        }
+        return "";
+    }
+
+    /**
+     * Return the information of sdcard.
+     *
+     * @return the information of sdcard
+     */
+    public static List<SDCardInfo> getSDCardInfo() {
+        List<SDCardInfo> paths = new ArrayList<>();
+        StorageManager sm = (StorageManager) Utils.getApp().getSystemService(Context.STORAGE_SERVICE);
+        if (sm == null) return paths;
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+            List<StorageVolume> storageVolumes = sm.getStorageVolumes();
+            try {
+                //noinspection JavaReflectionMemberAccess
+                Method getPathMethod = StorageVolume.class.getMethod("getPath");
+                for (StorageVolume storageVolume : storageVolumes) {
+                    boolean isRemovable = storageVolume.isRemovable();
+                    String state = storageVolume.getState();
+                    String path = (String) getPathMethod.invoke(storageVolume);
+                    paths.add(new SDCardInfo(path, state, isRemovable));
+                }
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
+                //noinspection JavaReflectionMemberAccess
+                Method getPathMethod = storageVolumeClazz.getMethod("getPath");
+                Method isRemovableMethod = storageVolumeClazz.getMethod("isRemovable");
+                //noinspection JavaReflectionMemberAccess
+                Method getVolumeStateMethod = StorageManager.class.getMethod("getVolumeState", String.class);
+                //noinspection JavaReflectionMemberAccess
+                Method getVolumeListMethod = StorageManager.class.getMethod("getVolumeList");
+                Object result = getVolumeListMethod.invoke(sm);
+                final int length = Array.getLength(result);
+                for (int i = 0; i < length; i++) {
+                    Object storageVolumeElement = Array.get(result, i);
+                    String path = (String) getPathMethod.invoke(storageVolumeElement);
+                    boolean isRemovable = (Boolean) isRemovableMethod.invoke(storageVolumeElement);
+                    String state = (String) getVolumeStateMethod.invoke(sm, path);
+                    paths.add(new SDCardInfo(path, state, isRemovable));
+                }
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        return paths;
+    }
+
+    /**
+     * Return the ptah of mounted sdcard.
+     *
+     * @return the ptah of mounted sdcard.
+     */
+    public static List<String> getMountedSDCardPath() {
+        List<String> path = new ArrayList<>();
+        List<SDCardInfo> sdCardInfo = getSDCardInfo();
+        if (sdCardInfo == null || sdCardInfo.isEmpty()) return path;
+        for (SDCardInfo cardInfo : sdCardInfo) {
+            String state = cardInfo.state;
+            if (state == null) continue;
+            if ("mounted".equals(state.toLowerCase())) {
+                path.add(cardInfo.path);
+            }
+        }
+        return path;
+    }
+
+
+    /**
+     * Return the total size of external storage
+     *
+     * @return the total size of external storage
+     */
+    public static long getExternalTotalSize() {
+        return UtilsBridge.getFsTotalSize(getSDCardPathByEnvironment());
+    }
+
+    /**
+     * Return the available size of external storage.
+     *
+     * @return the available size of external storage
+     */
+    public static long getExternalAvailableSize() {
+        return UtilsBridge.getFsAvailableSize(getSDCardPathByEnvironment());
+    }
+
+    /**
+     * Return the total size of internal storage
+     *
+     * @return the total size of internal storage
+     */
+    public static long getInternalTotalSize() {
+        return UtilsBridge.getFsTotalSize(Environment.getDataDirectory().getAbsolutePath());
+    }
+
+    /**
+     * Return the available size of internal storage.
+     *
+     * @return the available size of internal storage
+     */
+    public static long getInternalAvailableSize() {
+        return UtilsBridge.getFsAvailableSize(Environment.getDataDirectory().getAbsolutePath());
+    }
+
+    public static class SDCardInfo {
+
+        private String  path;
+        private String  state;
+        private boolean isRemovable;
+        private long    totalSize;
+        private long    availableSize;
+
+        SDCardInfo(String path, String state, boolean isRemovable) {
+            this.path = path;
+            this.state = state;
+            this.isRemovable = isRemovable;
+            this.totalSize = UtilsBridge.getFsTotalSize(path);
+            this.availableSize = UtilsBridge.getFsAvailableSize(path);
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String getState() {
+            return state;
+        }
+
+        public boolean isRemovable() {
+            return isRemovable;
+        }
+
+        public long getTotalSize() {
+            return totalSize;
+        }
+
+        public long getAvailableSize() {
+            return availableSize;
+        }
+
+        @Override
+        public String toString() {
+            return "SDCardInfo {" +
+                    "path = " + path +
+                    ", state = " + state +
+                    ", isRemovable = " + isRemovable +
+                    ", totalSize = " + Formatter.formatFileSize(Utils.getApp(), totalSize) +
+                    ", availableSize = " + Formatter.formatFileSize(Utils.getApp(), availableSize) +
+                    '}';
+        }
+    }
+}

+ 729 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SPStaticUtils.java

@@ -0,0 +1,729 @@
+package com.cooleshow.base.utils;
+
+import android.content.SharedPreferences;
+import androidx.annotation.NonNull;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/01/04
+ *     desc  : utils about shared preference
+ * </pre>
+ */
+public final class SPStaticUtils {
+
+    private static SPUtils sDefaultSPUtils;
+
+    /**
+     * Set the default instance of {@link SPUtils}.
+     *
+     * @param spUtils The default instance of {@link SPUtils}.
+     */
+    public static void setDefaultSPUtils(final SPUtils spUtils) {
+        sDefaultSPUtils = spUtils;
+    }
+
+    /**
+     * Put the string value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public static void put(@NonNull final String key, final String value) {
+        put(key, value, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the string value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void put(@NonNull final String key, final String value, final boolean isCommit) {
+        put(key, value, isCommit, getDefaultSPUtils());
+    }
+
+
+    /**
+     * Return the string value in sp.
+     *
+     * @param key The key of sp.
+     * @return the string value if sp exists or {@code ""} otherwise
+     */
+    public static String getString(@NonNull final String key) {
+        return getString(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the string value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the string value if sp exists or {@code defaultValue} otherwise
+     */
+    public static String getString(@NonNull final String key, final String defaultValue) {
+        return getString(key, defaultValue, getDefaultSPUtils());
+    }
+
+
+    /**
+     * Put the int value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public static void put(@NonNull final String key, final int value) {
+        put(key, value, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the int value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void put(@NonNull final String key, final int value, final boolean isCommit) {
+        put(key, value, isCommit, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the int value in sp.
+     *
+     * @param key The key of sp.
+     * @return the int value if sp exists or {@code -1} otherwise
+     */
+    public static int getInt(@NonNull final String key) {
+        return getInt(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the int value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the int value if sp exists or {@code defaultValue} otherwise
+     */
+    public static int getInt(@NonNull final String key, final int defaultValue) {
+        return getInt(key, defaultValue, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the long value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public static void put(@NonNull final String key, final long value) {
+        put(key, value, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the long value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void put(@NonNull final String key, final long value, final boolean isCommit) {
+        put(key, value, isCommit, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the long value in sp.
+     *
+     * @param key The key of sp.
+     * @return the long value if sp exists or {@code -1} otherwise
+     */
+    public static long getLong(@NonNull final String key) {
+        return getLong(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the long value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the long value if sp exists or {@code defaultValue} otherwise
+     */
+    public static long getLong(@NonNull final String key, final long defaultValue) {
+        return getLong(key, defaultValue, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the float value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public static void put(@NonNull final String key, final float value) {
+        put(key, value, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the float value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void put(@NonNull final String key, final float value, final boolean isCommit) {
+        put(key, value, isCommit, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the float value in sp.
+     *
+     * @param key The key of sp.
+     * @return the float value if sp exists or {@code -1f} otherwise
+     */
+    public static float getFloat(@NonNull final String key) {
+        return getFloat(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the float value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the float value if sp exists or {@code defaultValue} otherwise
+     */
+    public static float getFloat(@NonNull final String key, final float defaultValue) {
+        return getFloat(key, defaultValue, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the boolean value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public static void put(@NonNull final String key, final boolean value) {
+        put(key, value, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the boolean value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void put(@NonNull final String key, final boolean value, final boolean isCommit) {
+        put(key, value, isCommit, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the boolean value in sp.
+     *
+     * @param key The key of sp.
+     * @return the boolean value if sp exists or {@code false} otherwise
+     */
+    public static boolean getBoolean(@NonNull final String key) {
+        return getBoolean(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the boolean value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the boolean value if sp exists or {@code defaultValue} otherwise
+     */
+    public static boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
+        return getBoolean(key, defaultValue, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the set of string value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public static void put(@NonNull final String key, final Set<String> value) {
+        put(key, value, getDefaultSPUtils());
+    }
+
+    /**
+     * Put the set of string value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void put(@NonNull final String key,
+                           final Set<String> value,
+                           final boolean isCommit) {
+        put(key, value, isCommit, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the set of string value in sp.
+     *
+     * @param key The key of sp.
+     * @return the set of string value if sp exists
+     * or {@code Collections.<String>emptySet()} otherwise
+     */
+    public static Set<String> getStringSet(@NonNull final String key) {
+        return getStringSet(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Return the set of string value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the set of string value if sp exists or {@code defaultValue} otherwise
+     */
+    public static Set<String> getStringSet(@NonNull final String key,
+                                           final Set<String> defaultValue) {
+        return getStringSet(key, defaultValue, getDefaultSPUtils());
+    }
+
+    /**
+     * Return all values in sp.
+     *
+     * @return all values in sp
+     */
+    public static Map<String, ?> getAll() {
+        return getAll(getDefaultSPUtils());
+    }
+
+    /**
+     * Return whether the sp contains the preference.
+     *
+     * @param key The key of sp.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean contains(@NonNull final String key) {
+        return contains(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Remove the preference in sp.
+     *
+     * @param key The key of sp.
+     */
+    public static void remove(@NonNull final String key) {
+        remove(key, getDefaultSPUtils());
+    }
+
+    /**
+     * Remove the preference in sp.
+     *
+     * @param key      The key of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void remove(@NonNull final String key, final boolean isCommit) {
+        remove(key, isCommit, getDefaultSPUtils());
+    }
+
+    /**
+     * Remove all preferences in sp.
+     */
+    public static void clear() {
+        clear(getDefaultSPUtils());
+    }
+
+    /**
+     * Remove all preferences in sp.
+     *
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public static void clear(final boolean isCommit) {
+        clear(isCommit, getDefaultSPUtils());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // dividing line
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Put the string value in sp.
+     *
+     * @param key     The key of sp.
+     * @param value   The value of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key, final String value, @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value);
+    }
+
+    /**
+     * Put the string value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final String value,
+                           final boolean isCommit,
+                           @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value, isCommit);
+    }
+
+
+    /**
+     * Return the string value in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return the string value if sp exists or {@code ""} otherwise
+     */
+    public static String getString(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.getString(key);
+    }
+
+    /**
+     * Return the string value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @param spUtils      The instance of {@link SPUtils}.
+     * @return the string value if sp exists or {@code defaultValue} otherwise
+     */
+    public static String getString(@NonNull final String key,
+                                   final String defaultValue,
+                                   @NonNull final SPUtils spUtils) {
+        return spUtils.getString(key, defaultValue);
+    }
+
+
+    /**
+     * Put the int value in sp.
+     *
+     * @param key     The key of sp.
+     * @param value   The value of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key, final int value, @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value);
+    }
+
+    /**
+     * Put the int value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final int value,
+                           final boolean isCommit,
+                           @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value, isCommit);
+    }
+
+    /**
+     * Return the int value in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return the int value if sp exists or {@code -1} otherwise
+     */
+    public static int getInt(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.getInt(key);
+    }
+
+    /**
+     * Return the int value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @param spUtils      The instance of {@link SPUtils}.
+     * @return the int value if sp exists or {@code defaultValue} otherwise
+     */
+    public static int getInt(@NonNull final String key, final int defaultValue, @NonNull final SPUtils spUtils) {
+        return spUtils.getInt(key, defaultValue);
+    }
+
+    /**
+     * Put the long value in sp.
+     *
+     * @param key     The key of sp.
+     * @param value   The value of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key, final long value, @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value);
+    }
+
+    /**
+     * Put the long value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final long value,
+                           final boolean isCommit,
+                           @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value, isCommit);
+    }
+
+    /**
+     * Return the long value in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return the long value if sp exists or {@code -1} otherwise
+     */
+    public static long getLong(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.getLong(key);
+    }
+
+    /**
+     * Return the long value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @param spUtils      The instance of {@link SPUtils}.
+     * @return the long value if sp exists or {@code defaultValue} otherwise
+     */
+    public static long getLong(@NonNull final String key, final long defaultValue, @NonNull final SPUtils spUtils) {
+        return spUtils.getLong(key, defaultValue);
+    }
+
+    /**
+     * Put the float value in sp.
+     *
+     * @param key     The key of sp.
+     * @param value   The value of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key, final float value, @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value);
+    }
+
+    /**
+     * Put the float value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final float value,
+                           final boolean isCommit,
+                           @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value, isCommit);
+    }
+
+    /**
+     * Return the float value in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return the float value if sp exists or {@code -1f} otherwise
+     */
+    public static float getFloat(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.getFloat(key);
+    }
+
+    /**
+     * Return the float value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @param spUtils      The instance of {@link SPUtils}.
+     * @return the float value if sp exists or {@code defaultValue} otherwise
+     */
+    public static float getFloat(@NonNull final String key, final float defaultValue, @NonNull final SPUtils spUtils) {
+        return spUtils.getFloat(key, defaultValue);
+    }
+
+    /**
+     * Put the boolean value in sp.
+     *
+     * @param key     The key of sp.
+     * @param value   The value of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key, final boolean value, @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value);
+    }
+
+    /**
+     * Put the boolean value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final boolean value,
+                           final boolean isCommit,
+                           @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value, isCommit);
+    }
+
+    /**
+     * Return the boolean value in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return the boolean value if sp exists or {@code false} otherwise
+     */
+    public static boolean getBoolean(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.getBoolean(key);
+    }
+
+    /**
+     * Return the boolean value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @param spUtils      The instance of {@link SPUtils}.
+     * @return the boolean value if sp exists or {@code defaultValue} otherwise
+     */
+    public static boolean getBoolean(@NonNull final String key,
+                                     final boolean defaultValue,
+                                     @NonNull final SPUtils spUtils) {
+        return spUtils.getBoolean(key, defaultValue);
+    }
+
+    /**
+     * Put the set of string value in sp.
+     *
+     * @param key     The key of sp.
+     * @param value   The value of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key, final Set<String> value, @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value);
+    }
+
+    /**
+     * Put the set of string value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void put(@NonNull final String key,
+                           final Set<String> value,
+                           final boolean isCommit,
+                           @NonNull final SPUtils spUtils) {
+        spUtils.put(key, value, isCommit);
+    }
+
+    /**
+     * Return the set of string value in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return the set of string value if sp exists
+     * or {@code Collections.<String>emptySet()} otherwise
+     */
+    public static Set<String> getStringSet(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.getStringSet(key);
+    }
+
+    /**
+     * Return the set of string value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @param spUtils      The instance of {@link SPUtils}.
+     * @return the set of string value if sp exists or {@code defaultValue} otherwise
+     */
+    public static Set<String> getStringSet(@NonNull final String key,
+                                           final Set<String> defaultValue,
+                                           @NonNull final SPUtils spUtils) {
+        return spUtils.getStringSet(key, defaultValue);
+    }
+
+    /**
+     * Return all values in sp.
+     *
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return all values in sp
+     */
+    public static Map<String, ?> getAll(@NonNull final SPUtils spUtils) {
+        return spUtils.getAll();
+    }
+
+    /**
+     * Return whether the sp contains the preference.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean contains(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        return spUtils.contains(key);
+    }
+
+    /**
+     * Remove the preference in sp.
+     *
+     * @param key     The key of sp.
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void remove(@NonNull final String key, @NonNull final SPUtils spUtils) {
+        spUtils.remove(key);
+    }
+
+    /**
+     * Remove the preference in sp.
+     *
+     * @param key      The key of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void remove(@NonNull final String key, final boolean isCommit, @NonNull final SPUtils spUtils) {
+        spUtils.remove(key, isCommit);
+    }
+
+    /**
+     * Remove all preferences in sp.
+     *
+     * @param spUtils The instance of {@link SPUtils}.
+     */
+    public static void clear(@NonNull final SPUtils spUtils) {
+        spUtils.clear();
+    }
+
+    /**
+     * Remove all preferences in sp.
+     *
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     * @param spUtils  The instance of {@link SPUtils}.
+     */
+    public static void clear(final boolean isCommit, @NonNull final SPUtils spUtils) {
+        spUtils.clear(isCommit);
+    }
+
+    private static SPUtils getDefaultSPUtils() {
+        return sDefaultSPUtils != null ? sDefaultSPUtils : SPUtils.getInstance();
+    }
+}

+ 446 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SPUtils.java

@@ -0,0 +1,446 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about shared preference
+ * </pre>
+ */
+@SuppressLint("ApplySharedPref")
+public final class SPUtils {
+
+    private static final Map<String, SPUtils> SP_UTILS_MAP = new HashMap<>();
+
+    private SharedPreferences sp;
+
+    /**
+     * Return the single {@link SPUtils} instance
+     *
+     * @return the single {@link SPUtils} instance
+     */
+    public static SPUtils getInstance() {
+        return getInstance("", Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Return the single {@link SPUtils} instance
+     *
+     * @param mode Operating mode.
+     * @return the single {@link SPUtils} instance
+     */
+    public static SPUtils getInstance(final int mode) {
+        return getInstance("", mode);
+    }
+
+    /**
+     * Return the single {@link SPUtils} instance
+     *
+     * @param spName The name of sp.
+     * @return the single {@link SPUtils} instance
+     */
+    public static SPUtils getInstance(String spName) {
+        return getInstance(spName, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Return the single {@link SPUtils} instance
+     *
+     * @param spName The name of sp.
+     * @param mode   Operating mode.
+     * @return the single {@link SPUtils} instance
+     */
+    public static SPUtils getInstance(String spName, final int mode) {
+        if (isSpace(spName)) spName = "spUtils";
+        SPUtils spUtils = SP_UTILS_MAP.get(spName);
+        if (spUtils == null) {
+            synchronized (SPUtils.class) {
+                spUtils = SP_UTILS_MAP.get(spName);
+                if (spUtils == null) {
+                    spUtils = new SPUtils(spName, mode);
+                    SP_UTILS_MAP.put(spName, spUtils);
+                }
+            }
+        }
+        return spUtils;
+    }
+
+    private SPUtils(final String spName) {
+        sp = Utils.getApp().getSharedPreferences(spName, Context.MODE_PRIVATE);
+    }
+
+    private SPUtils(final String spName, final int mode) {
+        sp = Utils.getApp().getSharedPreferences(spName, mode);
+    }
+
+    /**
+     * Put the string value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public void put(@NonNull final String key, final String value) {
+        put(key, value, false);
+    }
+
+    /**
+     * Put the string value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void put(@NonNull final String key, final String value, final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().putString(key, value).commit();
+        } else {
+            sp.edit().putString(key, value).apply();
+        }
+    }
+
+    /**
+     * Return the string value in sp.
+     *
+     * @param key The key of sp.
+     * @return the string value if sp exists or {@code ""} otherwise
+     */
+    public String getString(@NonNull final String key) {
+        return getString(key, "");
+    }
+
+    /**
+     * Return the string value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the string value if sp exists or {@code defaultValue} otherwise
+     */
+    public String getString(@NonNull final String key, final String defaultValue) {
+        return sp.getString(key, defaultValue);
+    }
+
+    /**
+     * Put the int value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public void put(@NonNull final String key, final int value) {
+        put(key, value, false);
+    }
+
+    /**
+     * Put the int value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void put(@NonNull final String key, final int value, final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().putInt(key, value).commit();
+        } else {
+            sp.edit().putInt(key, value).apply();
+        }
+    }
+
+    /**
+     * Return the int value in sp.
+     *
+     * @param key The key of sp.
+     * @return the int value if sp exists or {@code -1} otherwise
+     */
+    public int getInt(@NonNull final String key) {
+        return getInt(key, -1);
+    }
+
+    /**
+     * Return the int value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the int value if sp exists or {@code defaultValue} otherwise
+     */
+    public int getInt(@NonNull final String key, final int defaultValue) {
+        return sp.getInt(key, defaultValue);
+    }
+
+    /**
+     * Put the long value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public void put(@NonNull final String key, final long value) {
+        put(key, value, false);
+    }
+
+    /**
+     * Put the long value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void put(@NonNull final String key, final long value, final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().putLong(key, value).commit();
+        } else {
+            sp.edit().putLong(key, value).apply();
+        }
+    }
+
+    /**
+     * Return the long value in sp.
+     *
+     * @param key The key of sp.
+     * @return the long value if sp exists or {@code -1} otherwise
+     */
+    public long getLong(@NonNull final String key) {
+        return getLong(key, -1L);
+    }
+
+    /**
+     * Return the long value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the long value if sp exists or {@code defaultValue} otherwise
+     */
+    public long getLong(@NonNull final String key, final long defaultValue) {
+        return sp.getLong(key, defaultValue);
+    }
+
+    /**
+     * Put the float value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public void put(@NonNull final String key, final float value) {
+        put(key, value, false);
+    }
+
+    /**
+     * Put the float value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void put(@NonNull final String key, final float value, final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().putFloat(key, value).commit();
+        } else {
+            sp.edit().putFloat(key, value).apply();
+        }
+    }
+
+    /**
+     * Return the float value in sp.
+     *
+     * @param key The key of sp.
+     * @return the float value if sp exists or {@code -1f} otherwise
+     */
+    public float getFloat(@NonNull final String key) {
+        return getFloat(key, -1f);
+    }
+
+    /**
+     * Return the float value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the float value if sp exists or {@code defaultValue} otherwise
+     */
+    public float getFloat(@NonNull final String key, final float defaultValue) {
+        return sp.getFloat(key, defaultValue);
+    }
+
+    /**
+     * Put the boolean value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public void put(@NonNull final String key, final boolean value) {
+        put(key, value, false);
+    }
+
+    /**
+     * Put the boolean value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void put(@NonNull final String key, final boolean value, final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().putBoolean(key, value).commit();
+        } else {
+            sp.edit().putBoolean(key, value).apply();
+        }
+    }
+
+    /**
+     * Return the boolean value in sp.
+     *
+     * @param key The key of sp.
+     * @return the boolean value if sp exists or {@code false} otherwise
+     */
+    public boolean getBoolean(@NonNull final String key) {
+        return getBoolean(key, false);
+    }
+
+    /**
+     * Return the boolean value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the boolean value if sp exists or {@code defaultValue} otherwise
+     */
+    public boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
+        return sp.getBoolean(key, defaultValue);
+    }
+
+    /**
+     * Put the set of string value in sp.
+     *
+     * @param key   The key of sp.
+     * @param value The value of sp.
+     */
+    public void put(@NonNull final String key, final Set<String> value) {
+        put(key, value, false);
+    }
+
+    /**
+     * Put the set of string value in sp.
+     *
+     * @param key      The key of sp.
+     * @param value    The value of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void put(@NonNull final String key,
+                    final Set<String> value,
+                    final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().putStringSet(key, value).commit();
+        } else {
+            sp.edit().putStringSet(key, value).apply();
+        }
+    }
+
+    /**
+     * Return the set of string value in sp.
+     *
+     * @param key The key of sp.
+     * @return the set of string value if sp exists
+     * or {@code Collections.<String>emptySet()} otherwise
+     */
+    public Set<String> getStringSet(@NonNull final String key) {
+        return getStringSet(key, Collections.<String>emptySet());
+    }
+
+    /**
+     * Return the set of string value in sp.
+     *
+     * @param key          The key of sp.
+     * @param defaultValue The default value if the sp doesn't exist.
+     * @return the set of string value if sp exists or {@code defaultValue} otherwise
+     */
+    public Set<String> getStringSet(@NonNull final String key,
+                                    final Set<String> defaultValue) {
+        return sp.getStringSet(key, defaultValue);
+    }
+
+    /**
+     * Return all values in sp.
+     *
+     * @return all values in sp
+     */
+    public Map<String, ?> getAll() {
+        return sp.getAll();
+    }
+
+    /**
+     * Return whether the sp contains the preference.
+     *
+     * @param key The key of sp.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public boolean contains(@NonNull final String key) {
+        return sp.contains(key);
+    }
+
+    /**
+     * Remove the preference in sp.
+     *
+     * @param key The key of sp.
+     */
+    public void remove(@NonNull final String key) {
+        remove(key, false);
+    }
+
+    /**
+     * Remove the preference in sp.
+     *
+     * @param key      The key of sp.
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void remove(@NonNull final String key, final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().remove(key).commit();
+        } else {
+            sp.edit().remove(key).apply();
+        }
+    }
+
+    /**
+     * Remove all preferences in sp.
+     */
+    public void clear() {
+        clear(false);
+    }
+
+    /**
+     * Remove all preferences in sp.
+     *
+     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},
+     *                 false to use {@link SharedPreferences.Editor#apply()}
+     */
+    public void clear(final boolean isCommit) {
+        if (isCommit) {
+            sp.edit().clear().commit();
+        } else {
+            sp.edit().clear().apply();
+        }
+    }
+
+    private static boolean isSpace(final String s) {
+        if (s == null) return true;
+        for (int i = 0, len = s.length(); i < len; ++i) {
+            if (!Character.isWhitespace(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 362 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ScreenUtils.java

@@ -0,0 +1,362 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.os.Build;
+import android.provider.Settings;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresPermission;
+import android.util.DisplayMetrics;
+import android.view.Surface;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import static android.Manifest.permission.WRITE_SETTINGS;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about screen
+ * </pre>
+ */
+public final class ScreenUtils {
+
+    private ScreenUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the width of screen, in pixel.
+     *
+     * @return the width of screen, in pixel
+     */
+    public static int getScreenWidth() {
+        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) return -1;
+        Point point = new Point();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            wm.getDefaultDisplay().getRealSize(point);
+        } else {
+            wm.getDefaultDisplay().getSize(point);
+        }
+        return point.x;
+    }
+
+    /**
+     * Return the height of screen, in pixel.
+     *
+     * @return the height of screen, in pixel
+     */
+    public static int getScreenHeight() {
+        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) return -1;
+        Point point = new Point();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            wm.getDefaultDisplay().getRealSize(point);
+        } else {
+            wm.getDefaultDisplay().getSize(point);
+        }
+        return point.y;
+    }
+
+    /**
+     * Return the application's width of screen, in pixel.
+     *
+     * @return the application's width of screen, in pixel
+     */
+    public static int getAppScreenWidth() {
+        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) return -1;
+        Point point = new Point();
+        wm.getDefaultDisplay().getSize(point);
+        return point.x;
+    }
+
+    /**
+     * Return the application's height of screen, in pixel.
+     *
+     * @return the application's height of screen, in pixel
+     */
+    public static int getAppScreenHeight() {
+        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) return -1;
+        Point point = new Point();
+        wm.getDefaultDisplay().getSize(point);
+        return point.y;
+    }
+
+    /**
+     * Return the density of screen.
+     *
+     * @return the density of screen
+     */
+    public static float getScreenDensity() {
+        return Resources.getSystem().getDisplayMetrics().density;
+    }
+
+    /**
+     * Return the screen density expressed as dots-per-inch.
+     *
+     * @return the screen density expressed as dots-per-inch
+     */
+    public static int getScreenDensityDpi() {
+        return Resources.getSystem().getDisplayMetrics().densityDpi;
+    }
+
+    /**
+     * Return the exact physical pixels per inch of the screen in the Y dimension.
+     *
+     * @return the exact physical pixels per inch of the screen in the Y dimension
+     */
+    public static float getScreenXDpi() {
+        return Resources.getSystem().getDisplayMetrics().xdpi;
+    }
+
+    /**
+     * Return the exact physical pixels per inch of the screen in the Y dimension.
+     *
+     * @return the exact physical pixels per inch of the screen in the Y dimension
+     */
+    public static float getScreenYDpi() {
+        return Resources.getSystem().getDisplayMetrics().ydpi;
+    }
+
+    /**
+     * Return the distance between the given View's X (start point of View's width) and the screen width.
+     *
+     * @return the distance between the given View's X (start point of View's width) and the screen width.
+     */
+    public int calculateDistanceByX(View view) {
+        int[] point = new int[2];
+        view.getLocationOnScreen(point);
+        return getScreenWidth() - point[0];
+    }
+
+    /**
+     * Return the distance between the given View's Y (start point of View's height) and the screen height.
+     *
+     * @return the distance between the given View's Y (start point of View's height) and the screen height.
+     */
+    public int calculateDistanceByY(View view) {
+        int[] point = new int[2];
+        view.getLocationOnScreen(point);
+        return getScreenHeight() - point[1];
+    }
+
+    /**
+     * Return the X coordinate of the given View on the screen.
+     *
+     * @return X coordinate of the given View on the screen.
+     */
+    public int getViewX(View view) {
+        int[] point = new int[2];
+        view.getLocationOnScreen(point);
+        return point[0];
+    }
+
+    /**
+     * Return the Y coordinate of the given View on the screen.
+     *
+     * @return Y coordinate of the given View on the screen.
+     */
+    public int getViewY(View view) {
+        int[] point = new int[2];
+        view.getLocationOnScreen(point);
+        return point[1];
+    }
+
+    /**
+     * Set full screen.
+     *
+     * @param activity The activity.
+     */
+    public static void setFullScreen(@NonNull final Activity activity) {
+        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+    }
+
+    /**
+     * Set non full screen.
+     *
+     * @param activity The activity.
+     */
+    public static void setNonFullScreen(@NonNull final Activity activity) {
+        activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+    }
+
+    /**
+     * Toggle full screen.
+     *
+     * @param activity The activity.
+     */
+    public static void toggleFullScreen(@NonNull final Activity activity) {
+        boolean isFullScreen = isFullScreen(activity);
+        Window window = activity.getWindow();
+        if (isFullScreen) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+    }
+
+    /**
+     * Return whether screen is full.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isFullScreen(@NonNull final Activity activity) {
+        int fullScreenFlag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
+        return (activity.getWindow().getAttributes().flags & fullScreenFlag) == fullScreenFlag;
+    }
+
+    /**
+     * Set the screen to landscape.
+     *
+     * @param activity The activity.
+     */
+    @SuppressLint("SourceLockedOrientationActivity")
+    public static void setLandscape(@NonNull final Activity activity) {
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+    }
+
+    /**
+     * Set the screen to portrait.
+     *
+     * @param activity The activity.
+     */
+    @SuppressLint("SourceLockedOrientationActivity")
+    public static void setPortrait(@NonNull final Activity activity) {
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    /**
+     * Return whether screen is landscape.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLandscape() {
+        return Utils.getApp().getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
+    }
+
+    /**
+     * Return whether screen is portrait.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPortrait() {
+        return Utils.getApp().getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_PORTRAIT;
+    }
+
+    /**
+     * Return the rotation of screen.
+     *
+     * @param activity The activity.
+     * @return the rotation of screen
+     */
+    public static int getScreenRotation(@NonNull final Activity activity) {
+        switch (activity.getWindowManager().getDefaultDisplay().getRotation()) {
+            case Surface.ROTATION_0:
+                return 0;
+            case Surface.ROTATION_90:
+                return 90;
+            case Surface.ROTATION_180:
+                return 180;
+            case Surface.ROTATION_270:
+                return 270;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Return the bitmap of screen.
+     *
+     * @param activity The activity.
+     * @return the bitmap of screen
+     */
+    public static Bitmap screenShot(@NonNull final Activity activity) {
+        return screenShot(activity, false);
+    }
+
+    /**
+     * Return the bitmap of screen.
+     *
+     * @param activity          The activity.
+     * @param isDeleteStatusBar True to delete status bar, false otherwise.
+     * @return the bitmap of screen
+     */
+    public static Bitmap screenShot(@NonNull final Activity activity, boolean isDeleteStatusBar) {
+        View decorView = activity.getWindow().getDecorView();
+        Bitmap bmp = UtilsBridge.view2Bitmap(decorView);
+        DisplayMetrics dm = new DisplayMetrics();
+        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
+        if (isDeleteStatusBar) {
+            int statusBarHeight = UtilsBridge.getStatusBarHeight();
+            return Bitmap.createBitmap(
+                    bmp,
+                    0,
+                    statusBarHeight,
+                    dm.widthPixels,
+                    dm.heightPixels - statusBarHeight
+            );
+        } else {
+            return Bitmap.createBitmap(bmp, 0, 0, dm.widthPixels, dm.heightPixels);
+        }
+    }
+
+    /**
+     * Return whether screen is locked.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isScreenLock() {
+        KeyguardManager km =
+                (KeyguardManager) Utils.getApp().getSystemService(Context.KEYGUARD_SERVICE);
+        if (km == null) return false;
+        return km.inKeyguardRestrictedInputMode();
+    }
+
+    /**
+     * Set the duration of sleep.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.WRITE_SETTINGS" />}</p>
+     *
+     * @param duration The duration.
+     */
+    @RequiresPermission(WRITE_SETTINGS)
+    public static void setSleepDuration(final int duration) {
+        Settings.System.putInt(
+                Utils.getApp().getContentResolver(),
+                Settings.System.SCREEN_OFF_TIMEOUT,
+                duration
+        );
+    }
+
+    /**
+     * Return the duration of sleep.
+     *
+     * @return the duration of sleep.
+     */
+    public static int getSleepDuration() {
+        try {
+            return Settings.System.getInt(
+                    Utils.getApp().getContentResolver(),
+                    Settings.System.SCREEN_OFF_TIMEOUT
+            );
+        } catch (Settings.SettingNotFoundException e) {
+            e.printStackTrace();
+            return -123;
+        }
+    }
+}

+ 238 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ServiceUtils.java

@@ -0,0 +1,238 @@
+package com.cooleshow.base.utils;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about service
+ * </pre>
+ */
+public final class ServiceUtils {
+
+    private ServiceUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return all of the services are running.
+     *
+     * @return all of the services are running
+     */
+    public static Set<String> getAllRunningServices() {
+        ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+        List<RunningServiceInfo> info = am.getRunningServices(0x7FFFFFFF);
+        Set<String> names = new HashSet<>();
+        if (info == null || info.size() == 0) return null;
+        for (RunningServiceInfo aInfo : info) {
+            names.add(aInfo.service.getClassName());
+        }
+        return names;
+    }
+
+    /**
+     * Start the service.
+     *
+     * @param className The name of class.
+     */
+    public static void startService(@NonNull final String className) {
+        try {
+            startService(Class.forName(className));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Start the service.
+     *
+     * @param cls The service class.
+     */
+    public static void startService(@NonNull final Class<?> cls) {
+        startService(new Intent(Utils.getApp(), cls));
+    }
+
+    /**
+     * Start the service.
+     *
+     * @param intent The intent.
+     */
+    public static void startService(Intent intent) {
+        try {
+            intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                Utils.getApp().startForegroundService(intent);
+            } else {
+                Utils.getApp().startService(intent);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Stop the service.
+     *
+     * @param className The name of class.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean stopService(@NonNull final String className) {
+        try {
+            return stopService(Class.forName(className));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Stop the service.
+     *
+     * @param cls The name of class.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean stopService(@NonNull final Class<?> cls) {
+        return stopService(new Intent(Utils.getApp(), cls));
+    }
+
+    /**
+     * Stop the service.
+     *
+     * @param intent The intent.
+     * @return {@code true}: success<br>{@code false}: fail
+     */
+    public static boolean stopService(@NonNull Intent intent) {
+        try {
+            return Utils.getApp().stopService(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Bind the service.
+     *
+     * @param className The name of class.
+     * @param conn      The ServiceConnection object.
+     * @param flags     Operation options for the binding.
+     *                  <ul>
+     *                  <li>0</li>
+     *                  <li>{@link Context#BIND_AUTO_CREATE}</li>
+     *                  <li>{@link Context#BIND_DEBUG_UNBIND}</li>
+     *                  <li>{@link Context#BIND_NOT_FOREGROUND}</li>
+     *                  <li>{@link Context#BIND_ABOVE_CLIENT}</li>
+     *                  <li>{@link Context#BIND_ALLOW_OOM_MANAGEMENT}</li>
+     *                  <li>{@link Context#BIND_WAIVE_PRIORITY}</li>
+     *                  </ul>
+     */
+    public static void bindService(@NonNull final String className,
+                                   @NonNull final ServiceConnection conn,
+                                   final int flags) {
+        try {
+            bindService(Class.forName(className), conn, flags);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Bind the service.
+     *
+     * @param cls   The service class.
+     * @param conn  The ServiceConnection object.
+     * @param flags Operation options for the binding.
+     *              <ul>
+     *              <li>0</li>
+     *              <li>{@link Context#BIND_AUTO_CREATE}</li>
+     *              <li>{@link Context#BIND_DEBUG_UNBIND}</li>
+     *              <li>{@link Context#BIND_NOT_FOREGROUND}</li>
+     *              <li>{@link Context#BIND_ABOVE_CLIENT}</li>
+     *              <li>{@link Context#BIND_ALLOW_OOM_MANAGEMENT}</li>
+     *              <li>{@link Context#BIND_WAIVE_PRIORITY}</li>
+     *              </ul>
+     */
+    public static void bindService(@NonNull final Class<?> cls,
+                                   @NonNull final ServiceConnection conn,
+                                   final int flags) {
+        bindService(new Intent(Utils.getApp(), cls), conn, flags);
+    }
+
+    /**
+     * Bind the service.
+     *
+     * @param intent The intent.
+     * @param conn   The ServiceConnection object.
+     * @param flags  Operation options for the binding.
+     *               <ul>
+     *               <li>0</li>
+     *               <li>{@link Context#BIND_AUTO_CREATE}</li>
+     *               <li>{@link Context#BIND_DEBUG_UNBIND}</li>
+     *               <li>{@link Context#BIND_NOT_FOREGROUND}</li>
+     *               <li>{@link Context#BIND_ABOVE_CLIENT}</li>
+     *               <li>{@link Context#BIND_ALLOW_OOM_MANAGEMENT}</li>
+     *               <li>{@link Context#BIND_WAIVE_PRIORITY}</li>
+     *               </ul>
+     */
+    public static void bindService(@NonNull final Intent intent,
+                                   @NonNull final ServiceConnection conn,
+                                   final int flags) {
+        try {
+            Utils.getApp().bindService(intent, conn, flags);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Unbind the service.
+     *
+     * @param conn The ServiceConnection object.
+     */
+    public static void unbindService(@NonNull final ServiceConnection conn) {
+        Utils.getApp().unbindService(conn);
+    }
+
+    /**
+     * Return whether service is running.
+     *
+     * @param cls The service class.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isServiceRunning(@NonNull final Class<?> cls) {
+        return isServiceRunning(cls.getName());
+    }
+
+    /**
+     * Return whether service is running.
+     *
+     * @param className The name of class.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isServiceRunning(@NonNull final String className) {
+        try {
+            ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+            List<RunningServiceInfo> info = am.getRunningServices(0x7FFFFFFF);
+            if (info == null || info.size() == 0) return false;
+            for (RunningServiceInfo aInfo : info) {
+                if (className.equals(aInfo.service.getClassName())) return true;
+            }
+            return false;
+        } catch (Exception ignore) {
+            return false;
+        }
+    }
+}

+ 691 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ShadowUtils.java

@@ -0,0 +1,691 @@
+package com.cooleshow.base.utils;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.StateSet;
+import android.view.View;
+
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.view.ViewCompat;
+
+/**
+ * <pre>
+ *     author: blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/09/13
+ *     desc  : utils about shadow
+ * </pre>
+ */
+public class ShadowUtils {
+
+    private static final int SHADOW_TAG = -16;
+
+    public static void apply(View... views) {
+        if (views == null) return;
+        for (View view : views) {
+            apply(view, new Config());
+        }
+    }
+
+    public static void apply(View view, Config builder) {
+        if (view == null || builder == null) return;
+        Drawable background = view.getBackground();
+        Object tag = view.getTag(SHADOW_TAG);
+        if (tag instanceof Drawable) {
+            ViewCompat.setBackground(view, (Drawable) tag);
+        } else {
+            background = builder.apply(background);
+            ViewCompat.setBackground(view, background);
+            view.setTag(SHADOW_TAG, background);
+        }
+    }
+
+    public static class Config {
+
+        private static final int SHADOW_COLOR_DEFAULT = 0x44000000;
+        private static final int SHADOW_SIZE          = UtilsBridge.dp2px(8);
+
+        private float   mShadowRadius         = -1;
+        private float   mShadowSizeNormal     = -1;
+        private float   mShadowSizePressed    = -1;
+        private float   mShadowMaxSizeNormal  = -1;
+        private float   mShadowMaxSizePressed = -1;
+        private int     mShadowColorNormal    = SHADOW_COLOR_DEFAULT;
+        private int     mShadowColorPressed   = SHADOW_COLOR_DEFAULT;
+        private boolean isCircle              = false;
+
+        public Config() {
+        }
+
+        public Config setShadowRadius(float radius) {
+            this.mShadowRadius = radius;
+            if (isCircle) {
+                throw new IllegalArgumentException("Set circle needn't set radius.");
+            }
+            return this;
+        }
+
+        public Config setCircle() {
+            isCircle = true;
+            if (mShadowRadius != -1) {
+                throw new IllegalArgumentException("Set circle needn't set radius.");
+            }
+            return this;
+        }
+
+        public Config setShadowSize(int size) {
+            return setShadowSize(size, size);
+        }
+
+        public Config setShadowSize(int sizeNormal, int sizePressed) {
+            this.mShadowSizeNormal = sizeNormal;
+            this.mShadowSizePressed = sizePressed;
+            return this;
+        }
+
+        public Config setShadowMaxSize(int maxSize) {
+            return setShadowMaxSize(maxSize, maxSize);
+        }
+
+        public Config setShadowMaxSize(int maxSizeNormal, int maxSizePressed) {
+            this.mShadowMaxSizeNormal = maxSizeNormal;
+            this.mShadowMaxSizePressed = maxSizePressed;
+            return this;
+        }
+
+        public Config setShadowColor(int color) {
+            return setShadowColor(color, color);
+        }
+
+        public Config setShadowColor(int colorNormal, int colorPressed) {
+            this.mShadowColorNormal = colorNormal;
+            this.mShadowColorPressed = colorPressed;
+            return this;
+        }
+
+        Drawable apply(Drawable src) {
+            if (src == null) {
+                src = new ColorDrawable(Color.TRANSPARENT);
+            }
+            StateListDrawable drawable = new StateListDrawable();
+            drawable.addState(
+                    new int[]{android.R.attr.state_pressed},
+                    new ShadowDrawable(src, getShadowRadius(), getShadowSizeNormal(),
+                            getShadowMaxSizeNormal(), mShadowColorPressed, isCircle)
+            );
+            drawable.addState(
+                    StateSet.WILD_CARD,
+                    new ShadowDrawable(src, getShadowRadius(), getShadowSizePressed(),
+                            getShadowMaxSizePressed(), mShadowColorNormal, isCircle)
+            );
+            return drawable;
+        }
+
+        private float getShadowRadius() {
+            if (mShadowRadius < 0) {
+                mShadowRadius = 0;
+            }
+            return mShadowRadius;
+        }
+
+        private float getShadowSizeNormal() {
+            if (mShadowSizeNormal == -1) {
+                mShadowSizeNormal = SHADOW_SIZE;
+            }
+            return mShadowSizeNormal;
+        }
+
+        private float getShadowSizePressed() {
+            if (mShadowSizePressed == -1) {
+                mShadowSizePressed = getShadowSizeNormal();
+            }
+            return mShadowSizePressed;
+        }
+
+        private float getShadowMaxSizeNormal() {
+            if (mShadowMaxSizeNormal == -1) {
+                mShadowMaxSizeNormal = getShadowSizeNormal();
+            }
+            return mShadowMaxSizeNormal;
+        }
+
+        private float getShadowMaxSizePressed() {
+            if (mShadowMaxSizePressed == -1) {
+                mShadowMaxSizePressed = getShadowSizePressed();
+            }
+            return mShadowMaxSizePressed;
+        }
+    }
+
+    public static class ShadowDrawable extends DrawableWrapper {
+        // used to calculate content padding
+        private static final double COS_45 = Math.cos(Math.toRadians(45));
+
+        private float mShadowMultiplier = 1f;
+
+        private float mShadowTopScale    = 1f;
+        private float mShadowHorizScale  = 1f;
+        private float mShadowBottomScale = 1f;
+
+        private Paint mCornerShadowPaint;
+        private Paint mEdgeShadowPaint;
+
+        private RectF mContentBounds;
+
+        private float mCornerRadius;
+
+        private Path mCornerShadowPath;
+
+        // updated value with inset
+        private float mMaxShadowSize;
+        // actual value set by developer
+        private float mRawMaxShadowSize;
+
+        // multiplied value to account for shadow offset
+        private float mShadowSize;
+        // actual value set by developer
+        private float mRawShadowSize;
+
+        private boolean mDirty = true;
+
+        private final int mShadowStartColor;
+        private final int mShadowEndColor;
+
+        private boolean mAddPaddingForCorners = false;
+
+        private float mRotation;
+
+        private boolean isCircle;
+
+        public ShadowDrawable(Drawable content, float radius,
+                              float shadowSize, float maxShadowSize, int shadowColor, boolean isCircle) {
+            super(content);
+            mShadowStartColor = shadowColor;
+            mShadowEndColor = mShadowStartColor & 0x00ffffff;
+            this.isCircle = isCircle;
+            if (isCircle) {
+                mShadowMultiplier = 1;
+                mShadowTopScale = 1;
+                mShadowHorizScale = 1;
+                mShadowBottomScale = 1;
+            }
+
+            mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+            mCornerShadowPaint.setStyle(Paint.Style.FILL);
+            mCornerRadius = Math.round(radius);
+            mContentBounds = new RectF();
+            mEdgeShadowPaint = new Paint(mCornerShadowPaint);
+            mEdgeShadowPaint.setAntiAlias(false);
+            setShadowSize(shadowSize, maxShadowSize);
+        }
+
+        /**
+         * Casts the value to an even integer.
+         */
+        private static int toEven(float value) {
+            int i = Math.round(value);
+            return (i % 2 == 1) ? i - 1 : i;
+        }
+
+        public void setAddPaddingForCorners(boolean addPaddingForCorners) {
+            mAddPaddingForCorners = addPaddingForCorners;
+            invalidateSelf();
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            super.setAlpha(alpha);
+            mCornerShadowPaint.setAlpha(alpha);
+            mEdgeShadowPaint.setAlpha(alpha);
+        }
+
+        @Override
+        protected void onBoundsChange(Rect bounds) {
+            mDirty = true;
+        }
+
+        void setShadowSize(float shadowSize, float maxShadowSize) {
+            if (shadowSize < 0 || maxShadowSize < 0) {
+                throw new IllegalArgumentException("invalid shadow size");
+            }
+            shadowSize = toEven(shadowSize);
+            maxShadowSize = toEven(maxShadowSize);
+            if (shadowSize > maxShadowSize) {
+                shadowSize = maxShadowSize;
+            }
+            if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
+                return;
+            }
+            mRawShadowSize = shadowSize;
+            mRawMaxShadowSize = maxShadowSize;
+            mShadowSize = Math.round(shadowSize * mShadowMultiplier);
+            mMaxShadowSize = maxShadowSize;
+            mDirty = true;
+            invalidateSelf();
+        }
+
+        @Override
+        public boolean getPadding(Rect padding) {
+            int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
+                    mAddPaddingForCorners));
+            int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
+                    mAddPaddingForCorners));
+            padding.set(hOffset, vOffset, hOffset, vOffset);
+            return true;
+        }
+
+        private float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
+                                               boolean addPaddingForCorners) {
+            if (addPaddingForCorners) {
+                return (float) (maxShadowSize * mShadowMultiplier + (1 - COS_45) * cornerRadius);
+            } else {
+                return maxShadowSize * mShadowMultiplier;
+            }
+        }
+
+        private static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
+                                                        boolean addPaddingForCorners) {
+            if (addPaddingForCorners) {
+                return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
+            } else {
+                return maxShadowSize;
+            }
+        }
+
+        @Override
+        public int getOpacity() {
+            return PixelFormat.TRANSLUCENT;
+        }
+
+        public void setCornerRadius(float radius) {
+            radius = Math.round(radius);
+            if (mCornerRadius == radius) {
+                return;
+            }
+            mCornerRadius = radius;
+            mDirty = true;
+            invalidateSelf();
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            if (mDirty) {
+                buildComponents(getBounds());
+                mDirty = false;
+            }
+            drawShadow(canvas);
+
+            super.draw(canvas);
+        }
+
+        final void setRotation(float rotation) {
+            if (mRotation != rotation) {
+                mRotation = rotation;
+                invalidateSelf();
+            }
+        }
+
+        private void drawShadow(Canvas canvas) {
+            if (isCircle) {
+                int saved = canvas.save();
+                canvas.translate(mContentBounds.centerX(), mContentBounds.centerY());
+                canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+                canvas.restoreToCount(saved);
+                return;
+            }
+
+            final int rotateSaved = canvas.save();
+            canvas.rotate(mRotation, mContentBounds.centerX(), mContentBounds.centerY());
+
+            final float edgeShadowTop = -mCornerRadius - mShadowSize;
+            final float shadowOffset = mCornerRadius;
+            final boolean drawHorizontalEdges = mContentBounds.width() - 2 * shadowOffset > 0;
+            final boolean drawVerticalEdges = mContentBounds.height() - 2 * shadowOffset > 0;
+
+            final float shadowOffsetTop = mRawShadowSize - (mRawShadowSize * mShadowTopScale);
+            final float shadowOffsetHorizontal = mRawShadowSize - (mRawShadowSize * mShadowHorizScale);
+            final float shadowOffsetBottom = mRawShadowSize - (mRawShadowSize * mShadowBottomScale);
+
+            final float shadowScaleHorizontal = shadowOffset == 0 ? 1 : shadowOffset / (shadowOffset + shadowOffsetHorizontal);
+            final float shadowScaleTop = shadowOffset == 0 ? 1 : shadowOffset / (shadowOffset + shadowOffsetTop);
+            final float shadowScaleBottom = shadowOffset == 0 ? 1 : shadowOffset / (shadowOffset + shadowOffsetBottom);
+
+            // LT
+            int saved = canvas.save();
+            canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.top + shadowOffset);
+            canvas.scale(shadowScaleHorizontal, shadowScaleTop);
+            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+            if (drawHorizontalEdges) {
+                // TE
+                canvas.scale(1f / shadowScaleHorizontal, 1f);
+                canvas.drawRect(0, edgeShadowTop,
+                        mContentBounds.width() - 2 * shadowOffset, -mCornerRadius,
+                        mEdgeShadowPaint);
+            }
+            canvas.restoreToCount(saved);
+            // RB
+            saved = canvas.save();
+            canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.bottom - shadowOffset);
+            canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
+            canvas.rotate(180f);
+            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+            if (drawHorizontalEdges) {
+                // BE
+                canvas.scale(1f / shadowScaleHorizontal, 1f);
+                canvas.drawRect(0, edgeShadowTop,
+                        mContentBounds.width() - 2 * shadowOffset, -mCornerRadius,
+                        mEdgeShadowPaint);
+            }
+            canvas.restoreToCount(saved);
+            // LB
+            saved = canvas.save();
+            canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.bottom - shadowOffset);
+            canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
+            canvas.rotate(270f);
+            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+            if (drawVerticalEdges) {
+                // LE
+                canvas.scale(1f / shadowScaleBottom, 1f);
+                canvas.drawRect(0, edgeShadowTop,
+                        mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint);
+            }
+            canvas.restoreToCount(saved);
+            // RT
+            saved = canvas.save();
+            canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.top + shadowOffset);
+            canvas.scale(shadowScaleHorizontal, shadowScaleTop);
+            canvas.rotate(90f);
+            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+            if (drawVerticalEdges) {
+                // RE
+                canvas.scale(1f / shadowScaleTop, 1f);
+                canvas.drawRect(0, edgeShadowTop,
+                        mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint);
+            }
+            canvas.restoreToCount(saved);
+
+            canvas.restoreToCount(rotateSaved);
+        }
+
+        private void buildShadowCorners() {
+            if (isCircle) {
+                float size = mContentBounds.width() / 2 - 1f;
+                RectF innerBounds = new RectF(-size, -size, size, size);
+                RectF outerBounds = new RectF(innerBounds);
+                outerBounds.inset(-mShadowSize, -mShadowSize);
+
+                if (mCornerShadowPath == null) {
+                    mCornerShadowPath = new Path();
+                } else {
+                    mCornerShadowPath.reset();
+                }
+                mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
+                mCornerShadowPath.moveTo(-size, 0);
+                mCornerShadowPath.rLineTo(-mShadowSize, 0);
+                // outer arc
+                mCornerShadowPath.arcTo(outerBounds, 180f, 180f, false);
+                mCornerShadowPath.arcTo(outerBounds, 0f, 180f, false);
+                // inner arc
+                mCornerShadowPath.arcTo(innerBounds, 180f, 180f, false);
+                mCornerShadowPath.arcTo(innerBounds, 0f, 180f, false);
+                mCornerShadowPath.close();
+
+                float shadowRadius = -outerBounds.top;
+                if (shadowRadius > 0f) {
+                    float startRatio = size / shadowRadius;
+                    mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius,
+                            new int[]{0, mShadowStartColor, mShadowEndColor}, new float[]{0.0F, startRatio, 1.0F},
+                            Shader.TileMode.CLAMP));
+                }
+                return;
+            }
+
+            RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
+            RectF outerBounds = new RectF(innerBounds);
+            outerBounds.inset(-mShadowSize, -mShadowSize);
+
+            if (mCornerShadowPath == null) {
+                mCornerShadowPath = new Path();
+            } else {
+                mCornerShadowPath.reset();
+            }
+            mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
+            mCornerShadowPath.moveTo(-mCornerRadius, 0);
+            mCornerShadowPath.rLineTo(-mShadowSize, 0);
+            // outer arc
+            mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
+            // inner arc
+            mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
+            mCornerShadowPath.close();
+
+            float shadowRadius = -outerBounds.top;
+            if (shadowRadius > 0f) {
+                float startRatio = mCornerRadius / shadowRadius;
+                mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius,
+                        new int[]{0, mShadowStartColor, mShadowEndColor}, new float[]{0F, startRatio, 1F},
+                        Shader.TileMode.CLAMP));
+            }
+
+            // we offset the content shadowSize/2 pixels up to make it more realistic.
+            // this is why edge shadow shader has some extra space
+            // When drawing bottom edge shadow, we use that extra space.
+            mEdgeShadowPaint.setShader(new LinearGradient(0, innerBounds.top, 0, outerBounds.top,
+                    mShadowStartColor, mShadowEndColor, Shader.TileMode.CLAMP));
+            mEdgeShadowPaint.setAntiAlias(false);
+        }
+
+        private void buildComponents(Rect bounds) {
+            // Card is offset mShadowMultiplier * maxShadowSize to account for the shadow shift.
+            // We could have different top-bottom offsets to avoid extra gap above but in that case
+            // center aligning Views inside the CardView would be problematic.
+            if (isCircle) {
+                mCornerRadius = bounds.width() / 2;
+            }
+            final float verticalOffset = mRawMaxShadowSize * mShadowMultiplier;
+            mContentBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
+                    bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
+
+            getWrappedDrawable().setBounds((int) mContentBounds.left, (int) mContentBounds.top,
+                    (int) mContentBounds.right, (int) mContentBounds.bottom);
+            buildShadowCorners();
+        }
+
+        public float getCornerRadius() {
+            return mCornerRadius;
+        }
+
+        public void setShadowSize(float size) {
+            setShadowSize(size, mRawMaxShadowSize);
+        }
+
+        public void setMaxShadowSize(float size) {
+            setShadowSize(mRawShadowSize, size);
+        }
+
+        public float getShadowSize() {
+            return mRawShadowSize;
+        }
+
+        public float getMaxShadowSize() {
+            return mRawMaxShadowSize;
+        }
+
+        public float getMinWidth() {
+            final float content = 2 *
+                    Math.max(mRawMaxShadowSize, mCornerRadius + mRawMaxShadowSize / 2);
+            return content + mRawMaxShadowSize * 2;
+        }
+
+        public float getMinHeight() {
+            final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius
+                    + mRawMaxShadowSize * mShadowMultiplier / 2);
+            return content + (mRawMaxShadowSize * mShadowMultiplier) * 2;
+        }
+    }
+
+    static class DrawableWrapper extends Drawable implements Drawable.Callback {
+        private Drawable mDrawable;
+
+        public DrawableWrapper(Drawable drawable) {
+            this.setWrappedDrawable(drawable);
+        }
+
+        public void draw(Canvas canvas) {
+            this.mDrawable.draw(canvas);
+        }
+
+        protected void onBoundsChange(Rect bounds) {
+            this.mDrawable.setBounds(bounds);
+        }
+
+        public void setChangingConfigurations(int configs) {
+            this.mDrawable.setChangingConfigurations(configs);
+        }
+
+        public int getChangingConfigurations() {
+            return this.mDrawable.getChangingConfigurations();
+        }
+
+        public void setDither(boolean dither) {
+            this.mDrawable.setDither(dither);
+        }
+
+        public void setFilterBitmap(boolean filter) {
+            this.mDrawable.setFilterBitmap(filter);
+        }
+
+        public void setAlpha(int alpha) {
+            this.mDrawable.setAlpha(alpha);
+        }
+
+        public void setColorFilter(ColorFilter cf) {
+            this.mDrawable.setColorFilter(cf);
+        }
+
+        public boolean isStateful() {
+            return this.mDrawable.isStateful();
+        }
+
+        public boolean setState(int[] stateSet) {
+            return this.mDrawable.setState(stateSet);
+        }
+
+        public int[] getState() {
+            return this.mDrawable.getState();
+        }
+
+        public void jumpToCurrentState() {
+            DrawableCompat.jumpToCurrentState(this.mDrawable);
+        }
+
+        public Drawable getCurrent() {
+            return this.mDrawable.getCurrent();
+        }
+
+        public boolean setVisible(boolean visible, boolean restart) {
+            return super.setVisible(visible, restart) || this.mDrawable.setVisible(visible, restart);
+        }
+
+        public int getOpacity() {
+            return this.mDrawable.getOpacity();
+        }
+
+        public Region getTransparentRegion() {
+            return this.mDrawable.getTransparentRegion();
+        }
+
+        public int getIntrinsicWidth() {
+            return this.mDrawable.getIntrinsicWidth();
+        }
+
+        public int getIntrinsicHeight() {
+            return this.mDrawable.getIntrinsicHeight();
+        }
+
+        public int getMinimumWidth() {
+            return this.mDrawable.getMinimumWidth();
+        }
+
+        public int getMinimumHeight() {
+            return this.mDrawable.getMinimumHeight();
+        }
+
+        public boolean getPadding(Rect padding) {
+            return this.mDrawable.getPadding(padding);
+        }
+
+        public void invalidateDrawable(Drawable who) {
+            this.invalidateSelf();
+        }
+
+        public void scheduleDrawable(Drawable who, Runnable what, long when) {
+            this.scheduleSelf(what, when);
+        }
+
+        public void unscheduleDrawable(Drawable who, Runnable what) {
+            this.unscheduleSelf(what);
+        }
+
+        protected boolean onLevelChange(int level) {
+            return this.mDrawable.setLevel(level);
+        }
+
+        public void setAutoMirrored(boolean mirrored) {
+            DrawableCompat.setAutoMirrored(this.mDrawable, mirrored);
+        }
+
+        public boolean isAutoMirrored() {
+            return DrawableCompat.isAutoMirrored(this.mDrawable);
+        }
+
+        public void setTint(int tint) {
+            DrawableCompat.setTint(this.mDrawable, tint);
+        }
+
+        public void setTintList(ColorStateList tint) {
+            DrawableCompat.setTintList(this.mDrawable, tint);
+        }
+
+        public void setTintMode(Mode tintMode) {
+            DrawableCompat.setTintMode(this.mDrawable, tintMode);
+        }
+
+        public void setHotspot(float x, float y) {
+            DrawableCompat.setHotspot(this.mDrawable, x, y);
+        }
+
+        public void setHotspotBounds(int left, int top, int right, int bottom) {
+            DrawableCompat.setHotspotBounds(this.mDrawable, left, top, right, bottom);
+        }
+
+        public Drawable getWrappedDrawable() {
+            return this.mDrawable;
+        }
+
+        public void setWrappedDrawable(Drawable drawable) {
+            if (this.mDrawable != null) {
+                this.mDrawable.setCallback((Callback) null);
+            }
+
+            this.mDrawable = drawable;
+            if (drawable != null) {
+                drawable.setCallback(this);
+            }
+        }
+    }
+}

+ 386 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ShellUtils.java

@@ -0,0 +1,386 @@
+package com.cooleshow.base.utils;
+
+import androidx.annotation.NonNull;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/07
+ *     desc  : utils about shell
+ * </pre>
+ */
+public final class ShellUtils {
+
+    private static final String LINE_SEP = System.getProperty("line.separator");
+
+    private ShellUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Execute the command asynchronously.
+     *
+     * @param command  The command.
+     * @param isRooted True to use root, false otherwise.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    public static Utils.Task<CommandResult> execCmdAsync(final String command,
+                                                         final boolean isRooted,
+                                                         final Utils.Consumer<CommandResult> consumer) {
+        return execCmdAsync(new String[]{command}, isRooted, true, consumer);
+    }
+
+    /**
+     * Execute the command asynchronously.
+     *
+     * @param commands The commands.
+     * @param isRooted True to use root, false otherwise.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    public static Utils.Task<CommandResult> execCmdAsync(final List<String> commands,
+                                                         final boolean isRooted,
+                                                         final Utils.Consumer<CommandResult> consumer) {
+        return execCmdAsync(commands == null ? null : commands.toArray(new String[]{}), isRooted, true, consumer);
+    }
+
+    /**
+     * Execute the command asynchronously.
+     *
+     * @param commands The commands.
+     * @param isRooted True to use root, false otherwise.
+     * @param consumer The consumer.
+     * @return the task
+     */
+    public static Utils.Task<CommandResult> execCmdAsync(final String[] commands,
+                                                         final boolean isRooted,
+                                                         final Utils.Consumer<CommandResult> consumer) {
+        return execCmdAsync(commands, isRooted, true, consumer);
+    }
+
+    /**
+     * Execute the command asynchronously.
+     *
+     * @param command         The command.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @param consumer        The consumer.
+     * @return the task
+     */
+    public static Utils.Task<CommandResult> execCmdAsync(final String command,
+                                                         final boolean isRooted,
+                                                         final boolean isNeedResultMsg,
+                                                         final Utils.Consumer<CommandResult> consumer) {
+        return execCmdAsync(new String[]{command}, isRooted, isNeedResultMsg, consumer);
+    }
+
+    /**
+     * Execute the command asynchronously.
+     *
+     * @param commands        The commands.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @param consumer        The consumer.
+     * @return the task
+     */
+    public static Utils.Task<CommandResult> execCmdAsync(final List<String> commands,
+                                                         final boolean isRooted,
+                                                         final boolean isNeedResultMsg,
+                                                         final Utils.Consumer<CommandResult> consumer) {
+        return execCmdAsync(commands == null ? null : commands.toArray(new String[]{}),
+                isRooted,
+                isNeedResultMsg,
+                consumer);
+    }
+
+    /**
+     * Execute the command asynchronously.
+     *
+     * @param commands        The commands.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @param consumer        The consumer.
+     * @return the task
+     */
+    public static Utils.Task<CommandResult> execCmdAsync(final String[] commands,
+                                                         final boolean isRooted,
+                                                         final boolean isNeedResultMsg,
+                                                         @NonNull final Utils.Consumer<CommandResult> consumer) {
+        return UtilsBridge.doAsync(new Utils.Task<CommandResult>(consumer) {
+            @Override
+            public CommandResult doInBackground() {
+                return execCmd(commands, isRooted, isNeedResultMsg);
+            }
+        });
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param command  The command.
+     * @param isRooted True to use root, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String command, final boolean isRooted) {
+        return execCmd(new String[]{command}, isRooted, true);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param command  The command.
+     * @param envp     The environment variable settings.
+     * @param isRooted True to use root, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String command, final List<String> envp, final boolean isRooted) {
+        return execCmd(new String[]{command},
+                envp == null ? null : envp.toArray(new String[]{}),
+                isRooted,
+                true);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param commands The commands.
+     * @param isRooted True to use root, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final List<String> commands, final boolean isRooted) {
+        return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRooted, true);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param commands The commands.
+     * @param envp     The environment variable settings.
+     * @param isRooted True to use root, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final List<String> commands,
+                                        final List<String> envp,
+                                        final boolean isRooted) {
+        return execCmd(commands == null ? null : commands.toArray(new String[]{}),
+                envp == null ? null : envp.toArray(new String[]{}),
+                isRooted,
+                true);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param commands The commands.
+     * @param isRooted True to use root, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String[] commands, final boolean isRooted) {
+        return execCmd(commands, isRooted, true);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param command         The command.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String command,
+                                        final boolean isRooted,
+                                        final boolean isNeedResultMsg) {
+        return execCmd(new String[]{command}, isRooted, isNeedResultMsg);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param command         The command.
+     * @param envp            The environment variable settings.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String command,
+                                        final List<String> envp,
+                                        final boolean isRooted,
+                                        final boolean isNeedResultMsg) {
+        return execCmd(new String[]{command}, envp == null ? null : envp.toArray(new String[]{}),
+                isRooted,
+                isNeedResultMsg);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param command         The command.
+     * @param envp            The environment variable settings array.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String command,
+                                        final String[] envp,
+                                        final boolean isRooted,
+                                        final boolean isNeedResultMsg) {
+        return execCmd(new String[]{command}, envp, isRooted, isNeedResultMsg);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param commands        The commands.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final List<String> commands,
+                                        final boolean isRooted,
+                                        final boolean isNeedResultMsg) {
+        return execCmd(commands == null ? null : commands.toArray(new String[]{}),
+                isRooted,
+                isNeedResultMsg);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param commands        The commands.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String[] commands,
+                                        final boolean isRooted,
+                                        final boolean isNeedResultMsg) {
+        return execCmd(commands, null, isRooted, isNeedResultMsg);
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param commands        The commands.
+     * @param envp            Array of strings, each element of which
+     *                        has environment variable settings in the format
+     *                        <i>name</i>=<i>value</i>, or
+     *                        <tt>null</tt> if the subprocess should inherit
+     *                        the environment of the current process.
+     * @param isRooted        True to use root, false otherwise.
+     * @param isNeedResultMsg True to return the message of result, false otherwise.
+     * @return the single {@link CommandResult} instance
+     */
+    public static CommandResult execCmd(final String[] commands,
+                                        final String[] envp,
+                                        final boolean isRooted,
+                                        final boolean isNeedResultMsg) {
+        int result = -1;
+        if (commands == null || commands.length == 0) {
+            return new CommandResult(result, "", "");
+        }
+        Process process = null;
+        BufferedReader successResult = null;
+        BufferedReader errorResult = null;
+        StringBuilder successMsg = null;
+        StringBuilder errorMsg = null;
+        DataOutputStream os = null;
+        try {
+            process = Runtime.getRuntime().exec(isRooted ? "su" : "sh", envp, null);
+            os = new DataOutputStream(process.getOutputStream());
+            for (String command : commands) {
+                if (command == null) continue;
+                os.write(command.getBytes());
+                os.writeBytes(LINE_SEP);
+                os.flush();
+            }
+            os.writeBytes("exit" + LINE_SEP);
+            os.flush();
+            result = process.waitFor();
+            if (isNeedResultMsg) {
+                successMsg = new StringBuilder();
+                errorMsg = new StringBuilder();
+                successResult = new BufferedReader(
+                        new InputStreamReader(process.getInputStream(), "UTF-8")
+                );
+                errorResult = new BufferedReader(
+                        new InputStreamReader(process.getErrorStream(), "UTF-8")
+                );
+                String line;
+                if ((line = successResult.readLine()) != null) {
+                    successMsg.append(line);
+                    while ((line = successResult.readLine()) != null) {
+                        successMsg.append(LINE_SEP).append(line);
+                    }
+                }
+                if ((line = errorResult.readLine()) != null) {
+                    errorMsg.append(line);
+                    while ((line = errorResult.readLine()) != null) {
+                        errorMsg.append(LINE_SEP).append(line);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            try {
+                if (successResult != null) {
+                    successResult.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            try {
+                if (errorResult != null) {
+                    errorResult.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            if (process != null) {
+                process.destroy();
+            }
+        }
+        return new CommandResult(
+                result,
+                successMsg == null ? "" : successMsg.toString(),
+                errorMsg == null ? "" : errorMsg.toString()
+        );
+    }
+
+    /**
+     * The result of command.
+     */
+    public static class CommandResult {
+        public int    result;
+        public String successMsg;
+        public String errorMsg;
+
+        public CommandResult(final int result, final String successMsg, final String errorMsg) {
+            this.result = result;
+            this.successMsg = successMsg;
+            this.errorMsg = errorMsg;
+        }
+
+        @Override
+        public String toString() {
+            return "result: " + result + "\n" +
+                    "successMsg: " + successMsg + "\n" +
+                    "errorMsg: " + errorMsg;
+        }
+    }
+}

+ 175 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SizeUtils.java

@@ -0,0 +1,175 @@
+package com.cooleshow.base.utils;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about size
+ * </pre>
+ */
+public final class SizeUtils {
+
+    private SizeUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Value of dp to value of px.
+     *
+     * @param dpValue The value of dp.
+     * @return value of px
+     */
+    public static int dp2px(final float dpValue) {
+        final float scale = Resources.getSystem().getDisplayMetrics().density;
+        return (int) (dpValue * scale + 0.5f);
+    }
+
+    /**
+     * Value of px to value of dp.
+     *
+     * @param pxValue The value of px.
+     * @return value of dp
+     */
+    public static int px2dp(final float pxValue) {
+        final float scale = Resources.getSystem().getDisplayMetrics().density;
+        return (int) (pxValue / scale + 0.5f);
+    }
+
+    /**
+     * Value of sp to value of px.
+     *
+     * @param spValue The value of sp.
+     * @return value of px
+     */
+    public static int sp2px(final float spValue) {
+        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
+        return (int) (spValue * fontScale + 0.5f);
+    }
+
+    /**
+     * Value of px to value of sp.
+     *
+     * @param pxValue The value of px.
+     * @return value of sp
+     */
+    public static int px2sp(final float pxValue) {
+        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
+        return (int) (pxValue / fontScale + 0.5f);
+    }
+
+    /**
+     * Converts an unpacked complex data value holding a dimension to its final floating
+     * point value. The two parameters <var>unit</var> and <var>value</var>
+     * are as in {@link TypedValue#TYPE_DIMENSION}.
+     *
+     * @param value The value to apply the unit to.
+     * @param unit  The unit to convert from.
+     * @return The complex floating point value multiplied by the appropriate
+     * metrics depending on its unit.
+     */
+    public static float applyDimension(final float value, final int unit) {
+        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
+        switch (unit) {
+            case TypedValue.COMPLEX_UNIT_PX:
+                return value;
+            case TypedValue.COMPLEX_UNIT_DIP:
+                return value * metrics.density;
+            case TypedValue.COMPLEX_UNIT_SP:
+                return value * metrics.scaledDensity;
+            case TypedValue.COMPLEX_UNIT_PT:
+                return value * metrics.xdpi * (1.0f / 72);
+            case TypedValue.COMPLEX_UNIT_IN:
+                return value * metrics.xdpi;
+            case TypedValue.COMPLEX_UNIT_MM:
+                return value * metrics.xdpi * (1.0f / 25.4f);
+        }
+        return 0;
+    }
+
+    /**
+     * Force get the size of view.
+     * <p>e.g.</p>
+     * <pre>
+     * SizeUtils.forceGetViewSize(view, new SizeUtils.OnGetSizeListener() {
+     *     Override
+     *     public void onGetSize(final View view) {
+     *         view.getWidth();
+     *     }
+     * });
+     * </pre>
+     *
+     * @param view     The view.
+     * @param listener The get size listener.
+     */
+    public static void forceGetViewSize(final View view, final OnGetSizeListener listener) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onGetSize(view);
+                }
+            }
+        });
+    }
+
+    /**
+     * Return the width of view.
+     *
+     * @param view The view.
+     * @return the width of view
+     */
+    public static int getMeasuredWidth(final View view) {
+        return measureView(view)[0];
+    }
+
+    /**
+     * Return the height of view.
+     *
+     * @param view The view.
+     * @return the height of view
+     */
+    public static int getMeasuredHeight(final View view) {
+        return measureView(view)[1];
+    }
+
+    /**
+     * Measure the view.
+     *
+     * @param view The view.
+     * @return arr[0]: view's width, arr[1]: view's height
+     */
+    public static int[] measureView(final View view) {
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp == null) {
+            lp = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT
+            );
+        }
+        int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
+        int lpHeight = lp.height;
+        int heightSpec;
+        if (lpHeight > 0) {
+            heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY);
+        } else {
+            heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        }
+        view.measure(widthSpec, heightSpec);
+        return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()};
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // interface
+    ///////////////////////////////////////////////////////////////////////////
+
+    public interface OnGetSizeListener {
+        void onGetSize(View view);
+    }
+}

+ 1465 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SpanUtils.java

@@ -0,0 +1,1465 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.method.LinkMovementMethod;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.MaskFilterSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import android.text.style.UpdateAppearance;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import static android.graphics.BlurMaskFilter.Blur;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 16/12/13
+ *     desc  : utils about span
+ * </pre>
+ */
+public final class SpanUtils {
+
+    private static final int COLOR_DEFAULT = 0xFEFFFFFF;
+
+    public static final int ALIGN_BOTTOM   = 0;
+    public static final int ALIGN_BASELINE = 1;
+    public static final int ALIGN_CENTER   = 2;
+    public static final int ALIGN_TOP      = 3;
+
+    @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Align {
+    }
+
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    public static SpanUtils with(final TextView textView) {
+        return new SpanUtils(textView);
+    }
+
+    private TextView      mTextView;
+    private CharSequence  mText;
+    private int           flag;
+    private int           foregroundColor;
+    private int           backgroundColor;
+    private int           lineHeight;
+    private int           alignLine;
+    private int           quoteColor;
+    private int           stripeWidth;
+    private int           quoteGapWidth;
+    private int           first;
+    private int           rest;
+    private int           bulletColor;
+    private int           bulletRadius;
+    private int           bulletGapWidth;
+    private int           fontSize;
+    private float         proportion;
+    private float         xProportion;
+    private boolean       isStrikethrough;
+    private boolean       isUnderline;
+    private boolean       isSuperscript;
+    private boolean       isSubscript;
+    private boolean       isBold;
+    private boolean       isItalic;
+    private boolean       isBoldItalic;
+    private String        fontFamily;
+    private Typeface      typeface;
+    private Alignment     alignment;
+    private int           verticalAlign;
+    private ClickableSpan clickSpan;
+    private String        url;
+    private float         blurRadius;
+    private Blur          style;
+    private Shader        shader;
+    private float         shadowRadius;
+    private float         shadowDx;
+    private float         shadowDy;
+    private int           shadowColor;
+    private Object[]      spans;
+
+    private Bitmap   imageBitmap;
+    private Drawable imageDrawable;
+    private Uri      imageUri;
+    private int      imageResourceId;
+    private int      alignImage;
+
+    private int spaceSize;
+    private int spaceColor;
+
+    private SerializableSpannableStringBuilder mBuilder;
+    private boolean                            isCreated;
+
+    private       int mType;
+    private final int mTypeCharSequence = 0;
+    private final int mTypeImage        = 1;
+    private final int mTypeSpace        = 2;
+
+    private SpanUtils(TextView textView) {
+        this();
+        mTextView = textView;
+    }
+
+    public SpanUtils() {
+        mBuilder = new SerializableSpannableStringBuilder();
+        mText = "";
+        mType = -1;
+        setDefault();
+    }
+
+    private void setDefault() {
+        flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
+        foregroundColor = COLOR_DEFAULT;
+        backgroundColor = COLOR_DEFAULT;
+        lineHeight = -1;
+        quoteColor = COLOR_DEFAULT;
+        first = -1;
+        bulletColor = COLOR_DEFAULT;
+        fontSize = -1;
+        proportion = -1;
+        xProportion = -1;
+        isStrikethrough = false;
+        isUnderline = false;
+        isSuperscript = false;
+        isSubscript = false;
+        isBold = false;
+        isItalic = false;
+        isBoldItalic = false;
+        fontFamily = null;
+        typeface = null;
+        alignment = null;
+        verticalAlign = -1;
+        clickSpan = null;
+        url = null;
+        blurRadius = -1;
+        shader = null;
+        shadowRadius = -1;
+        spans = null;
+
+        imageBitmap = null;
+        imageDrawable = null;
+        imageUri = null;
+        imageResourceId = -1;
+
+        spaceSize = -1;
+    }
+
+    /**
+     * Set the span of flag.
+     *
+     * @param flag The flag.
+     *             <ul>
+     *             <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>
+     *             <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>
+     *             <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>
+     *             <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>
+     *             </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setFlag(final int flag) {
+        this.flag = flag;
+        return this;
+    }
+
+    /**
+     * Set the span of foreground's color.
+     *
+     * @param color The color of foreground
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setForegroundColor(@ColorInt final int color) {
+        this.foregroundColor = color;
+        return this;
+    }
+
+    /**
+     * Set the span of background's color.
+     *
+     * @param color The color of background
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setBackgroundColor(@ColorInt final int color) {
+        this.backgroundColor = color;
+        return this;
+    }
+
+    /**
+     * Set the span of line height.
+     *
+     * @param lineHeight The line height, in pixel.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight) {
+        return setLineHeight(lineHeight, ALIGN_CENTER);
+    }
+
+    /**
+     * Set the span of line height.
+     *
+     * @param lineHeight The line height, in pixel.
+     * @param align      The alignment.
+     *                   <ul>
+     *                   <li>{@link Align#ALIGN_TOP   }</li>
+     *                   <li>{@link Align#ALIGN_CENTER}</li>
+     *                   <li>{@link Align#ALIGN_BOTTOM}</li>
+     *                   </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight,
+                                   @Align final int align) {
+        this.lineHeight = lineHeight;
+        this.alignLine = align;
+        return this;
+    }
+
+    /**
+     * Set the span of quote's color.
+     *
+     * @param color The color of quote
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setQuoteColor(@ColorInt final int color) {
+        return setQuoteColor(color, 2, 2);
+    }
+
+    /**
+     * Set the span of quote's color.
+     *
+     * @param color       The color of quote.
+     * @param stripeWidth The width of stripe, in pixel.
+     * @param gapWidth    The width of gap, in pixel.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setQuoteColor(@ColorInt final int color,
+                                   @IntRange(from = 1) final int stripeWidth,
+                                   @IntRange(from = 0) final int gapWidth) {
+        this.quoteColor = color;
+        this.stripeWidth = stripeWidth;
+        this.quoteGapWidth = gapWidth;
+        return this;
+    }
+
+    /**
+     * Set the span of leading margin.
+     *
+     * @param first The indent for the first line of the paragraph.
+     * @param rest  The indent for the remaining lines of the paragraph.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setLeadingMargin(@IntRange(from = 0) final int first,
+                                      @IntRange(from = 0) final int rest) {
+        this.first = first;
+        this.rest = rest;
+        return this;
+    }
+
+    /**
+     * Set the span of bullet.
+     *
+     * @param gapWidth The width of gap, in pixel.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setBullet(@IntRange(from = 0) final int gapWidth) {
+        return setBullet(0, 3, gapWidth);
+    }
+
+    /**
+     * Set the span of bullet.
+     *
+     * @param color    The color of bullet.
+     * @param radius   The radius of bullet, in pixel.
+     * @param gapWidth The width of gap, in pixel.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setBullet(@ColorInt final int color,
+                               @IntRange(from = 0) final int radius,
+                               @IntRange(from = 0) final int gapWidth) {
+        this.bulletColor = color;
+        this.bulletRadius = radius;
+        this.bulletGapWidth = gapWidth;
+        return this;
+    }
+
+    /**
+     * Set the span of font's size.
+     *
+     * @param size The size of font.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setFontSize(@IntRange(from = 0) final int size) {
+        return setFontSize(size, false);
+    }
+
+    /**
+     * Set the span of size of font.
+     *
+     * @param size The size of font.
+     * @param isSp True to use sp, false to use pixel.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setFontSize(@IntRange(from = 0) final int size, final boolean isSp) {
+        if (isSp) {
+            final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
+            this.fontSize = (int) (size * fontScale + 0.5f);
+        } else {
+            this.fontSize = size;
+        }
+        return this;
+    }
+
+    /**
+     * Set the span of proportion of font.
+     *
+     * @param proportion The proportion of font.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setFontProportion(final float proportion) {
+        this.proportion = proportion;
+        return this;
+    }
+
+    /**
+     * Set the span of transverse proportion of font.
+     *
+     * @param proportion The transverse proportion of font.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setFontXProportion(final float proportion) {
+        this.xProportion = proportion;
+        return this;
+    }
+
+    /**
+     * Set the span of strikethrough.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setStrikethrough() {
+        this.isStrikethrough = true;
+        return this;
+    }
+
+    /**
+     * Set the span of underline.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setUnderline() {
+        this.isUnderline = true;
+        return this;
+    }
+
+    /**
+     * Set the span of superscript.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setSuperscript() {
+        this.isSuperscript = true;
+        return this;
+    }
+
+    /**
+     * Set the span of subscript.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setSubscript() {
+        this.isSubscript = true;
+        return this;
+    }
+
+    /**
+     * Set the span of bold.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setBold() {
+        isBold = true;
+        return this;
+    }
+
+    /**
+     * Set the span of italic.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setItalic() {
+        isItalic = true;
+        return this;
+    }
+
+    /**
+     * Set the span of bold italic.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setBoldItalic() {
+        isBoldItalic = true;
+        return this;
+    }
+
+    /**
+     * Set the span of font family.
+     *
+     * @param fontFamily The font family.
+     *                   <ul>
+     *                   <li>monospace</li>
+     *                   <li>serif</li>
+     *                   <li>sans-serif</li>
+     *                   </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setFontFamily(@NonNull final String fontFamily) {
+        this.fontFamily = fontFamily;
+        return this;
+    }
+
+    /**
+     * Set the span of typeface.
+     *
+     * @param typeface The typeface.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setTypeface(@NonNull final Typeface typeface) {
+        this.typeface = typeface;
+        return this;
+    }
+
+    /**
+     * Set the span of horizontal alignment.
+     *
+     * @param alignment The alignment.
+     *                  <ul>
+     *                  <li>{@link Alignment#ALIGN_NORMAL  }</li>
+     *                  <li>{@link Alignment#ALIGN_OPPOSITE}</li>
+     *                  <li>{@link Alignment#ALIGN_CENTER  }</li>
+     *                  </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setHorizontalAlign(@NonNull final Alignment alignment) {
+        this.alignment = alignment;
+        return this;
+    }
+
+    /**
+     * Set the span of vertical alignment.
+     *
+     * @param align The alignment.
+     *              <ul>
+     *              <li>{@link Align#ALIGN_TOP     }</li>
+     *              <li>{@link Align#ALIGN_CENTER  }</li>
+     *              <li>{@link Align#ALIGN_BASELINE}</li>
+     *              <li>{@link Align#ALIGN_BOTTOM  }</li>
+     *              </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setVerticalAlign(@Align final int align) {
+        this.verticalAlign = align;
+        return this;
+    }
+
+    /**
+     * Set the span of click.
+     * <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>
+     *
+     * @param clickSpan The span of click.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setClickSpan(@NonNull final ClickableSpan clickSpan) {
+        setMovementMethodIfNeed();
+        this.clickSpan = clickSpan;
+        return this;
+    }
+
+    /**
+     * Set the span of click.
+     * <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>
+     *
+     * @param color         The color of click span.
+     * @param underlineText True to support underline, false otherwise.
+     * @param listener      The listener of click span.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setClickSpan(@ColorInt final int color,
+                                  final boolean underlineText,
+                                  final View.OnClickListener listener) {
+        setMovementMethodIfNeed();
+        this.clickSpan = new ClickableSpan() {
+
+            @Override
+            public void updateDrawState(@NonNull TextPaint paint) {
+                paint.setColor(color);
+                paint.setUnderlineText(underlineText);
+            }
+
+            @Override
+            public void onClick(@NonNull View widget) {
+                if (listener != null) {
+                    listener.onClick(widget);
+                }
+            }
+        };
+        return this;
+    }
+
+    /**
+     * Set the span of url.
+     * <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>
+     *
+     * @param url The url.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setUrl(@NonNull final String url) {
+        setMovementMethodIfNeed();
+        this.url = url;
+        return this;
+    }
+
+    private void setMovementMethodIfNeed() {
+        if (mTextView != null && mTextView.getMovementMethod() == null) {
+            mTextView.setMovementMethod(LinkMovementMethod.getInstance());
+        }
+    }
+
+    /**
+     * Set the span of blur.
+     *
+     * @param radius The radius of blur.
+     * @param style  The style.
+     *               <ul>
+     *               <li>{@link Blur#NORMAL}</li>
+     *               <li>{@link Blur#SOLID}</li>
+     *               <li>{@link Blur#OUTER}</li>
+     *               <li>{@link Blur#INNER}</li>
+     *               </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setBlur(@FloatRange(from = 0, fromInclusive = false) final float radius,
+                             final Blur style) {
+        this.blurRadius = radius;
+        this.style = style;
+        return this;
+    }
+
+    /**
+     * Set the span of shader.
+     *
+     * @param shader The shader.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setShader(@NonNull final Shader shader) {
+        this.shader = shader;
+        return this;
+    }
+
+    /**
+     * Set the span of shadow.
+     *
+     * @param radius      The radius of shadow.
+     * @param dx          X-axis offset, in pixel.
+     * @param dy          Y-axis offset, in pixel.
+     * @param shadowColor The color of shadow.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setShadow(@FloatRange(from = 0, fromInclusive = false) final float radius,
+                               final float dx,
+                               final float dy,
+                               final int shadowColor) {
+        this.shadowRadius = radius;
+        this.shadowDx = dx;
+        this.shadowDy = dy;
+        this.shadowColor = shadowColor;
+        return this;
+    }
+
+
+    /**
+     * Set the spans.
+     *
+     * @param spans The spans.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils setSpans(@NonNull final Object... spans) {
+        if (spans.length > 0) {
+            this.spans = spans;
+        }
+        return this;
+    }
+
+    /**
+     * Append the text text.
+     *
+     * @param text The text.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils append(@NonNull final CharSequence text) {
+        apply(mTypeCharSequence);
+        mText = text;
+        return this;
+    }
+
+    /**
+     * Append one line.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendLine() {
+        apply(mTypeCharSequence);
+        mText = LINE_SEPARATOR;
+        return this;
+    }
+
+    /**
+     * Append text and one line.
+     *
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendLine(@NonNull final CharSequence text) {
+        apply(mTypeCharSequence);
+        mText = text + LINE_SEPARATOR;
+        return this;
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param bitmap The bitmap of image.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@NonNull final Bitmap bitmap) {
+        return appendImage(bitmap, ALIGN_BOTTOM);
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param bitmap The bitmap.
+     * @param align  The alignment.
+     *               <ul>
+     *               <li>{@link Align#ALIGN_TOP     }</li>
+     *               <li>{@link Align#ALIGN_CENTER  }</li>
+     *               <li>{@link Align#ALIGN_BASELINE}</li>
+     *               <li>{@link Align#ALIGN_BOTTOM  }</li>
+     *               </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@NonNull final Bitmap bitmap, @Align final int align) {
+        apply(mTypeImage);
+        this.imageBitmap = bitmap;
+        this.alignImage = align;
+        return this;
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param drawable The drawable of image.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@NonNull final Drawable drawable) {
+        return appendImage(drawable, ALIGN_BOTTOM);
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param drawable The drawable of image.
+     * @param align    The alignment.
+     *                 <ul>
+     *                 <li>{@link Align#ALIGN_TOP     }</li>
+     *                 <li>{@link Align#ALIGN_CENTER  }</li>
+     *                 <li>{@link Align#ALIGN_BASELINE}</li>
+     *                 <li>{@link Align#ALIGN_BOTTOM  }</li>
+     *                 </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@NonNull final Drawable drawable, @Align final int align) {
+        apply(mTypeImage);
+        this.imageDrawable = drawable;
+        this.alignImage = align;
+        return this;
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param uri The uri of image.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@NonNull final Uri uri) {
+        return appendImage(uri, ALIGN_BOTTOM);
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param uri   The uri of image.
+     * @param align The alignment.
+     *              <ul>
+     *              <li>{@link Align#ALIGN_TOP     }</li>
+     *              <li>{@link Align#ALIGN_CENTER  }</li>
+     *              <li>{@link Align#ALIGN_BASELINE}</li>
+     *              <li>{@link Align#ALIGN_BOTTOM  }</li>
+     *              </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@NonNull final Uri uri, @Align final int align) {
+        apply(mTypeImage);
+        this.imageUri = uri;
+        this.alignImage = align;
+        return this;
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param resourceId The resource id of image.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@DrawableRes final int resourceId) {
+        return appendImage(resourceId, ALIGN_BOTTOM);
+    }
+
+    /**
+     * Append one image.
+     *
+     * @param resourceId The resource id of image.
+     * @param align      The alignment.
+     *                   <ul>
+     *                   <li>{@link Align#ALIGN_TOP     }</li>
+     *                   <li>{@link Align#ALIGN_CENTER  }</li>
+     *                   <li>{@link Align#ALIGN_BASELINE}</li>
+     *                   <li>{@link Align#ALIGN_BOTTOM  }</li>
+     *                   </ul>
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendImage(@DrawableRes final int resourceId, @Align final int align) {
+        apply(mTypeImage);
+        this.imageResourceId = resourceId;
+        this.alignImage = align;
+        return this;
+    }
+
+    /**
+     * Append space.
+     *
+     * @param size The size of space.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendSpace(@IntRange(from = 0) final int size) {
+        return appendSpace(size, Color.TRANSPARENT);
+    }
+
+    /**
+     * Append space.
+     *
+     * @param size  The size of space.
+     * @param color The color of space.
+     * @return the single {@link SpanUtils} instance
+     */
+    public SpanUtils appendSpace(@IntRange(from = 0) final int size, @ColorInt final int color) {
+        apply(mTypeSpace);
+        spaceSize = size;
+        spaceColor = color;
+        return this;
+    }
+
+    private void apply(final int type) {
+        applyLast();
+        mType = type;
+    }
+
+    public SpannableStringBuilder get() {
+        return mBuilder;
+    }
+
+    /**
+     * Create the span string.
+     *
+     * @return the span string
+     */
+    public SpannableStringBuilder create() {
+        applyLast();
+        if (mTextView != null) {
+            mTextView.setText(mBuilder);
+        }
+        isCreated = true;
+        return mBuilder;
+    }
+
+    private void applyLast() {
+        if (isCreated) {
+            return;
+        }
+        if (mType == mTypeCharSequence) {
+            updateCharCharSequence();
+        } else if (mType == mTypeImage) {
+            updateImage();
+        } else if (mType == mTypeSpace) {
+            updateSpace();
+        }
+        setDefault();
+    }
+
+    private void updateCharCharSequence() {
+        if (mText.length() == 0) return;
+        int start = mBuilder.length();
+        if (start == 0 && lineHeight != -1) {// bug of LineHeightSpan when first line
+            mBuilder.append(Character.toString((char) 2))
+                    .append("\n")
+                    .setSpan(new AbsoluteSizeSpan(0), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            start = 2;
+        }
+        mBuilder.append(mText);
+        int end = mBuilder.length();
+        if (verticalAlign != -1) {
+            mBuilder.setSpan(new VerticalAlignSpan(verticalAlign), start, end, flag);
+        }
+        if (foregroundColor != COLOR_DEFAULT) {
+            mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
+        }
+        if (backgroundColor != COLOR_DEFAULT) {
+            mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
+        }
+        if (first != -1) {
+            mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
+        }
+        if (quoteColor != COLOR_DEFAULT) {
+            mBuilder.setSpan(
+                    new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth),
+                    start,
+                    end,
+                    flag
+            );
+        }
+        if (bulletColor != COLOR_DEFAULT) {
+            mBuilder.setSpan(
+                    new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth),
+                    start,
+                    end,
+                    flag
+            );
+        }
+        if (fontSize != -1) {
+            mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, false), start, end, flag);
+        }
+        if (proportion != -1) {
+            mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
+        }
+        if (xProportion != -1) {
+            mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
+        }
+        if (lineHeight != -1) {
+            mBuilder.setSpan(new CustomLineHeightSpan(lineHeight, alignLine), start, end, flag);
+        }
+        if (isStrikethrough) {
+            mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
+        }
+        if (isUnderline) {
+            mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
+        }
+        if (isSuperscript) {
+            mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
+        }
+        if (isSubscript) {
+            mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
+        }
+        if (isBold) {
+            mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
+        }
+        if (isItalic) {
+            mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
+        }
+        if (isBoldItalic) {
+            mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
+        }
+        if (fontFamily != null) {
+            mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
+        }
+        if (typeface != null) {
+            mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
+        }
+        if (alignment != null) {
+            mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
+        }
+        if (clickSpan != null) {
+            mBuilder.setSpan(clickSpan, start, end, flag);
+        }
+        if (url != null) {
+            mBuilder.setSpan(new URLSpan(url), start, end, flag);
+        }
+        if (blurRadius != -1) {
+            mBuilder.setSpan(
+                    new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)),
+                    start,
+                    end,
+                    flag
+            );
+        }
+        if (shader != null) {
+            mBuilder.setSpan(new ShaderSpan(shader), start, end, flag);
+        }
+        if (shadowRadius != -1) {
+            mBuilder.setSpan(
+                    new ShadowSpan(shadowRadius, shadowDx, shadowDy, shadowColor),
+                    start,
+                    end,
+                    flag
+            );
+        }
+        if (spans != null) {
+            for (Object span : spans) {
+                mBuilder.setSpan(span, start, end, flag);
+            }
+        }
+    }
+
+    private void updateImage() {
+        int start = mBuilder.length();
+        mText = "<img>";
+        updateCharCharSequence();
+        int end = mBuilder.length();
+        if (imageBitmap != null) {
+            mBuilder.setSpan(new CustomImageSpan(imageBitmap, alignImage), start, end, flag);
+        } else if (imageDrawable != null) {
+            mBuilder.setSpan(new CustomImageSpan(imageDrawable, alignImage), start, end, flag);
+        } else if (imageUri != null) {
+            mBuilder.setSpan(new CustomImageSpan(imageUri, alignImage), start, end, flag);
+        } else if (imageResourceId != -1) {
+            mBuilder.setSpan(new CustomImageSpan(imageResourceId, alignImage), start, end, flag);
+        }
+    }
+
+    private void updateSpace() {
+        int start = mBuilder.length();
+        mText = "< >";
+        updateCharCharSequence();
+        int end = mBuilder.length();
+        mBuilder.setSpan(new SpaceSpan(spaceSize, spaceColor), start, end, flag);
+    }
+
+    static class VerticalAlignSpan extends ReplacementSpan {
+
+        static final int ALIGN_CENTER = 2;
+        static final int ALIGN_TOP    = 3;
+
+        final int mVerticalAlignment;
+
+        VerticalAlignSpan(int verticalAlignment) {
+            mVerticalAlignment = verticalAlignment;
+        }
+
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
+            text = text.subSequence(start, end);
+            return (int) paint.measureText(text.toString());
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
+            text = text.subSequence(start, end);
+            Paint.FontMetricsInt fm = paint.getFontMetricsInt();
+//            int need = height - (v + fm.descent - fm.ascent - spanstartv);
+//            if (need > 0) {
+//                if (mVerticalAlignment == ALIGN_TOP) {
+//                    fm.descent += need;
+//                } else if (mVerticalAlignment == ALIGN_CENTER) {
+//                    fm.descent += need / 2;
+//                    fm.ascent -= need / 2;
+//                } else {
+//                    fm.ascent -= need;
+//                }
+//            }
+//            need = height - (v + fm.bottom - fm.top - spanstartv);
+//            if (need > 0) {
+//                if (mVerticalAlignment == ALIGN_TOP) {
+//                    fm.bottom += need;
+//                } else if (mVerticalAlignment == ALIGN_CENTER) {
+//                    fm.bottom += need / 2;
+//                    fm.top -= need / 2;
+//                } else {
+//                    fm.top -= need;
+//                }
+//            }
+
+            canvas.drawText(text.toString(), x, y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), paint);
+        }
+    }
+
+    static class CustomLineHeightSpan implements LineHeightSpan {
+
+        private final int height;
+
+        static final int ALIGN_CENTER = 2;
+        static final int ALIGN_TOP    = 3;
+
+        final  int                  mVerticalAlignment;
+        static Paint.FontMetricsInt sfm;
+
+        CustomLineHeightSpan(int height, int verticalAlignment) {
+            this.height = height;
+            mVerticalAlignment = verticalAlignment;
+        }
+
+        @Override
+        public void chooseHeight(final CharSequence text, final int start, final int end,
+                                 final int spanstartv, final int v, final Paint.FontMetricsInt fm) {
+//            LogUtils.e(fm, sfm);
+            if (sfm == null) {
+                sfm = new Paint.FontMetricsInt();
+                sfm.top = fm.top;
+                sfm.ascent = fm.ascent;
+                sfm.descent = fm.descent;
+                sfm.bottom = fm.bottom;
+                sfm.leading = fm.leading;
+            } else {
+                fm.top = sfm.top;
+                fm.ascent = sfm.ascent;
+                fm.descent = sfm.descent;
+                fm.bottom = sfm.bottom;
+                fm.leading = sfm.leading;
+            }
+            int need = height - (v + fm.descent - fm.ascent - spanstartv);
+            if (need > 0) {
+                if (mVerticalAlignment == ALIGN_TOP) {
+                    fm.descent += need;
+                } else if (mVerticalAlignment == ALIGN_CENTER) {
+                    fm.descent += need / 2;
+                    fm.ascent -= need / 2;
+                } else {
+                    fm.ascent -= need;
+                }
+            }
+            need = height - (v + fm.bottom - fm.top - spanstartv);
+            if (need > 0) {
+                if (mVerticalAlignment == ALIGN_TOP) {
+                    fm.bottom += need;
+                } else if (mVerticalAlignment == ALIGN_CENTER) {
+                    fm.bottom += need / 2;
+                    fm.top -= need / 2;
+                } else {
+                    fm.top -= need;
+                }
+            }
+            if (end == ((Spanned) text).getSpanEnd(this)) {
+                sfm = null;
+            }
+//            LogUtils.e(fm, sfm);
+        }
+    }
+
+    static class SpaceSpan extends ReplacementSpan {
+
+        private final int   width;
+        private final Paint paint = new Paint();
+
+        private SpaceSpan(final int width) {
+            this(width, Color.TRANSPARENT);
+        }
+
+        private SpaceSpan(final int width, final int color) {
+            super();
+            this.width = width;
+            paint.setColor(color);
+            paint.setStyle(Paint.Style.FILL);
+        }
+
+        @Override
+        public int getSize(@NonNull final Paint paint, final CharSequence text,
+                           @IntRange(from = 0) final int start,
+                           @IntRange(from = 0) final int end,
+                           @Nullable final Paint.FontMetricsInt fm) {
+            return width;
+        }
+
+        @Override
+        public void draw(@NonNull final Canvas canvas, final CharSequence text,
+                         @IntRange(from = 0) final int start,
+                         @IntRange(from = 0) final int end,
+                         final float x, final int top, final int y, final int bottom,
+                         @NonNull final Paint paint) {
+            canvas.drawRect(x, top, x + width, bottom, this.paint);
+        }
+    }
+
+    static class CustomQuoteSpan implements LeadingMarginSpan {
+
+        private final int color;
+        private final int stripeWidth;
+        private final int gapWidth;
+
+        private CustomQuoteSpan(final int color, final int stripeWidth, final int gapWidth) {
+            super();
+            this.color = color;
+            this.stripeWidth = stripeWidth;
+            this.gapWidth = gapWidth;
+        }
+
+        public int getLeadingMargin(final boolean first) {
+            return stripeWidth + gapWidth;
+        }
+
+        public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
+                                      final int top, final int baseline, final int bottom,
+                                      final CharSequence text, final int start, final int end,
+                                      final boolean first, final Layout layout) {
+            Paint.Style style = p.getStyle();
+            int color = p.getColor();
+
+            p.setStyle(Paint.Style.FILL);
+            p.setColor(this.color);
+
+            c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
+
+            p.setStyle(style);
+            p.setColor(color);
+        }
+    }
+
+    static class CustomBulletSpan implements LeadingMarginSpan {
+
+        private final int color;
+        private final int radius;
+        private final int gapWidth;
+
+        private Path sBulletPath = null;
+
+        private CustomBulletSpan(final int color, final int radius, final int gapWidth) {
+            this.color = color;
+            this.radius = radius;
+            this.gapWidth = gapWidth;
+        }
+
+        public int getLeadingMargin(final boolean first) {
+            return 2 * radius + gapWidth;
+        }
+
+        public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
+                                      final int top, final int baseline, final int bottom,
+                                      final CharSequence text, final int start, final int end,
+                                      final boolean first, final Layout l) {
+            if (((Spanned) text).getSpanStart(this) == start) {
+                Paint.Style style = p.getStyle();
+                int oldColor = 0;
+                oldColor = p.getColor();
+                p.setColor(color);
+                p.setStyle(Paint.Style.FILL);
+                if (c.isHardwareAccelerated()) {
+                    if (sBulletPath == null) {
+                        sBulletPath = new Path();
+                        // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
+                        sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
+                    }
+                    c.save();
+                    c.translate(x + dir * radius, (top + bottom) / 2.0f);
+                    c.drawPath(sBulletPath, p);
+                    c.restore();
+                } else {
+                    c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
+                }
+                p.setColor(oldColor);
+                p.setStyle(style);
+            }
+        }
+    }
+
+    @SuppressLint("ParcelCreator")
+    static class CustomTypefaceSpan extends TypefaceSpan {
+
+        private final Typeface newType;
+
+        private CustomTypefaceSpan(final Typeface type) {
+            super("");
+            newType = type;
+        }
+
+        @Override
+        public void updateDrawState(final TextPaint textPaint) {
+            apply(textPaint, newType);
+        }
+
+        @Override
+        public void updateMeasureState(final TextPaint paint) {
+            apply(paint, newType);
+        }
+
+        private void apply(final Paint paint, final Typeface tf) {
+            int oldStyle;
+            Typeface old = paint.getTypeface();
+            if (old == null) {
+                oldStyle = 0;
+            } else {
+                oldStyle = old.getStyle();
+            }
+
+            int fake = oldStyle & ~tf.getStyle();
+            if ((fake & Typeface.BOLD) != 0) {
+                paint.setFakeBoldText(true);
+            }
+
+            if ((fake & Typeface.ITALIC) != 0) {
+                paint.setTextSkewX(-0.25f);
+            }
+
+            paint.getShader();
+
+            paint.setTypeface(tf);
+        }
+    }
+
+    static class CustomImageSpan extends CustomDynamicDrawableSpan {
+        private Drawable mDrawable;
+        private Uri      mContentUri;
+        private int      mResourceId;
+
+        private CustomImageSpan(final Bitmap b, final int verticalAlignment) {
+            super(verticalAlignment);
+            mDrawable = new BitmapDrawable(Utils.getApp().getResources(), b);
+            mDrawable.setBounds(
+                    0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
+            );
+        }
+
+        private CustomImageSpan(final Drawable d, final int verticalAlignment) {
+            super(verticalAlignment);
+            mDrawable = d;
+            mDrawable.setBounds(
+                    0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
+            );
+        }
+
+        private CustomImageSpan(final Uri uri, final int verticalAlignment) {
+            super(verticalAlignment);
+            mContentUri = uri;
+        }
+
+        private CustomImageSpan(@DrawableRes final int resourceId, final int verticalAlignment) {
+            super(verticalAlignment);
+            mResourceId = resourceId;
+        }
+
+        @Override
+        public Drawable getDrawable() {
+            Drawable drawable = null;
+            if (mDrawable != null) {
+                drawable = mDrawable;
+            } else if (mContentUri != null) {
+                Bitmap bitmap;
+                try {
+                    InputStream is =
+                            Utils.getApp().getContentResolver().openInputStream(mContentUri);
+                    bitmap = BitmapFactory.decodeStream(is);
+                    drawable = new BitmapDrawable(Utils.getApp().getResources(), bitmap);
+                    drawable.setBounds(
+                            0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
+                    );
+                    if (is != null) {
+                        is.close();
+                    }
+                } catch (Exception e) {
+                    Log.e("sms", "Failed to loaded content " + mContentUri, e);
+                }
+            } else {
+                try {
+                    drawable = ContextCompat.getDrawable(Utils.getApp(), mResourceId);
+                    drawable.setBounds(
+                            0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
+                    );
+                } catch (Exception e) {
+                    Log.e("sms", "Unable to find resource: " + mResourceId);
+                }
+            }
+            return drawable;
+        }
+    }
+
+    static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
+
+        static final int ALIGN_BOTTOM = 0;
+
+        static final int ALIGN_BASELINE = 1;
+
+        static final int ALIGN_CENTER = 2;
+
+        static final int ALIGN_TOP = 3;
+
+        final int mVerticalAlignment;
+
+        private CustomDynamicDrawableSpan() {
+            mVerticalAlignment = ALIGN_BOTTOM;
+        }
+
+        private CustomDynamicDrawableSpan(final int verticalAlignment) {
+            mVerticalAlignment = verticalAlignment;
+        }
+
+        public abstract Drawable getDrawable();
+
+        @Override
+        public int getSize(@NonNull final Paint paint, final CharSequence text,
+                           final int start, final int end, final Paint.FontMetricsInt fm) {
+            Drawable d = getCachedDrawable();
+            Rect rect = d.getBounds();
+            if (fm != null) {
+//                LogUtils.d("fm.top: " + fm.top,
+//                        "fm.ascent: " + fm.ascent,
+//                        "fm.descent: " + fm.descent,
+//                        "fm.bottom: " + fm.bottom,
+//                        "lineHeight: " + (fm.bottom - fm.top));
+                int lineHeight = fm.bottom - fm.top;
+                if (lineHeight < rect.height()) {
+                    if (mVerticalAlignment == ALIGN_TOP) {
+                        fm.top = fm.top;
+                        fm.bottom = rect.height() + fm.top;
+                    } else if (mVerticalAlignment == ALIGN_CENTER) {
+                        fm.top = -rect.height() / 2 - lineHeight / 4;
+                        fm.bottom = rect.height() / 2 - lineHeight / 4;
+                    } else {
+                        fm.top = -rect.height() + fm.bottom;
+                        fm.bottom = fm.bottom;
+                    }
+                    fm.ascent = fm.top;
+                    fm.descent = fm.bottom;
+                }
+            }
+            return rect.right;
+        }
+
+        @Override
+        public void draw(@NonNull final Canvas canvas, final CharSequence text,
+                         final int start, final int end, final float x,
+                         final int top, final int y, final int bottom, @NonNull final Paint paint) {
+            Drawable d = getCachedDrawable();
+            Rect rect = d.getBounds();
+            canvas.save();
+            float transY;
+            int lineHeight = bottom - top;
+//            LogUtils.d("rectHeight: " + rect.height(),
+//                    "lineHeight: " + (bottom - top));
+            if (rect.height() < lineHeight) {
+                if (mVerticalAlignment == ALIGN_TOP) {
+                    transY = top;
+                } else if (mVerticalAlignment == ALIGN_CENTER) {
+                    transY = (bottom + top - rect.height()) / 2;
+                } else if (mVerticalAlignment == ALIGN_BASELINE) {
+                    transY = y - rect.height();
+                } else {
+                    transY = bottom - rect.height();
+                }
+                canvas.translate(x, transY);
+            } else {
+                canvas.translate(x, top);
+            }
+            d.draw(canvas);
+            canvas.restore();
+        }
+
+        private Drawable getCachedDrawable() {
+            WeakReference<Drawable> wr = mDrawableRef;
+            Drawable d = null;
+            if (wr != null) {
+                d = wr.get();
+            }
+            if (d == null) {
+                d = getDrawable();
+                mDrawableRef = new WeakReference<>(d);
+            }
+            return d;
+        }
+
+        private WeakReference<Drawable> mDrawableRef;
+    }
+
+    static class ShaderSpan extends CharacterStyle implements UpdateAppearance {
+        private Shader mShader;
+
+        private ShaderSpan(final Shader shader) {
+            this.mShader = shader;
+        }
+
+        @Override
+        public void updateDrawState(final TextPaint tp) {
+            tp.setShader(mShader);
+        }
+    }
+
+    static class ShadowSpan extends CharacterStyle implements UpdateAppearance {
+        private float radius;
+        private float dx, dy;
+        private int shadowColor;
+
+        private ShadowSpan(final float radius,
+                           final float dx,
+                           final float dy,
+                           final int shadowColor) {
+            this.radius = radius;
+            this.dx = dx;
+            this.dy = dy;
+            this.shadowColor = shadowColor;
+        }
+
+        @Override
+        public void updateDrawState(final TextPaint tp) {
+            tp.setShadowLayer(radius, dx, dy, shadowColor);
+        }
+    }
+
+    private static class SerializableSpannableStringBuilder extends SpannableStringBuilder
+            implements Serializable {
+
+        private static final long serialVersionUID = 4909567650765875771L;
+    }
+}

+ 262 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/StringUtils.java

@@ -0,0 +1,262 @@
+package com.cooleshow.base.utils;
+
+import android.content.res.Resources;
+import androidx.annotation.ArrayRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import java.util.IllegalFormatException;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/16
+ *     desc  : utils about string
+ * </pre>
+ */
+public final class StringUtils {
+
+    private StringUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return whether the string is null or 0-length.
+     *
+     * @param s The string.
+     * @return {@code true}: yes<br> {@code false}: no
+     */
+    public static boolean isEmpty(final CharSequence s) {
+        return s == null || s.length() == 0;
+    }
+
+    /**
+     * Return whether the string is null or whitespace.
+     *
+     * @param s The string.
+     * @return {@code true}: yes<br> {@code false}: no
+     */
+    public static boolean isTrimEmpty(final String s) {
+        return (s == null || s.trim().length() == 0);
+    }
+
+    /**
+     * Return whether the string is null or white space.
+     *
+     * @param s The string.
+     * @return {@code true}: yes<br> {@code false}: no
+     */
+    public static boolean isSpace(final String s) {
+        if (s == null) return true;
+        for (int i = 0, len = s.length(); i < len; ++i) {
+            if (!Character.isWhitespace(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return whether string1 is equals to string2.
+     *
+     * @param s1 The first string.
+     * @param s2 The second string.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean equals(final CharSequence s1, final CharSequence s2) {
+        if (s1 == s2) return true;
+        int length;
+        if (s1 != null && s2 != null && (length = s1.length()) == s2.length()) {
+            if (s1 instanceof String && s2 instanceof String) {
+                return s1.equals(s2);
+            } else {
+                for (int i = 0; i < length; i++) {
+                    if (s1.charAt(i) != s2.charAt(i)) return false;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return whether string1 is equals to string2, ignoring case considerations..
+     *
+     * @param s1 The first string.
+     * @param s2 The second string.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean equalsIgnoreCase(final String s1, final String s2) {
+        return s1 == null ? s2 == null : s1.equalsIgnoreCase(s2);
+    }
+
+    /**
+     * Return {@code ""} if string equals null.
+     *
+     * @param s The string.
+     * @return {@code ""} if string equals null
+     */
+    public static String null2Length0(final String s) {
+        return s == null ? "" : s;
+    }
+
+    /**
+     * Return the length of string.
+     *
+     * @param s The string.
+     * @return the length of string
+     */
+    public static int length(final CharSequence s) {
+        return s == null ? 0 : s.length();
+    }
+
+    /**
+     * Set the first letter of string upper.
+     *
+     * @param s The string.
+     * @return the string with first letter upper.
+     */
+    public static String upperFirstLetter(final String s) {
+        if (s == null || s.length() == 0) return "";
+        if (!Character.isLowerCase(s.charAt(0))) return s;
+        return (char) (s.charAt(0) - 32) + s.substring(1);
+    }
+
+    /**
+     * Set the first letter of string lower.
+     *
+     * @param s The string.
+     * @return the string with first letter lower.
+     */
+    public static String lowerFirstLetter(final String s) {
+        if (s == null || s.length() == 0) return "";
+        if (!Character.isUpperCase(s.charAt(0))) return s;
+        return String.valueOf((char) (s.charAt(0) + 32)) + s.substring(1);
+    }
+
+    /**
+     * Reverse the string.
+     *
+     * @param s The string.
+     * @return the reverse string.
+     */
+    public static String reverse(final String s) {
+        if (s == null) return "";
+        int len = s.length();
+        if (len <= 1) return s;
+        int mid = len >> 1;
+        char[] chars = s.toCharArray();
+        char c;
+        for (int i = 0; i < mid; ++i) {
+            c = chars[i];
+            chars[i] = chars[len - i - 1];
+            chars[len - i - 1] = c;
+        }
+        return new String(chars);
+    }
+
+    /**
+     * Convert string to DBC.
+     *
+     * @param s The string.
+     * @return the DBC string
+     */
+    public static String toDBC(final String s) {
+        if (s == null || s.length() == 0) return "";
+        char[] chars = s.toCharArray();
+        for (int i = 0, len = chars.length; i < len; i++) {
+            if (chars[i] == 12288) {
+                chars[i] = ' ';
+            } else if (65281 <= chars[i] && chars[i] <= 65374) {
+                chars[i] = (char) (chars[i] - 65248);
+            } else {
+                chars[i] = chars[i];
+            }
+        }
+        return new String(chars);
+    }
+
+    /**
+     * Convert string to SBC.
+     *
+     * @param s The string.
+     * @return the SBC string
+     */
+    public static String toSBC(final String s) {
+        if (s == null || s.length() == 0) return "";
+        char[] chars = s.toCharArray();
+        for (int i = 0, len = chars.length; i < len; i++) {
+            if (chars[i] == ' ') {
+                chars[i] = (char) 12288;
+            } else if (33 <= chars[i] && chars[i] <= 126) {
+                chars[i] = (char) (chars[i] + 65248);
+            } else {
+                chars[i] = chars[i];
+            }
+        }
+        return new String(chars);
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier.
+     * @return the string value associated with a particular resource ID.
+     */
+    public static String getString(@StringRes int id) {
+        return getString(id, (Object[]) null);
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID.
+     *
+     * @param id         The desired resource identifier.
+     * @param formatArgs The format arguments that will be used for substitution.
+     * @return the string value associated with a particular resource ID.
+     */
+    public static String getString(@StringRes int id, Object... formatArgs) {
+        try {
+            return format(Utils.getApp().getString(id), formatArgs);
+        } catch (Resources.NotFoundException e) {
+            e.printStackTrace();
+            return String.valueOf(id);
+        }
+    }
+
+    /**
+     * Return the string array associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier.
+     * @return The string array associated with the resource.
+     */
+    public static String[] getStringArray(@ArrayRes int id) {
+        try {
+            return Utils.getApp().getResources().getStringArray(id);
+        } catch (Resources.NotFoundException e) {
+            e.printStackTrace();
+            return new String[]{String.valueOf(id)};
+        }
+    }
+
+    /**
+     * Format the string.
+     *
+     * @param str  The string.
+     * @param args The args.
+     * @return a formatted string.
+     */
+    public static String format(@Nullable String str, Object... args) {
+        String text = str;
+        if (text != null) {
+            if (args != null && args.length > 0) {
+                try {
+                    text = String.format(str, args);
+                } catch (IllegalFormatException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return text;
+    }
+}

+ 1391 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ThreadUtils.java

@@ -0,0 +1,1391 @@
+package com.cooleshow.base.utils;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/05/08
+ *     desc  : utils about thread
+ * </pre>
+ */
+public final class ThreadUtils {
+
+    private static final Handler HANDLER = new Handler(Looper.getMainLooper());
+
+    private static final Map<Integer, Map<Integer, ExecutorService>> TYPE_PRIORITY_POOLS = new HashMap<>();
+
+    private static final Map<Task, ExecutorService> TASK_POOL_MAP = new ConcurrentHashMap<>();
+
+    private static final int   CPU_COUNT = Runtime.getRuntime().availableProcessors();
+    private static final Timer TIMER     = new Timer();
+
+    private static final byte TYPE_SINGLE = -1;
+    private static final byte TYPE_CACHED = -2;
+    private static final byte TYPE_IO     = -4;
+    private static final byte TYPE_CPU    = -8;
+
+    private static Executor sDeliver;
+
+    /**
+     * Return whether the thread is the main thread.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isMainThread() {
+        return Looper.myLooper() == Looper.getMainLooper();
+    }
+
+    public static Handler getMainHandler() {
+        return HANDLER;
+    }
+
+    public static void runOnUiThread(final Runnable runnable) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            runnable.run();
+        } else {
+            HANDLER.post(runnable);
+        }
+    }
+
+    public static void runOnUiThreadDelayed(final Runnable runnable, long delayMillis) {
+        HANDLER.postDelayed(runnable, delayMillis);
+    }
+
+    /**
+     * Return a thread pool that reuses a fixed number of threads
+     * operating off a shared unbounded queue, using the provided
+     * ThreadFactory to create new threads when needed.
+     *
+     * @param size The size of thread in the pool.
+     * @return a fixed thread pool
+     */
+    public static ExecutorService getFixedPool(@IntRange(from = 1) final int size) {
+        return getPoolByTypeAndPriority(size);
+    }
+
+    /**
+     * Return a thread pool that reuses a fixed number of threads
+     * operating off a shared unbounded queue, using the provided
+     * ThreadFactory to create new threads when needed.
+     *
+     * @param size     The size of thread in the pool.
+     * @param priority The priority of thread in the poll.
+     * @return a fixed thread pool
+     */
+    public static ExecutorService getFixedPool(@IntRange(from = 1) final int size,
+                                               @IntRange(from = 1, to = 10) final int priority) {
+        return getPoolByTypeAndPriority(size, priority);
+    }
+
+    /**
+     * Return a thread pool that uses a single worker thread operating
+     * off an unbounded queue, and uses the provided ThreadFactory to
+     * create a new thread when needed.
+     *
+     * @return a single thread pool
+     */
+    public static ExecutorService getSinglePool() {
+        return getPoolByTypeAndPriority(TYPE_SINGLE);
+    }
+
+    /**
+     * Return a thread pool that uses a single worker thread operating
+     * off an unbounded queue, and uses the provided ThreadFactory to
+     * create a new thread when needed.
+     *
+     * @param priority The priority of thread in the poll.
+     * @return a single thread pool
+     */
+    public static ExecutorService getSinglePool(@IntRange(from = 1, to = 10) final int priority) {
+        return getPoolByTypeAndPriority(TYPE_SINGLE, priority);
+    }
+
+    /**
+     * Return a thread pool that creates new threads as needed, but
+     * will reuse previously constructed threads when they are
+     * available.
+     *
+     * @return a cached thread pool
+     */
+    public static ExecutorService getCachedPool() {
+        return getPoolByTypeAndPriority(TYPE_CACHED);
+    }
+
+    /**
+     * Return a thread pool that creates new threads as needed, but
+     * will reuse previously constructed threads when they are
+     * available.
+     *
+     * @param priority The priority of thread in the poll.
+     * @return a cached thread pool
+     */
+    public static ExecutorService getCachedPool(@IntRange(from = 1, to = 10) final int priority) {
+        return getPoolByTypeAndPriority(TYPE_CACHED, priority);
+    }
+
+    /**
+     * Return a thread pool that creates (2 * CPU_COUNT + 1) threads
+     * operating off a queue which size is 128.
+     *
+     * @return a IO thread pool
+     */
+    public static ExecutorService getIoPool() {
+        return getPoolByTypeAndPriority(TYPE_IO);
+    }
+
+    /**
+     * Return a thread pool that creates (2 * CPU_COUNT + 1) threads
+     * operating off a queue which size is 128.
+     *
+     * @param priority The priority of thread in the poll.
+     * @return a IO thread pool
+     */
+    public static ExecutorService getIoPool(@IntRange(from = 1, to = 10) final int priority) {
+        return getPoolByTypeAndPriority(TYPE_IO, priority);
+    }
+
+    /**
+     * Return a thread pool that creates (CPU_COUNT + 1) threads
+     * operating off a queue which size is 128 and the maximum
+     * number of threads equals (2 * CPU_COUNT + 1).
+     *
+     * @return a cpu thread pool for
+     */
+    public static ExecutorService getCpuPool() {
+        return getPoolByTypeAndPriority(TYPE_CPU);
+    }
+
+    /**
+     * Return a thread pool that creates (CPU_COUNT + 1) threads
+     * operating off a queue which size is 128 and the maximum
+     * number of threads equals (2 * CPU_COUNT + 1).
+     *
+     * @param priority The priority of thread in the poll.
+     * @return a cpu thread pool for
+     */
+    public static ExecutorService getCpuPool(@IntRange(from = 1, to = 10) final int priority) {
+        return getPoolByTypeAndPriority(TYPE_CPU, priority);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool.
+     *
+     * @param size The size of thread in the fixed thread pool.
+     * @param task The task to execute.
+     * @param <T>  The type of the task's result.
+     */
+    public static <T> void executeByFixed(@IntRange(from = 1) final int size, final Task<T> task) {
+        execute(getPoolByTypeAndPriority(size), task);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool.
+     *
+     * @param size     The size of thread in the fixed thread pool.
+     * @param task     The task to execute.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByFixed(@IntRange(from = 1) final int size,
+                                          final Task<T> task,
+                                          @IntRange(from = 1, to = 10) final int priority) {
+        execute(getPoolByTypeAndPriority(size, priority), task);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool after the given delay.
+     *
+     * @param size  The size of thread in the fixed thread pool.
+     * @param task  The task to execute.
+     * @param delay The time from now to delay execution.
+     * @param unit  The time unit of the delay parameter.
+     * @param <T>   The type of the task's result.
+     */
+    public static <T> void executeByFixedWithDelay(@IntRange(from = 1) final int size,
+                                                   final Task<T> task,
+                                                   final long delay,
+                                                   final TimeUnit unit) {
+        executeWithDelay(getPoolByTypeAndPriority(size), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool after the given delay.
+     *
+     * @param size     The size of thread in the fixed thread pool.
+     * @param task     The task to execute.
+     * @param delay    The time from now to delay execution.
+     * @param unit     The time unit of the delay parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByFixedWithDelay(@IntRange(from = 1) final int size,
+                                                   final Task<T> task,
+                                                   final long delay,
+                                                   final TimeUnit unit,
+                                                   @IntRange(from = 1, to = 10) final int priority) {
+        executeWithDelay(getPoolByTypeAndPriority(size, priority), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool at fix rate.
+     *
+     * @param size   The size of thread in the fixed thread pool.
+     * @param task   The task to execute.
+     * @param period The period between successive executions.
+     * @param unit   The time unit of the period parameter.
+     * @param <T>    The type of the task's result.
+     */
+    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+                                                   final Task<T> task,
+                                                   final long period,
+                                                   final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(size), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool at fix rate.
+     *
+     * @param size     The size of thread in the fixed thread pool.
+     * @param task     The task to execute.
+     * @param period   The period between successive executions.
+     * @param unit     The time unit of the period parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+                                                   final Task<T> task,
+                                                   final long period,
+                                                   final TimeUnit unit,
+                                                   @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool at fix rate.
+     *
+     * @param size         The size of thread in the fixed thread pool.
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+                                                   final Task<T> task,
+                                                   long initialDelay,
+                                                   final long period,
+                                                   final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(size), task, initialDelay, period, unit);
+    }
+
+    /**
+     * Executes the given task in a fixed thread pool at fix rate.
+     *
+     * @param size         The size of thread in the fixed thread pool.
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param priority     The priority of thread in the poll.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+                                                   final Task<T> task,
+                                                   long initialDelay,
+                                                   final long period,
+                                                   final TimeUnit unit,
+                                                   @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, initialDelay, period, unit);
+    }
+
+    /**
+     * Executes the given task in a single thread pool.
+     *
+     * @param task The task to execute.
+     * @param <T>  The type of the task's result.
+     */
+    public static <T> void executeBySingle(final Task<T> task) {
+        execute(getPoolByTypeAndPriority(TYPE_SINGLE), task);
+    }
+
+    /**
+     * Executes the given task in a single thread pool.
+     *
+     * @param task     The task to execute.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeBySingle(final Task<T> task,
+                                           @IntRange(from = 1, to = 10) final int priority) {
+        execute(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task);
+    }
+
+    /**
+     * Executes the given task in a single thread pool after the given delay.
+     *
+     * @param task  The task to execute.
+     * @param delay The time from now to delay execution.
+     * @param unit  The time unit of the delay parameter.
+     * @param <T>   The type of the task's result.
+     */
+    public static <T> void executeBySingleWithDelay(final Task<T> task,
+                                                    final long delay,
+                                                    final TimeUnit unit) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_SINGLE), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a single thread pool after the given delay.
+     *
+     * @param task     The task to execute.
+     * @param delay    The time from now to delay execution.
+     * @param unit     The time unit of the delay parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeBySingleWithDelay(final Task<T> task,
+                                                    final long delay,
+                                                    final TimeUnit unit,
+                                                    @IntRange(from = 1, to = 10) final int priority) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a single thread pool at fix rate.
+     *
+     * @param task   The task to execute.
+     * @param period The period between successive executions.
+     * @param unit   The time unit of the period parameter.
+     * @param <T>    The type of the task's result.
+     */
+    public static <T> void executeBySingleAtFixRate(final Task<T> task,
+                                                    final long period,
+                                                    final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a single thread pool at fix rate.
+     *
+     * @param task     The task to execute.
+     * @param period   The period between successive executions.
+     * @param unit     The time unit of the period parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeBySingleAtFixRate(final Task<T> task,
+                                                    final long period,
+                                                    final TimeUnit unit,
+                                                    @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a single thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeBySingleAtFixRate(final Task<T> task,
+                                                    long initialDelay,
+                                                    final long period,
+                                                    final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE), task, initialDelay, period, unit);
+    }
+
+    /**
+     * Executes the given task in a single thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param priority     The priority of thread in the poll.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeBySingleAtFixRate(final Task<T> task,
+                                                    long initialDelay,
+                                                    final long period,
+                                                    final TimeUnit unit,
+                                                    @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(
+                getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, initialDelay, period, unit
+        );
+    }
+
+    /**
+     * Executes the given task in a cached thread pool.
+     *
+     * @param task The task to execute.
+     * @param <T>  The type of the task's result.
+     */
+    public static <T> void executeByCached(final Task<T> task) {
+        execute(getPoolByTypeAndPriority(TYPE_CACHED), task);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool.
+     *
+     * @param task     The task to execute.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByCached(final Task<T> task,
+                                           @IntRange(from = 1, to = 10) final int priority) {
+        execute(getPoolByTypeAndPriority(TYPE_CACHED, priority), task);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool after the given delay.
+     *
+     * @param task  The task to execute.
+     * @param delay The time from now to delay execution.
+     * @param unit  The time unit of the delay parameter.
+     * @param <T>   The type of the task's result.
+     */
+    public static <T> void executeByCachedWithDelay(final Task<T> task,
+                                                    final long delay,
+                                                    final TimeUnit unit) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_CACHED), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool after the given delay.
+     *
+     * @param task     The task to execute.
+     * @param delay    The time from now to delay execution.
+     * @param unit     The time unit of the delay parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByCachedWithDelay(final Task<T> task,
+                                                    final long delay,
+                                                    final TimeUnit unit,
+                                                    @IntRange(from = 1, to = 10) final int priority) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_CACHED, priority), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool at fix rate.
+     *
+     * @param task   The task to execute.
+     * @param period The period between successive executions.
+     * @param unit   The time unit of the period parameter.
+     * @param <T>    The type of the task's result.
+     */
+    public static <T> void executeByCachedAtFixRate(final Task<T> task,
+                                                    final long period,
+                                                    final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool at fix rate.
+     *
+     * @param task     The task to execute.
+     * @param period   The period between successive executions.
+     * @param unit     The time unit of the period parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByCachedAtFixRate(final Task<T> task,
+                                                    final long period,
+                                                    final TimeUnit unit,
+                                                    @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED, priority), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByCachedAtFixRate(final Task<T> task,
+                                                    long initialDelay,
+                                                    final long period,
+                                                    final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED), task, initialDelay, period, unit);
+    }
+
+    /**
+     * Executes the given task in a cached thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param priority     The priority of thread in the poll.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByCachedAtFixRate(final Task<T> task,
+                                                    long initialDelay,
+                                                    final long period,
+                                                    final TimeUnit unit,
+                                                    @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(
+                getPoolByTypeAndPriority(TYPE_CACHED, priority), task, initialDelay, period, unit
+        );
+    }
+
+    /**
+     * Executes the given task in an IO thread pool.
+     *
+     * @param task The task to execute.
+     * @param <T>  The type of the task's result.
+     */
+    public static <T> void executeByIo(final Task<T> task) {
+        execute(getPoolByTypeAndPriority(TYPE_IO), task);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool.
+     *
+     * @param task     The task to execute.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByIo(final Task<T> task,
+                                       @IntRange(from = 1, to = 10) final int priority) {
+        execute(getPoolByTypeAndPriority(TYPE_IO, priority), task);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool after the given delay.
+     *
+     * @param task  The task to execute.
+     * @param delay The time from now to delay execution.
+     * @param unit  The time unit of the delay parameter.
+     * @param <T>   The type of the task's result.
+     */
+    public static <T> void executeByIoWithDelay(final Task<T> task,
+                                                final long delay,
+                                                final TimeUnit unit) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_IO), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool after the given delay.
+     *
+     * @param task     The task to execute.
+     * @param delay    The time from now to delay execution.
+     * @param unit     The time unit of the delay parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByIoWithDelay(final Task<T> task,
+                                                final long delay,
+                                                final TimeUnit unit,
+                                                @IntRange(from = 1, to = 10) final int priority) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_IO, priority), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool at fix rate.
+     *
+     * @param task   The task to execute.
+     * @param period The period between successive executions.
+     * @param unit   The time unit of the period parameter.
+     * @param <T>    The type of the task's result.
+     */
+    public static <T> void executeByIoAtFixRate(final Task<T> task,
+                                                final long period,
+                                                final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool at fix rate.
+     *
+     * @param task     The task to execute.
+     * @param period   The period between successive executions.
+     * @param unit     The time unit of the period parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByIoAtFixRate(final Task<T> task,
+                                                final long period,
+                                                final TimeUnit unit,
+                                                @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO, priority), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByIoAtFixRate(final Task<T> task,
+                                                long initialDelay,
+                                                final long period,
+                                                final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO), task, initialDelay, period, unit);
+    }
+
+    /**
+     * Executes the given task in an IO thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param priority     The priority of thread in the poll.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByIoAtFixRate(final Task<T> task,
+                                                long initialDelay,
+                                                final long period,
+                                                final TimeUnit unit,
+                                                @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(
+                getPoolByTypeAndPriority(TYPE_IO, priority), task, initialDelay, period, unit
+        );
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool.
+     *
+     * @param task The task to execute.
+     * @param <T>  The type of the task's result.
+     */
+    public static <T> void executeByCpu(final Task<T> task) {
+        execute(getPoolByTypeAndPriority(TYPE_CPU), task);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool.
+     *
+     * @param task     The task to execute.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByCpu(final Task<T> task,
+                                        @IntRange(from = 1, to = 10) final int priority) {
+        execute(getPoolByTypeAndPriority(TYPE_CPU, priority), task);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool after the given delay.
+     *
+     * @param task  The task to execute.
+     * @param delay The time from now to delay execution.
+     * @param unit  The time unit of the delay parameter.
+     * @param <T>   The type of the task's result.
+     */
+    public static <T> void executeByCpuWithDelay(final Task<T> task,
+                                                 final long delay,
+                                                 final TimeUnit unit) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_CPU), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool after the given delay.
+     *
+     * @param task     The task to execute.
+     * @param delay    The time from now to delay execution.
+     * @param unit     The time unit of the delay parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByCpuWithDelay(final Task<T> task,
+                                                 final long delay,
+                                                 final TimeUnit unit,
+                                                 @IntRange(from = 1, to = 10) final int priority) {
+        executeWithDelay(getPoolByTypeAndPriority(TYPE_CPU, priority), task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool at fix rate.
+     *
+     * @param task   The task to execute.
+     * @param period The period between successive executions.
+     * @param unit   The time unit of the period parameter.
+     * @param <T>    The type of the task's result.
+     */
+    public static <T> void executeByCpuAtFixRate(final Task<T> task,
+                                                 final long period,
+                                                 final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool at fix rate.
+     *
+     * @param task     The task to execute.
+     * @param period   The period between successive executions.
+     * @param unit     The time unit of the period parameter.
+     * @param priority The priority of thread in the poll.
+     * @param <T>      The type of the task's result.
+     */
+    public static <T> void executeByCpuAtFixRate(final Task<T> task,
+                                                 final long period,
+                                                 final TimeUnit unit,
+                                                 @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU, priority), task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByCpuAtFixRate(final Task<T> task,
+                                                 long initialDelay,
+                                                 final long period,
+                                                 final TimeUnit unit) {
+        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU), task, initialDelay, period, unit);
+    }
+
+    /**
+     * Executes the given task in a cpu thread pool at fix rate.
+     *
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param priority     The priority of thread in the poll.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByCpuAtFixRate(final Task<T> task,
+                                                 long initialDelay,
+                                                 final long period,
+                                                 final TimeUnit unit,
+                                                 @IntRange(from = 1, to = 10) final int priority) {
+        executeAtFixedRate(
+                getPoolByTypeAndPriority(TYPE_CPU, priority), task, initialDelay, period, unit
+        );
+    }
+
+    /**
+     * Executes the given task in a custom thread pool.
+     *
+     * @param pool The custom thread pool.
+     * @param task The task to execute.
+     * @param <T>  The type of the task's result.
+     */
+    public static <T> void executeByCustom(final ExecutorService pool, final Task<T> task) {
+        execute(pool, task);
+    }
+
+    /**
+     * Executes the given task in a custom thread pool after the given delay.
+     *
+     * @param pool  The custom thread pool.
+     * @param task  The task to execute.
+     * @param delay The time from now to delay execution.
+     * @param unit  The time unit of the delay parameter.
+     * @param <T>   The type of the task's result.
+     */
+    public static <T> void executeByCustomWithDelay(final ExecutorService pool,
+                                                    final Task<T> task,
+                                                    final long delay,
+                                                    final TimeUnit unit) {
+        executeWithDelay(pool, task, delay, unit);
+    }
+
+    /**
+     * Executes the given task in a custom thread pool at fix rate.
+     *
+     * @param pool   The custom thread pool.
+     * @param task   The task to execute.
+     * @param period The period between successive executions.
+     * @param unit   The time unit of the period parameter.
+     * @param <T>    The type of the task's result.
+     */
+    public static <T> void executeByCustomAtFixRate(final ExecutorService pool,
+                                                    final Task<T> task,
+                                                    final long period,
+                                                    final TimeUnit unit) {
+        executeAtFixedRate(pool, task, 0, period, unit);
+    }
+
+    /**
+     * Executes the given task in a custom thread pool at fix rate.
+     *
+     * @param pool         The custom thread pool.
+     * @param task         The task to execute.
+     * @param initialDelay The time to delay first execution.
+     * @param period       The period between successive executions.
+     * @param unit         The time unit of the initialDelay and period parameters.
+     * @param <T>          The type of the task's result.
+     */
+    public static <T> void executeByCustomAtFixRate(final ExecutorService pool,
+                                                    final Task<T> task,
+                                                    long initialDelay,
+                                                    final long period,
+                                                    final TimeUnit unit) {
+        executeAtFixedRate(pool, task, initialDelay, period, unit);
+    }
+
+    /**
+     * Cancel the given task.
+     *
+     * @param task The task to cancel.
+     */
+    public static void cancel(final Task task) {
+        if (task == null) return;
+        task.cancel();
+    }
+
+    /**
+     * Cancel the given tasks.
+     *
+     * @param tasks The tasks to cancel.
+     */
+    public static void cancel(final Task... tasks) {
+        if (tasks == null || tasks.length == 0) return;
+        for (Task task : tasks) {
+            if (task == null) continue;
+            task.cancel();
+        }
+    }
+
+    /**
+     * Cancel the given tasks.
+     *
+     * @param tasks The tasks to cancel.
+     */
+    public static void cancel(final List<Task> tasks) {
+        if (tasks == null || tasks.size() == 0) return;
+        for (Task task : tasks) {
+            if (task == null) continue;
+            task.cancel();
+        }
+    }
+
+    /**
+     * Cancel the tasks in pool.
+     *
+     * @param executorService The pool.
+     */
+    public static void cancel(ExecutorService executorService) {
+        if (executorService instanceof ThreadPoolExecutor4Util) {
+            for (Map.Entry<Task, ExecutorService> taskTaskInfoEntry : TASK_POOL_MAP.entrySet()) {
+                if (taskTaskInfoEntry.getValue() == executorService) {
+                    cancel(taskTaskInfoEntry.getKey());
+                }
+            }
+        } else {
+            Log.e("ThreadUtils", "The executorService is not ThreadUtils's pool.");
+        }
+    }
+
+    /**
+     * Set the deliver.
+     *
+     * @param deliver The deliver.
+     */
+    public static void setDeliver(final Executor deliver) {
+        sDeliver = deliver;
+    }
+
+    private static <T> void execute(final ExecutorService pool, final Task<T> task) {
+        execute(pool, task, 0, 0, null);
+    }
+
+    private static <T> void executeWithDelay(final ExecutorService pool,
+                                             final Task<T> task,
+                                             final long delay,
+                                             final TimeUnit unit) {
+        execute(pool, task, delay, 0, unit);
+    }
+
+    private static <T> void executeAtFixedRate(final ExecutorService pool,
+                                               final Task<T> task,
+                                               long delay,
+                                               final long period,
+                                               final TimeUnit unit) {
+        execute(pool, task, delay, period, unit);
+    }
+
+    private static <T> void execute(final ExecutorService pool, final Task<T> task,
+                                    long delay, final long period, final TimeUnit unit) {
+        synchronized (TASK_POOL_MAP) {
+            if (TASK_POOL_MAP.get(task) != null) {
+                Log.e("ThreadUtils", "Task can only be executed once.");
+                return;
+            }
+            TASK_POOL_MAP.put(task, pool);
+        }
+        if (period == 0) {
+            if (delay == 0) {
+                pool.execute(task);
+            } else {
+                TimerTask timerTask = new TimerTask() {
+                    @Override
+                    public void run() {
+                        pool.execute(task);
+                    }
+                };
+                TIMER.schedule(timerTask, unit.toMillis(delay));
+            }
+        } else {
+            task.setSchedule(true);
+            TimerTask timerTask = new TimerTask() {
+                @Override
+                public void run() {
+                    pool.execute(task);
+                }
+            };
+            TIMER.scheduleAtFixedRate(timerTask, unit.toMillis(delay), unit.toMillis(period));
+        }
+    }
+
+    private static ExecutorService getPoolByTypeAndPriority(final int type) {
+        return getPoolByTypeAndPriority(type, Thread.NORM_PRIORITY);
+    }
+
+    private static ExecutorService getPoolByTypeAndPriority(final int type, final int priority) {
+        synchronized (TYPE_PRIORITY_POOLS) {
+            ExecutorService pool;
+            Map<Integer, ExecutorService> priorityPools = TYPE_PRIORITY_POOLS.get(type);
+            if (priorityPools == null) {
+                priorityPools = new ConcurrentHashMap<>();
+                pool = ThreadPoolExecutor4Util.createPool(type, priority);
+                priorityPools.put(priority, pool);
+                TYPE_PRIORITY_POOLS.put(type, priorityPools);
+            } else {
+                pool = priorityPools.get(priority);
+                if (pool == null) {
+                    pool = ThreadPoolExecutor4Util.createPool(type, priority);
+                    priorityPools.put(priority, pool);
+                }
+            }
+            return pool;
+        }
+    }
+
+    static final class ThreadPoolExecutor4Util extends ThreadPoolExecutor {
+
+        private static ExecutorService createPool(final int type, final int priority) {
+            switch (type) {
+                case TYPE_SINGLE:
+                    return new ThreadPoolExecutor4Util(1, 1,
+                            0L, TimeUnit.MILLISECONDS,
+                            new LinkedBlockingQueue4Util(),
+                            new UtilsThreadFactory("single", priority)
+                    );
+                case TYPE_CACHED:
+                    return new ThreadPoolExecutor4Util(0, 128,
+                            60L, TimeUnit.SECONDS,
+                            new LinkedBlockingQueue4Util(true),
+                            new UtilsThreadFactory("cached", priority)
+                    );
+                case TYPE_IO:
+                    return new ThreadPoolExecutor4Util(2 * CPU_COUNT + 1, 2 * CPU_COUNT + 1,
+                            30, TimeUnit.SECONDS,
+                            new LinkedBlockingQueue4Util(),
+                            new UtilsThreadFactory("io", priority)
+                    );
+                case TYPE_CPU:
+                    return new ThreadPoolExecutor4Util(CPU_COUNT + 1, 2 * CPU_COUNT + 1,
+                            30, TimeUnit.SECONDS,
+                            new LinkedBlockingQueue4Util(true),
+                            new UtilsThreadFactory("cpu", priority)
+                    );
+                default:
+                    return new ThreadPoolExecutor4Util(type, type,
+                            0L, TimeUnit.MILLISECONDS,
+                            new LinkedBlockingQueue4Util(),
+                            new UtilsThreadFactory("fixed(" + type + ")", priority)
+                    );
+            }
+        }
+
+        private final AtomicInteger mSubmittedCount = new AtomicInteger();
+
+        private LinkedBlockingQueue4Util mWorkQueue;
+
+        ThreadPoolExecutor4Util(int corePoolSize, int maximumPoolSize,
+                                long keepAliveTime, TimeUnit unit,
+                                LinkedBlockingQueue4Util workQueue,
+                                ThreadFactory threadFactory) {
+            super(corePoolSize, maximumPoolSize,
+                    keepAliveTime, unit,
+                    workQueue,
+                    threadFactory
+            );
+            workQueue.mPool = this;
+            mWorkQueue = workQueue;
+        }
+
+        private int getSubmittedCount() {
+            return mSubmittedCount.get();
+        }
+
+        @Override
+        protected void afterExecute(Runnable r, Throwable t) {
+            mSubmittedCount.decrementAndGet();
+            super.afterExecute(r, t);
+        }
+
+        @Override
+        public void execute(@NonNull Runnable command) {
+            if (this.isShutdown()) return;
+            mSubmittedCount.incrementAndGet();
+            try {
+                super.execute(command);
+            } catch (RejectedExecutionException ignore) {
+                Log.e("ThreadUtils", "This will not happen!");
+                mWorkQueue.offer(command);
+            } catch (Throwable t) {
+                mSubmittedCount.decrementAndGet();
+            }
+        }
+    }
+
+    private static final class LinkedBlockingQueue4Util extends LinkedBlockingQueue<Runnable> {
+
+        private volatile ThreadPoolExecutor4Util mPool;
+
+        private int mCapacity = Integer.MAX_VALUE;
+
+        LinkedBlockingQueue4Util() {
+            super();
+        }
+
+        LinkedBlockingQueue4Util(boolean isAddSubThreadFirstThenAddQueue) {
+            super();
+            if (isAddSubThreadFirstThenAddQueue) {
+                mCapacity = 0;
+            }
+        }
+
+        LinkedBlockingQueue4Util(int capacity) {
+            super();
+            mCapacity = capacity;
+        }
+
+        @Override
+        public boolean offer(@NonNull Runnable runnable) {
+            if (mCapacity <= size() &&
+                    mPool != null && mPool.getPoolSize() < mPool.getMaximumPoolSize()) {
+                // create a non-core thread
+                return false;
+            }
+            return super.offer(runnable);
+        }
+    }
+
+    static final class UtilsThreadFactory extends AtomicLong
+            implements ThreadFactory {
+        private static final AtomicInteger POOL_NUMBER      = new AtomicInteger(1);
+        private static final long          serialVersionUID = -9209200509960368598L;
+        private final        String        namePrefix;
+        private final        int           priority;
+        private final        boolean       isDaemon;
+
+        UtilsThreadFactory(String prefix, int priority) {
+            this(prefix, priority, false);
+        }
+
+        UtilsThreadFactory(String prefix, int priority, boolean isDaemon) {
+            namePrefix = prefix + "-pool-" +
+                    POOL_NUMBER.getAndIncrement() +
+                    "-thread-";
+            this.priority = priority;
+            this.isDaemon = isDaemon;
+        }
+
+        @Override
+        public Thread newThread(@NonNull Runnable r) {
+            Thread t = new Thread(r, namePrefix + getAndIncrement()) {
+                @Override
+                public void run() {
+                    try {
+                        super.run();
+                    } catch (Throwable t) {
+                        Log.e("ThreadUtils", "Request threw uncaught throwable", t);
+                    }
+                }
+            };
+            t.setDaemon(isDaemon);
+            t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                @Override
+                public void uncaughtException(Thread t, Throwable e) {
+                    System.out.println(e);
+                }
+            });
+            t.setPriority(priority);
+            return t;
+        }
+    }
+
+    public abstract static class SimpleTask<T> extends Task<T> {
+
+        @Override
+        public void onCancel() {
+            Log.e("ThreadUtils", "onCancel: " + Thread.currentThread());
+        }
+
+        @Override
+        public void onFail(Throwable t) {
+            Log.e("ThreadUtils", "onFail: ", t);
+        }
+
+    }
+
+    public abstract static class Task<T> implements Runnable {
+
+        private static final int NEW         = 0;
+        private static final int RUNNING     = 1;
+        private static final int EXCEPTIONAL = 2;
+        private static final int COMPLETING  = 3;
+        private static final int CANCELLED   = 4;
+        private static final int INTERRUPTED = 5;
+        private static final int TIMEOUT     = 6;
+
+        private final AtomicInteger state = new AtomicInteger(NEW);
+
+        private volatile boolean isSchedule;
+        private volatile Thread  runner;
+
+        private Timer             mTimer;
+        private long              mTimeoutMillis;
+        private OnTimeoutListener mTimeoutListener;
+
+        private Executor deliver;
+
+        public abstract T doInBackground() throws Throwable;
+
+        public abstract void onSuccess(T result);
+
+        public abstract void onCancel();
+
+        public abstract void onFail(Throwable t);
+
+        @Override
+        public void run() {
+            if (isSchedule) {
+                if (runner == null) {
+                    if (!state.compareAndSet(NEW, RUNNING)) return;
+                    runner = Thread.currentThread();
+                    if (mTimeoutListener != null) {
+                        Log.w("ThreadUtils", "Scheduled task doesn't support timeout.");
+                    }
+                } else {
+                    if (state.get() != RUNNING) return;
+                }
+            } else {
+                if (!state.compareAndSet(NEW, RUNNING)) return;
+                runner = Thread.currentThread();
+                if (mTimeoutListener != null) {
+                    mTimer = new Timer();
+                    mTimer.schedule(new TimerTask() {
+                        @Override
+                        public void run() {
+                            if (!isDone() && mTimeoutListener != null) {
+                                timeout();
+                                mTimeoutListener.onTimeout();
+                                onDone();
+                            }
+                        }
+                    }, mTimeoutMillis);
+                }
+            }
+            try {
+                final T result = doInBackground();
+                if (isSchedule) {
+                    if (state.get() != RUNNING) return;
+                    getDeliver().execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            onSuccess(result);
+                        }
+                    });
+                } else {
+                    if (!state.compareAndSet(RUNNING, COMPLETING)) return;
+                    getDeliver().execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            onSuccess(result);
+                            onDone();
+                        }
+                    });
+                }
+            } catch (InterruptedException ignore) {
+                state.compareAndSet(CANCELLED, INTERRUPTED);
+            } catch (final Throwable throwable) {
+                if (!state.compareAndSet(RUNNING, EXCEPTIONAL)) return;
+                getDeliver().execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        onFail(throwable);
+                        onDone();
+                    }
+                });
+            }
+        }
+
+        public void cancel() {
+            cancel(true);
+        }
+
+        public void cancel(boolean mayInterruptIfRunning) {
+            synchronized (state) {
+                if (state.get() > RUNNING) return;
+                state.set(CANCELLED);
+            }
+            if (mayInterruptIfRunning) {
+                if (runner != null) {
+                    runner.interrupt();
+                }
+            }
+
+            getDeliver().execute(new Runnable() {
+                @Override
+                public void run() {
+                    onCancel();
+                    onDone();
+                }
+            });
+        }
+
+        private void timeout() {
+            synchronized (state) {
+                if (state.get() > RUNNING) return;
+                state.set(TIMEOUT);
+            }
+            if (runner != null) {
+                runner.interrupt();
+            }
+        }
+
+
+        public boolean isCanceled() {
+            return state.get() >= CANCELLED;
+        }
+
+        public boolean isDone() {
+            return state.get() > RUNNING;
+        }
+
+        public Task<T> setDeliver(Executor deliver) {
+            this.deliver = deliver;
+            return this;
+        }
+
+        /**
+         * Scheduled task doesn't support timeout.
+         */
+        public Task<T> setTimeout(final long timeoutMillis, final OnTimeoutListener listener) {
+            mTimeoutMillis = timeoutMillis;
+            mTimeoutListener = listener;
+            return this;
+        }
+
+        private void setSchedule(boolean isSchedule) {
+            this.isSchedule = isSchedule;
+        }
+
+        private Executor getDeliver() {
+            if (deliver == null) {
+                return getGlobalDeliver();
+            }
+            return deliver;
+        }
+
+        @CallSuper
+        protected void onDone() {
+            TASK_POOL_MAP.remove(this);
+            if (mTimer != null) {
+                mTimer.cancel();
+                mTimer = null;
+                mTimeoutListener = null;
+            }
+        }
+
+        public interface OnTimeoutListener {
+            void onTimeout();
+        }
+    }
+
+    public static class SyncValue<T> {
+
+        private CountDownLatch mLatch = new CountDownLatch(1);
+        private AtomicBoolean  mFlag  = new AtomicBoolean();
+        private T              mValue;
+
+        public void setValue(T value) {
+            if (mFlag.compareAndSet(false, true)) {
+                mValue = value;
+                mLatch.countDown();
+            }
+        }
+
+        public T getValue() {
+            if (!mFlag.get()) {
+                try {
+                    mLatch.await();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+            return mValue;
+        }
+
+        public T getValue(long timeout, TimeUnit unit, T defaultValue) {
+            if (!mFlag.get()) {
+                try {
+                    mLatch.await(timeout, unit);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                    return defaultValue;
+                }
+            }
+            return mValue;
+        }
+    }
+
+    private static Executor getGlobalDeliver() {
+        if (sDeliver == null) {
+            sDeliver = new Executor() {
+                @Override
+                public void execute(@NonNull Runnable command) {
+                    runOnUiThread(command);
+                }
+            };
+        }
+        return sDeliver;
+    }
+}

+ 91 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ThrowableUtils.java

@@ -0,0 +1,91 @@
+package com.cooleshow.base.utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/02/12
+ *     desc  : utils about exception
+ * </pre>
+ */
+public class ThrowableUtils {
+
+    private static final String LINE_SEP = System.getProperty("line.separator");
+
+    private ThrowableUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    public static String getFullStackTrace(Throwable throwable) {
+        final List<Throwable> throwableList = new ArrayList<>();
+        while (throwable != null && !throwableList.contains(throwable)) {
+            throwableList.add(throwable);
+            throwable = throwable.getCause();
+        }
+        final int size = throwableList.size();
+        final List<String> frames = new ArrayList<>();
+        List<String> nextTrace = getStackFrameList(throwableList.get(size - 1));
+        for (int i = size; --i >= 0; ) {
+            final List<String> trace = nextTrace;
+            if (i != 0) {
+                nextTrace = getStackFrameList(throwableList.get(i - 1));
+                removeCommonFrames(trace, nextTrace);
+            }
+            if (i == size - 1) {
+                frames.add(throwableList.get(i).toString());
+            } else {
+                frames.add(" Caused by: " + throwableList.get(i).toString());
+            }
+            frames.addAll(trace);
+        }
+        StringBuilder sb = new StringBuilder();
+        for (final String element : frames) {
+            sb.append(element).append(LINE_SEP);
+        }
+        return sb.toString();
+    }
+
+    private static List<String> getStackFrameList(final Throwable throwable) {
+        final StringWriter sw = new StringWriter();
+        final PrintWriter pw = new PrintWriter(sw, true);
+        throwable.printStackTrace(pw);
+        final String stackTrace = sw.toString();
+        final StringTokenizer frames = new StringTokenizer(stackTrace, LINE_SEP);
+        final List<String> list = new ArrayList<>();
+        boolean traceStarted = false;
+        while (frames.hasMoreTokens()) {
+            final String token = frames.nextToken();
+            // Determine if the line starts with <whitespace>at
+            final int at = token.indexOf("at");
+            if (at != -1 && token.substring(0, at).trim().isEmpty()) {
+                traceStarted = true;
+                list.add(token);
+            } else if (traceStarted) {
+                break;
+            }
+        }
+        return list;
+    }
+
+    private static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {
+        int causeFrameIndex = causeFrames.size() - 1;
+        int wrapperFrameIndex = wrapperFrames.size() - 1;
+        while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
+            // Remove the frame from the cause trace if it is the same
+            // as in the wrapper trace
+            final String causeFrame = causeFrames.get(causeFrameIndex);
+            final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
+            if (causeFrame.equals(wrapperFrame)) {
+                causeFrames.remove(causeFrameIndex);
+            }
+            causeFrameIndex--;
+            wrapperFrameIndex--;
+        }
+    }
+}

+ 1636 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/TimeUtils.java

@@ -0,0 +1,1636 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.provider.Settings;
+
+
+import com.cooleshow.base.constanst.TimeConstants;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/02
+ *     desc  : utils about time
+ * </pre>
+ */
+public final class TimeUtils {
+
+    private static final ThreadLocal<Map<String, SimpleDateFormat>> SDF_THREAD_LOCAL
+            = new ThreadLocal<Map<String, SimpleDateFormat>>() {
+        @Override
+        protected Map<String, SimpleDateFormat> initialValue() {
+            return new HashMap<>();
+        }
+    };
+
+    private static SimpleDateFormat getDefaultFormat() {
+        return getSafeDateFormat("yyyy-MM-dd HH:mm:ss");
+    }
+    /**
+     * Checks whether the device is using Network Provided Time or not.
+     *  Useful in situations where you want to verify that the device has a correct time set, to avoid fraud, or if you want to prevent the user from messing with the time and abusing your "one-time" and "expiring" features. 
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isUsingNetworkProvidedTime() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return Settings.Global.getInt(Utils.getApp().getContentResolver(), Settings.Global.AUTO_TIME, 0) == 1;
+        } else {
+            return android.provider.Settings.System.getInt(Utils.getApp().getContentResolver(), android.provider.Settings.System.AUTO_TIME, 0) == 1;
+        }
+    }
+    
+
+    @SuppressLint("SimpleDateFormat")
+    public static SimpleDateFormat getSafeDateFormat(String pattern) {
+        Map<String, SimpleDateFormat> sdfMap = SDF_THREAD_LOCAL.get();
+        //noinspection ConstantConditions
+        SimpleDateFormat simpleDateFormat = sdfMap.get(pattern);
+        if (simpleDateFormat == null) {
+            simpleDateFormat = new SimpleDateFormat(pattern);
+            sdfMap.put(pattern, simpleDateFormat);
+        }
+        return simpleDateFormat;
+    }
+
+    private TimeUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Milliseconds to the formatted time string.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param millis The milliseconds.
+     * @return the formatted time string
+     */
+    public static String millis2String(final long millis) {
+        return millis2String(millis, getDefaultFormat());
+    }
+
+    /**
+     * Milliseconds to the formatted time string.
+     *
+     * @param millis  The milliseconds.
+     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm
+     * @return the formatted time string
+     */
+    public static String millis2String(long millis, @NonNull final String pattern) {
+        return millis2String(millis, getSafeDateFormat(pattern));
+    }
+
+    /**
+     * Milliseconds to the formatted time string.
+     *
+     * @param millis The milliseconds.
+     * @param format The format.
+     * @return the formatted time string
+     */
+    public static String millis2String(final long millis, @NonNull final DateFormat format) {
+        return format.format(new Date(millis));
+    }
+
+    /**
+     * Formatted time string to the milliseconds.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the milliseconds
+     */
+    public static long string2Millis(final String time) {
+        return string2Millis(time, getDefaultFormat());
+    }
+
+    /**
+     * Formatted time string to the milliseconds.
+     *
+     * @param time    The formatted time string.
+     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm
+     * @return the milliseconds
+     */
+    public static long string2Millis(final String time, @NonNull final String pattern) {
+        return string2Millis(time, getSafeDateFormat(pattern));
+    }
+
+    /**
+     * Formatted time string to the milliseconds.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the milliseconds
+     */
+    public static long string2Millis(final String time, @NonNull final DateFormat format) {
+        try {
+            return format.parse(time).getTime();
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return -1;
+    }
+
+    /**
+     * Formatted time string to the date.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the date
+     */
+    public static Date string2Date(final String time) {
+        return string2Date(time, getDefaultFormat());
+    }
+
+    /**
+     * Formatted time string to the date.
+     *
+     * @param time    The formatted time string.
+     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm
+     * @return the date
+     */
+    public static Date string2Date(final String time, @NonNull final String pattern) {
+        return string2Date(time, getSafeDateFormat(pattern));
+    }
+
+    /**
+     * Formatted time string to the date.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the date
+     */
+    public static Date string2Date(final String time, @NonNull final DateFormat format) {
+        try {
+            return format.parse(time);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Date to the formatted time string.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param date The date.
+     * @return the formatted time string
+     */
+    public static String date2String(final Date date) {
+        return date2String(date, getDefaultFormat());
+    }
+
+    /**
+     * Date to the formatted time string.
+     *
+     * @param date    The date.
+     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm
+     * @return the formatted time string
+     */
+    public static String date2String(final Date date, @NonNull final String pattern) {
+        return getSafeDateFormat(pattern).format(date);
+    }
+
+    /**
+     * Date to the formatted time string.
+     *
+     * @param date   The date.
+     * @param format The format.
+     * @return the formatted time string
+     */
+    public static String date2String(final Date date, @NonNull final DateFormat format) {
+        return format.format(date);
+    }
+
+    /**
+     * Date to the milliseconds.
+     *
+     * @param date The date.
+     * @return the milliseconds
+     */
+    public static long date2Millis(final Date date) {
+        return date.getTime();
+    }
+
+    /**
+     * Milliseconds to the date.
+     *
+     * @param millis The milliseconds.
+     * @return the date
+     */
+    public static Date millis2Date(final long millis) {
+        return new Date(millis);
+    }
+
+    /**
+     * Return the time span, in unit.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time1 The first formatted time string.
+     * @param time2 The second formatted time string.
+     * @param unit  The unit of time span.
+     *              <ul>
+     *              <li>{@link TimeConstants#MSEC}</li>
+     *              <li>{@link TimeConstants#SEC }</li>
+     *              <li>{@link TimeConstants#MIN }</li>
+     *              <li>{@link TimeConstants#HOUR}</li>
+     *              <li>{@link TimeConstants#DAY }</li>
+     *              </ul>
+     * @return the time span, in unit
+     */
+    public static long getTimeSpan(final String time1,
+                                   final String time2,
+                                   @TimeConstants.Unit final int unit) {
+        return getTimeSpan(time1, time2, getDefaultFormat(), unit);
+    }
+
+    /**
+     * Return the time span, in unit.
+     *
+     * @param time1  The first formatted time string.
+     * @param time2  The second formatted time string.
+     * @param format The format.
+     * @param unit   The unit of time span.
+     *               <ul>
+     *               <li>{@link TimeConstants#MSEC}</li>
+     *               <li>{@link TimeConstants#SEC }</li>
+     *               <li>{@link TimeConstants#MIN }</li>
+     *               <li>{@link TimeConstants#HOUR}</li>
+     *               <li>{@link TimeConstants#DAY }</li>
+     *               </ul>
+     * @return the time span, in unit
+     */
+    public static long getTimeSpan(final String time1,
+                                   final String time2,
+                                   @NonNull final DateFormat format,
+                                   @TimeConstants.Unit final int unit) {
+        return millis2TimeSpan(string2Millis(time1, format) - string2Millis(time2, format), unit);
+    }
+
+    /**
+     * Return the time span, in unit.
+     *
+     * @param date1 The first date.
+     * @param date2 The second date.
+     * @param unit  The unit of time span.
+     *              <ul>
+     *              <li>{@link TimeConstants#MSEC}</li>
+     *              <li>{@link TimeConstants#SEC }</li>
+     *              <li>{@link TimeConstants#MIN }</li>
+     *              <li>{@link TimeConstants#HOUR}</li>
+     *              <li>{@link TimeConstants#DAY }</li>
+     *              </ul>
+     * @return the time span, in unit
+     */
+    public static long getTimeSpan(final Date date1,
+                                   final Date date2,
+                                   @TimeConstants.Unit final int unit) {
+        return millis2TimeSpan(date2Millis(date1) - date2Millis(date2), unit);
+    }
+
+    /**
+     * Return the time span, in unit.
+     *
+     * @param millis1 The first milliseconds.
+     * @param millis2 The second milliseconds.
+     * @param unit    The unit of time span.
+     *                <ul>
+     *                <li>{@link TimeConstants#MSEC}</li>
+     *                <li>{@link TimeConstants#SEC }</li>
+     *                <li>{@link TimeConstants#MIN }</li>
+     *                <li>{@link TimeConstants#HOUR}</li>
+     *                <li>{@link TimeConstants#DAY }</li>
+     *                </ul>
+     * @return the time span, in unit
+     */
+    public static long getTimeSpan(final long millis1,
+                                   final long millis2,
+                                   @TimeConstants.Unit final int unit) {
+        return millis2TimeSpan(millis1 - millis2, unit);
+    }
+
+    /**
+     * Return the fit time span.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time1     The first formatted time string.
+     * @param time2     The second formatted time string.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0, return null</li>
+     *                  <li>precision = 1, return 天</li>
+     *                  <li>precision = 2, return 天, 小时</li>
+     *                  <li>precision = 3, return 天, 小时, 分钟</li>
+     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>
+     *                  <li>precision &gt;= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
+     *                  </ul>
+     * @return the fit time span
+     */
+    public static String getFitTimeSpan(final String time1,
+                                        final String time2,
+                                        final int precision) {
+        long delta = string2Millis(time1, getDefaultFormat()) - string2Millis(time2, getDefaultFormat());
+        return millis2FitTimeSpan(delta, precision);
+    }
+
+    /**
+     * Return the fit time span.
+     *
+     * @param time1     The first formatted time string.
+     * @param time2     The second formatted time string.
+     * @param format    The format.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0, return null</li>
+     *                  <li>precision = 1, return 天</li>
+     *                  <li>precision = 2, return 天, 小时</li>
+     *                  <li>precision = 3, return 天, 小时, 分钟</li>
+     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>
+     *                  <li>precision &gt;= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
+     *                  </ul>
+     * @return the fit time span
+     */
+    public static String getFitTimeSpan(final String time1,
+                                        final String time2,
+                                        @NonNull final DateFormat format,
+                                        final int precision) {
+        long delta = string2Millis(time1, format) - string2Millis(time2, format);
+        return millis2FitTimeSpan(delta, precision);
+    }
+
+    /**
+     * Return the fit time span.
+     *
+     * @param date1     The first date.
+     * @param date2     The second date.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0, return null</li>
+     *                  <li>precision = 1, return 天</li>
+     *                  <li>precision = 2, return 天, 小时</li>
+     *                  <li>precision = 3, return 天, 小时, 分钟</li>
+     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>
+     *                  <li>precision &gt;= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
+     *                  </ul>
+     * @return the fit time span
+     */
+    public static String getFitTimeSpan(final Date date1, final Date date2, final int precision) {
+        return millis2FitTimeSpan(date2Millis(date1) - date2Millis(date2), precision);
+    }
+
+    /**
+     * Return the fit time span.
+     *
+     * @param millis1   The first milliseconds.
+     * @param millis2   The second milliseconds.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0, return null</li>
+     *                  <li>precision = 1, return 天</li>
+     *                  <li>precision = 2, return 天, 小时</li>
+     *                  <li>precision = 3, return 天, 小时, 分钟</li>
+     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>
+     *                  <li>precision &gt;= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
+     *                  </ul>
+     * @return the fit time span
+     */
+    public static String getFitTimeSpan(final long millis1,
+                                        final long millis2,
+                                        final int precision) {
+        return millis2FitTimeSpan(millis1 - millis2, precision);
+    }
+
+    /**
+     * Return the current time in milliseconds.
+     *
+     * @return the current time in milliseconds
+     */
+    public static long getNowMills() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * Return the current formatted time string.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @return the current formatted time string
+     */
+    public static String getNowString() {
+        return millis2String(System.currentTimeMillis(), getDefaultFormat());
+    }
+
+    /**
+     * Return the current formatted time string.
+     *
+     * @param format The format.
+     * @return the current formatted time string
+     */
+    public static String getNowString(@NonNull final DateFormat format) {
+        return millis2String(System.currentTimeMillis(), format);
+    }
+
+    /**
+     * Return the current date.
+     *
+     * @return the current date
+     */
+    public static Date getNowDate() {
+        return new Date();
+    }
+
+    /**
+     * Return the time span by now, in unit.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @param unit The unit of time span.
+     *             <ul>
+     *             <li>{@link TimeConstants#MSEC}</li>
+     *             <li>{@link TimeConstants#SEC }</li>
+     *             <li>{@link TimeConstants#MIN }</li>
+     *             <li>{@link TimeConstants#HOUR}</li>
+     *             <li>{@link TimeConstants#DAY }</li>
+     *             </ul>
+     * @return the time span by now, in unit
+     */
+    public static long getTimeSpanByNow(final String time, @TimeConstants.Unit final int unit) {
+        return getTimeSpan(time, getNowString(), getDefaultFormat(), unit);
+    }
+
+    /**
+     * Return the time span by now, in unit.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @param unit   The unit of time span.
+     *               <ul>
+     *               <li>{@link TimeConstants#MSEC}</li>
+     *               <li>{@link TimeConstants#SEC }</li>
+     *               <li>{@link TimeConstants#MIN }</li>
+     *               <li>{@link TimeConstants#HOUR}</li>
+     *               <li>{@link TimeConstants#DAY }</li>
+     *               </ul>
+     * @return the time span by now, in unit
+     */
+    public static long getTimeSpanByNow(final String time,
+                                        @NonNull final DateFormat format,
+                                        @TimeConstants.Unit final int unit) {
+        return getTimeSpan(time, getNowString(format), format, unit);
+    }
+
+    /**
+     * Return the time span by now, in unit.
+     *
+     * @param date The date.
+     * @param unit The unit of time span.
+     *             <ul>
+     *             <li>{@link TimeConstants#MSEC}</li>
+     *             <li>{@link TimeConstants#SEC }</li>
+     *             <li>{@link TimeConstants#MIN }</li>
+     *             <li>{@link TimeConstants#HOUR}</li>
+     *             <li>{@link TimeConstants#DAY }</li>
+     *             </ul>
+     * @return the time span by now, in unit
+     */
+    public static long getTimeSpanByNow(final Date date, @TimeConstants.Unit final int unit) {
+        return getTimeSpan(date, new Date(), unit);
+    }
+
+    /**
+     * Return the time span by now, in unit.
+     *
+     * @param millis The milliseconds.
+     * @param unit   The unit of time span.
+     *               <ul>
+     *               <li>{@link TimeConstants#MSEC}</li>
+     *               <li>{@link TimeConstants#SEC }</li>
+     *               <li>{@link TimeConstants#MIN }</li>
+     *               <li>{@link TimeConstants#HOUR}</li>
+     *               <li>{@link TimeConstants#DAY }</li>
+     *               </ul>
+     * @return the time span by now, in unit
+     */
+    public static long getTimeSpanByNow(final long millis, @TimeConstants.Unit final int unit) {
+        return getTimeSpan(millis, System.currentTimeMillis(), unit);
+    }
+
+    /**
+     * Return the fit time span by now.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time      The formatted time string.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0,返回 null</li>
+     *                  <li>precision = 1,返回天</li>
+     *                  <li>precision = 2,返回天和小时</li>
+     *                  <li>precision = 3,返回天、小时和分钟</li>
+     *                  <li>precision = 4,返回天、小时、分钟和秒</li>
+     *                  <li>precision &gt;= 5,返回天、小时、分钟、秒和毫秒</li>
+     *                  </ul>
+     * @return the fit time span by now
+     */
+    public static String getFitTimeSpanByNow(final String time, final int precision) {
+        return getFitTimeSpan(time, getNowString(), getDefaultFormat(), precision);
+    }
+
+    /**
+     * Return the fit time span by now.
+     *
+     * @param time      The formatted time string.
+     * @param format    The format.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0,返回 null</li>
+     *                  <li>precision = 1,返回天</li>
+     *                  <li>precision = 2,返回天和小时</li>
+     *                  <li>precision = 3,返回天、小时和分钟</li>
+     *                  <li>precision = 4,返回天、小时、分钟和秒</li>
+     *                  <li>precision &gt;= 5,返回天、小时、分钟、秒和毫秒</li>
+     *                  </ul>
+     * @return the fit time span by now
+     */
+    public static String getFitTimeSpanByNow(final String time,
+                                             @NonNull final DateFormat format,
+                                             final int precision) {
+        return getFitTimeSpan(time, getNowString(format), format, precision);
+    }
+
+    /**
+     * Return the fit time span by now.
+     *
+     * @param date      The date.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0,返回 null</li>
+     *                  <li>precision = 1,返回天</li>
+     *                  <li>precision = 2,返回天和小时</li>
+     *                  <li>precision = 3,返回天、小时和分钟</li>
+     *                  <li>precision = 4,返回天、小时、分钟和秒</li>
+     *                  <li>precision &gt;= 5,返回天、小时、分钟、秒和毫秒</li>
+     *                  </ul>
+     * @return the fit time span by now
+     */
+    public static String getFitTimeSpanByNow(final Date date, final int precision) {
+        return getFitTimeSpan(date, getNowDate(), precision);
+    }
+
+    /**
+     * Return the fit time span by now.
+     *
+     * @param millis    The milliseconds.
+     * @param precision The precision of time span.
+     *                  <ul>
+     *                  <li>precision = 0,返回 null</li>
+     *                  <li>precision = 1,返回天</li>
+     *                  <li>precision = 2,返回天和小时</li>
+     *                  <li>precision = 3,返回天、小时和分钟</li>
+     *                  <li>precision = 4,返回天、小时、分钟和秒</li>
+     *                  <li>precision &gt;= 5,返回天、小时、分钟、秒和毫秒</li>
+     *                  </ul>
+     * @return the fit time span by now
+     */
+    public static String getFitTimeSpanByNow(final long millis, final int precision) {
+        return getFitTimeSpan(millis, System.currentTimeMillis(), precision);
+    }
+
+    /**
+     * Return the friendly time span by now.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the friendly time span by now
+     * <ul>
+     * <li>如果小于 1 秒钟内,显示刚刚</li>
+     * <li>如果在 1 分钟内,显示 XXX秒前</li>
+     * <li>如果在 1 小时内,显示 XXX分钟前</li>
+     * <li>如果在 1 小时外的今天内,显示今天15:32</li>
+     * <li>如果是昨天的,显示昨天15:32</li>
+     * <li>其余显示,2016-10-15</li>
+     * <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
+     * </ul>
+     */
+    public static String getFriendlyTimeSpanByNow(final String time) {
+        return getFriendlyTimeSpanByNow(time, getDefaultFormat());
+    }
+
+    /**
+     * Return the friendly time span by now.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the friendly time span by now
+     * <ul>
+     * <li>如果小于 1 秒钟内,显示刚刚</li>
+     * <li>如果在 1 分钟内,显示 XXX秒前</li>
+     * <li>如果在 1 小时内,显示 XXX分钟前</li>
+     * <li>如果在 1 小时外的今天内,显示今天15:32</li>
+     * <li>如果是昨天的,显示昨天15:32</li>
+     * <li>其余显示,2016-10-15</li>
+     * <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
+     * </ul>
+     */
+    public static String getFriendlyTimeSpanByNow(final String time,
+                                                  @NonNull final DateFormat format) {
+        return getFriendlyTimeSpanByNow(string2Millis(time, format));
+    }
+
+    /**
+     * Return the friendly time span by now.
+     *
+     * @param date The date.
+     * @return the friendly time span by now
+     * <ul>
+     * <li>如果小于 1 秒钟内,显示刚刚</li>
+     * <li>如果在 1 分钟内,显示 XXX秒前</li>
+     * <li>如果在 1 小时内,显示 XXX分钟前</li>
+     * <li>如果在 1 小时外的今天内,显示今天15:32</li>
+     * <li>如果是昨天的,显示昨天15:32</li>
+     * <li>其余显示,2016-10-15</li>
+     * <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
+     * </ul>
+     */
+    public static String getFriendlyTimeSpanByNow(final Date date) {
+        return getFriendlyTimeSpanByNow(date.getTime());
+    }
+
+    /**
+     * Return the friendly time span by now.
+     *
+     * @param millis The milliseconds.
+     * @return the friendly time span by now
+     * <ul>
+     * <li>如果小于 1 秒钟内,显示刚刚</li>
+     * <li>如果在 1 分钟内,显示 XXX秒前</li>
+     * <li>如果在 1 小时内,显示 XXX分钟前</li>
+     * <li>如果在 1 小时外的今天内,显示今天15:32</li>
+     * <li>如果是昨天的,显示昨天15:32</li>
+     * <li>其余显示,2016-10-15</li>
+     * <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
+     * </ul>
+     */
+    public static String getFriendlyTimeSpanByNow(final long millis) {
+        long now = System.currentTimeMillis();
+        long span = now - millis;
+        if (span < 0)
+            // U can read http://www.apihome.cn/api/java/Formatter.html to understand it.
+            return String.format("%tc", millis);
+        if (span < 1000) {
+            return "刚刚";
+        } else if (span < TimeConstants.MIN) {
+            return String.format(Locale.getDefault(), "%d秒前", span / TimeConstants.SEC);
+        } else if (span < TimeConstants.HOUR) {
+            return String.format(Locale.getDefault(), "%d分钟前", span / TimeConstants.MIN);
+        }
+        // 获取当天 00:00
+        long wee = getWeeOfToday();
+        if (millis >= wee) {
+            return String.format("今天%tR", millis);
+        } else if (millis >= wee - TimeConstants.DAY) {
+            return String.format("昨天%tR", millis);
+        } else {
+            return String.format("%tF", millis);
+        }
+    }
+
+    private static long getWeeOfToday() {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTimeInMillis();
+    }
+
+    /**
+     * Return the milliseconds differ time span.
+     *
+     * @param millis   The milliseconds.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the milliseconds differ time span
+     */
+    public static long getMillis(final long millis,
+                                 final long timeSpan,
+                                 @TimeConstants.Unit final int unit) {
+        return millis + timeSpan2Millis(timeSpan, unit);
+    }
+
+    /**
+     * Return the milliseconds differ time span.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time     The formatted time string.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the milliseconds differ time span
+     */
+    public static long getMillis(final String time,
+                                 final long timeSpan,
+                                 @TimeConstants.Unit final int unit) {
+        return getMillis(time, getDefaultFormat(), timeSpan, unit);
+    }
+
+    /**
+     * Return the milliseconds differ time span.
+     *
+     * @param time     The formatted time string.
+     * @param format   The format.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the milliseconds differ time span.
+     */
+    public static long getMillis(final String time,
+                                 @NonNull final DateFormat format,
+                                 final long timeSpan,
+                                 @TimeConstants.Unit final int unit) {
+        return string2Millis(time, format) + timeSpan2Millis(timeSpan, unit);
+    }
+
+    /**
+     * Return the milliseconds differ time span.
+     *
+     * @param date     The date.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the milliseconds differ time span.
+     */
+    public static long getMillis(final Date date,
+                                 final long timeSpan,
+                                 @TimeConstants.Unit final int unit) {
+        return date2Millis(date) + timeSpan2Millis(timeSpan, unit);
+    }
+
+    /**
+     * Return the formatted time string differ time span.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param millis   The milliseconds.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span
+     */
+    public static String getString(final long millis,
+                                   final long timeSpan,
+                                   @TimeConstants.Unit final int unit) {
+        return getString(millis, getDefaultFormat(), timeSpan, unit);
+    }
+
+    /**
+     * Return the formatted time string differ time span.
+     *
+     * @param millis   The milliseconds.
+     * @param format   The format.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span
+     */
+    public static String getString(final long millis,
+                                   @NonNull final DateFormat format,
+                                   final long timeSpan,
+                                   @TimeConstants.Unit final int unit) {
+        return millis2String(millis + timeSpan2Millis(timeSpan, unit), format);
+    }
+
+    /**
+     * Return the formatted time string differ time span.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time     The formatted time string.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span
+     */
+    public static String getString(final String time,
+                                   final long timeSpan,
+                                   @TimeConstants.Unit final int unit) {
+        return getString(time, getDefaultFormat(), timeSpan, unit);
+    }
+
+    /**
+     * Return the formatted time string differ time span.
+     *
+     * @param time     The formatted time string.
+     * @param format   The format.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span
+     */
+    public static String getString(final String time,
+                                   @NonNull final DateFormat format,
+                                   final long timeSpan,
+                                   @TimeConstants.Unit final int unit) {
+        return millis2String(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit), format);
+    }
+
+    /**
+     * Return the formatted time string differ time span.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param date     The date.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span
+     */
+    public static String getString(final Date date,
+                                   final long timeSpan,
+                                   @TimeConstants.Unit final int unit) {
+        return getString(date, getDefaultFormat(), timeSpan, unit);
+    }
+
+    /**
+     * Return the formatted time string differ time span.
+     *
+     * @param date     The date.
+     * @param format   The format.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span
+     */
+    public static String getString(final Date date,
+                                   @NonNull final DateFormat format,
+                                   final long timeSpan,
+                                   @TimeConstants.Unit final int unit) {
+        return millis2String(date2Millis(date) + timeSpan2Millis(timeSpan, unit), format);
+    }
+
+    /**
+     * Return the date differ time span.
+     *
+     * @param millis   The milliseconds.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the date differ time span
+     */
+    public static Date getDate(final long millis,
+                               final long timeSpan,
+                               @TimeConstants.Unit final int unit) {
+        return millis2Date(millis + timeSpan2Millis(timeSpan, unit));
+    }
+
+    /**
+     * Return the date differ time span.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time     The formatted time string.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the date differ time span
+     */
+    public static Date getDate(final String time,
+                               final long timeSpan,
+                               @TimeConstants.Unit final int unit) {
+        return getDate(time, getDefaultFormat(), timeSpan, unit);
+    }
+
+    /**
+     * Return the date differ time span.
+     *
+     * @param time     The formatted time string.
+     * @param format   The format.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the date differ time span
+     */
+    public static Date getDate(final String time,
+                               @NonNull final DateFormat format,
+                               final long timeSpan,
+                               @TimeConstants.Unit final int unit) {
+        return millis2Date(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit));
+    }
+
+    /**
+     * Return the date differ time span.
+     *
+     * @param date     The date.
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the date differ time span
+     */
+    public static Date getDate(final Date date,
+                               final long timeSpan,
+                               @TimeConstants.Unit final int unit) {
+        return millis2Date(date2Millis(date) + timeSpan2Millis(timeSpan, unit));
+    }
+
+    /**
+     * Return the milliseconds differ time span by now.
+     *
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the milliseconds differ time span by now
+     */
+    public static long getMillisByNow(final long timeSpan, @TimeConstants.Unit final int unit) {
+        return getMillis(getNowMills(), timeSpan, unit);
+    }
+
+    /**
+     * Return the formatted time string differ time span by now.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span by now
+     */
+    public static String getStringByNow(final long timeSpan, @TimeConstants.Unit final int unit) {
+        return getStringByNow(timeSpan, getDefaultFormat(), unit);
+    }
+
+    /**
+     * Return the formatted time string differ time span by now.
+     *
+     * @param timeSpan The time span.
+     * @param format   The format.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the formatted time string differ time span by now
+     */
+    public static String getStringByNow(final long timeSpan,
+                                        @NonNull final DateFormat format,
+                                        @TimeConstants.Unit final int unit) {
+        return getString(getNowMills(), format, timeSpan, unit);
+    }
+
+    /**
+     * Return the date differ time span by now.
+     *
+     * @param timeSpan The time span.
+     * @param unit     The unit of time span.
+     *                 <ul>
+     *                 <li>{@link TimeConstants#MSEC}</li>
+     *                 <li>{@link TimeConstants#SEC }</li>
+     *                 <li>{@link TimeConstants#MIN }</li>
+     *                 <li>{@link TimeConstants#HOUR}</li>
+     *                 <li>{@link TimeConstants#DAY }</li>
+     *                 </ul>
+     * @return the date differ time span by now
+     */
+    public static Date getDateByNow(final long timeSpan, @TimeConstants.Unit final int unit) {
+        return getDate(getNowMills(), timeSpan, unit);
+    }
+
+    /**
+     * Return whether it is today.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isToday(final String time) {
+        return isToday(string2Millis(time, getDefaultFormat()));
+    }
+
+    /**
+     * Return whether it is today.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isToday(final String time, @NonNull final DateFormat format) {
+        return isToday(string2Millis(time, format));
+    }
+
+    /**
+     * Return whether it is today.
+     *
+     * @param date The date.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isToday(final Date date) {
+        return isToday(date.getTime());
+    }
+
+    /**
+     * Return whether it is today.
+     *
+     * @param millis The milliseconds.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isToday(final long millis) {
+        long wee = getWeeOfToday();
+        return millis >= wee && millis < wee + TimeConstants.DAY;
+    }
+
+    /**
+     * Return whether it is leap year.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLeapYear(final String time) {
+        return isLeapYear(string2Date(time, getDefaultFormat()));
+    }
+
+    /**
+     * Return whether it is leap year.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLeapYear(final String time, @NonNull final DateFormat format) {
+        return isLeapYear(string2Date(time, format));
+    }
+
+    /**
+     * Return whether it is leap year.
+     *
+     * @param date The date.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLeapYear(final Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        int year = cal.get(Calendar.YEAR);
+        return isLeapYear(year);
+    }
+
+    /**
+     * Return whether it is leap year.
+     *
+     * @param millis The milliseconds.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLeapYear(final long millis) {
+        return isLeapYear(millis2Date(millis));
+    }
+
+    /**
+     * Return whether it is leap year.
+     *
+     * @param year The year.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isLeapYear(final int year) {
+        return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
+    }
+
+    /**
+     * Return the day of week in Chinese.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the day of week in Chinese
+     */
+    public static String getChineseWeek(final String time) {
+        return getChineseWeek(string2Date(time, getDefaultFormat()));
+    }
+
+    /**
+     * Return the day of week in Chinese.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the day of week in Chinese
+     */
+    public static String getChineseWeek(final String time, @NonNull final DateFormat format) {
+        return getChineseWeek(string2Date(time, format));
+    }
+
+    /**
+     * Return the day of week in Chinese.
+     *
+     * @param date The date.
+     * @return the day of week in Chinese
+     */
+    public static String getChineseWeek(final Date date) {
+        return new SimpleDateFormat("E", Locale.CHINA).format(date);
+    }
+
+    /**
+     * Return the day of week in Chinese.
+     *
+     * @param millis The milliseconds.
+     * @return the day of week in Chinese
+     */
+    public static String getChineseWeek(final long millis) {
+        return getChineseWeek(new Date(millis));
+    }
+
+    /**
+     * Return the day of week in US.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the day of week in US
+     */
+    public static String getUSWeek(final String time) {
+        return getUSWeek(string2Date(time, getDefaultFormat()));
+    }
+
+    /**
+     * Return the day of week in US.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the day of week in US
+     */
+    public static String getUSWeek(final String time, @NonNull final DateFormat format) {
+        return getUSWeek(string2Date(time, format));
+    }
+
+    /**
+     * Return the day of week in US.
+     *
+     * @param date The date.
+     * @return the day of week in US
+     */
+    public static String getUSWeek(final Date date) {
+        return new SimpleDateFormat("EEEE", Locale.US).format(date);
+    }
+
+    /**
+     * Return the day of week in US.
+     *
+     * @param millis The milliseconds.
+     * @return the day of week in US
+     */
+    public static String getUSWeek(final long millis) {
+        return getUSWeek(new Date(millis));
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAm() {
+        Calendar cal = Calendar.getInstance();
+        return cal.get(GregorianCalendar.AM_PM) == 0;
+    }
+
+    /**
+     * Return whether it is am.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAm(final String time) {
+        return getValueByCalendarField(time, getDefaultFormat(), GregorianCalendar.AM_PM) == 0;
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAm(final String time,
+                               @NonNull final DateFormat format) {
+        return getValueByCalendarField(time, format, GregorianCalendar.AM_PM) == 0;
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @param date The date.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAm(final Date date) {
+        return getValueByCalendarField(date, GregorianCalendar.AM_PM) == 0;
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @param millis The milliseconds.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isAm(final long millis) {
+        return getValueByCalendarField(millis, GregorianCalendar.AM_PM) == 0;
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPm() {
+        return !isAm();
+    }
+
+    /**
+     * Return whether it is am.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPm(final String time) {
+        return !isAm(time);
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPm(final String time,
+                               @NonNull final DateFormat format) {
+        return !isAm(time, format);
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @param date The date.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPm(final Date date) {
+        return !isAm(date);
+    }
+
+    /**
+     * Return whether it is am.
+     *
+     * @param millis The milliseconds.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isPm(final long millis) {
+        return !isAm(millis);
+    }
+
+    /**
+     * Returns the value of the given calendar field.
+     *
+     * @param field The given calendar field.
+     *              <ul>
+     *              <li>{@link Calendar#ERA}</li>
+     *              <li>{@link Calendar#YEAR}</li>
+     *              <li>{@link Calendar#MONTH}</li>
+     *              <li>...</li>
+     *              <li>{@link Calendar#DST_OFFSET}</li>
+     *              </ul>
+     * @return the value of the given calendar field
+     */
+    public static int getValueByCalendarField(final int field) {
+        Calendar cal = Calendar.getInstance();
+        return cal.get(field);
+    }
+
+    /**
+     * Returns the value of the given calendar field.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time  The formatted time string.
+     * @param field The given calendar field.
+     *              <ul>
+     *              <li>{@link Calendar#ERA}</li>
+     *              <li>{@link Calendar#YEAR}</li>
+     *              <li>{@link Calendar#MONTH}</li>
+     *              <li>...</li>
+     *              <li>{@link Calendar#DST_OFFSET}</li>
+     *              </ul>
+     * @return the value of the given calendar field
+     */
+    public static int getValueByCalendarField(final String time, final int field) {
+        return getValueByCalendarField(string2Date(time, getDefaultFormat()), field);
+    }
+
+    /**
+     * Returns the value of the given calendar field.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @param field  The given calendar field.
+     *               <ul>
+     *               <li>{@link Calendar#ERA}</li>
+     *               <li>{@link Calendar#YEAR}</li>
+     *               <li>{@link Calendar#MONTH}</li>
+     *               <li>...</li>
+     *               <li>{@link Calendar#DST_OFFSET}</li>
+     *               </ul>
+     * @return the value of the given calendar field
+     */
+    public static int getValueByCalendarField(final String time,
+                                              @NonNull final DateFormat format,
+                                              final int field) {
+        return getValueByCalendarField(string2Date(time, format), field);
+    }
+
+    /**
+     * Returns the value of the given calendar field.
+     *
+     * @param date  The date.
+     * @param field The given calendar field.
+     *              <ul>
+     *              <li>{@link Calendar#ERA}</li>
+     *              <li>{@link Calendar#YEAR}</li>
+     *              <li>{@link Calendar#MONTH}</li>
+     *              <li>...</li>
+     *              <li>{@link Calendar#DST_OFFSET}</li>
+     *              </ul>
+     * @return the value of the given calendar field
+     */
+    public static int getValueByCalendarField(final Date date, final int field) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        return cal.get(field);
+    }
+
+    /**
+     * Returns the value of the given calendar field.
+     *
+     * @param millis The milliseconds.
+     * @param field  The given calendar field.
+     *               <ul>
+     *               <li>{@link Calendar#ERA}</li>
+     *               <li>{@link Calendar#YEAR}</li>
+     *               <li>{@link Calendar#MONTH}</li>
+     *               <li>...</li>
+     *               <li>{@link Calendar#DST_OFFSET}</li>
+     *               </ul>
+     * @return the value of the given calendar field
+     */
+    public static int getValueByCalendarField(final long millis, final int field) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(millis);
+        return cal.get(field);
+    }
+
+    private static final String[] CHINESE_ZODIAC =
+            {"猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"};
+
+    /**
+     * Return the Chinese zodiac.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the Chinese zodiac
+     */
+    public static String getChineseZodiac(final String time) {
+        return getChineseZodiac(string2Date(time, getDefaultFormat()));
+    }
+
+    /**
+     * Return the Chinese zodiac.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the Chinese zodiac
+     */
+    public static String getChineseZodiac(final String time, @NonNull final DateFormat format) {
+        return getChineseZodiac(string2Date(time, format));
+    }
+
+    /**
+     * Return the Chinese zodiac.
+     *
+     * @param date The date.
+     * @return the Chinese zodiac
+     */
+    public static String getChineseZodiac(final Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        return CHINESE_ZODIAC[cal.get(Calendar.YEAR) % 12];
+    }
+
+    /**
+     * Return the Chinese zodiac.
+     *
+     * @param millis The milliseconds.
+     * @return the Chinese zodiac
+     */
+    public static String getChineseZodiac(final long millis) {
+        return getChineseZodiac(millis2Date(millis));
+    }
+
+    /**
+     * Return the Chinese zodiac.
+     *
+     * @param year The year.
+     * @return the Chinese zodiac
+     */
+    public static String getChineseZodiac(final int year) {
+        return CHINESE_ZODIAC[year % 12];
+    }
+
+    private static final int[]    ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22};
+    private static final String[] ZODIAC       = {
+            "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座",
+            "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座"
+    };
+
+    /**
+     * Return the zodiac.
+     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
+     *
+     * @param time The formatted time string.
+     * @return the zodiac
+     */
+    public static String getZodiac(final String time) {
+        return getZodiac(string2Date(time, getDefaultFormat()));
+    }
+
+    /**
+     * Return the zodiac.
+     *
+     * @param time   The formatted time string.
+     * @param format The format.
+     * @return the zodiac
+     */
+    public static String getZodiac(final String time, @NonNull final DateFormat format) {
+        return getZodiac(string2Date(time, format));
+    }
+
+    /**
+     * Return the zodiac.
+     *
+     * @param date The date.
+     * @return the zodiac
+     */
+    public static String getZodiac(final Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        int month = cal.get(Calendar.MONTH) + 1;
+        int day = cal.get(Calendar.DAY_OF_MONTH);
+        return getZodiac(month, day);
+    }
+
+    /**
+     * Return the zodiac.
+     *
+     * @param millis The milliseconds.
+     * @return the zodiac
+     */
+    public static String getZodiac(final long millis) {
+        return getZodiac(millis2Date(millis));
+    }
+
+    /**
+     * Return the zodiac.
+     *
+     * @param month The month.
+     * @param day   The day.
+     * @return the zodiac
+     */
+    public static String getZodiac(final int month, final int day) {
+        return ZODIAC[day >= ZODIAC_FLAGS[month - 1]
+                ? month - 1
+                : (month + 10) % 12];
+    }
+
+    private static long timeSpan2Millis(final long timeSpan, @TimeConstants.Unit final int unit) {
+        return timeSpan * unit;
+    }
+
+    private static long millis2TimeSpan(final long millis, @TimeConstants.Unit final int unit) {
+        return millis / unit;
+    }
+
+    static String millis2FitTimeSpan(long millis, int precision) {
+        if (precision <= 0) return null;
+        precision = Math.min(precision, 5);
+        String[] units = {"天", "小时", "分钟", "秒", "毫秒"};
+        if (millis == 0) return 0 + units[precision - 1];
+        StringBuilder sb = new StringBuilder();
+        if (millis < 0) {
+            sb.append("-");
+            millis = -millis;
+        }
+        int[] unitLen = {86400000, 3600000, 60000, 1000, 1};
+        for (int i = 0; i < precision; i++) {
+            if (millis >= unitLen[i]) {
+                long mode = millis / unitLen[i];
+                millis -= mode * unitLen[i];
+                sb.append(mode).append(units[i]);
+            }
+        }
+        return sb.toString();
+    }
+
+}

+ 912 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ToastUtils.java

@@ -0,0 +1,912 @@
+package com.cooleshow.base.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+
+import com.cooleshow.base.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringDef;
+import androidx.annotation.StringRes;
+import androidx.core.app.NotificationManagerCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/09/29
+ *     desc  : utils about toast
+ * </pre>
+ */
+public final class ToastUtils {
+
+    @StringDef({MODE.LIGHT, MODE.DARK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MODE {
+        String LIGHT = "light";
+        String DARK  = "dark";
+    }
+
+    private static final String     TAG_TOAST     = "TAG_TOAST";
+    private static final int        COLOR_DEFAULT = 0xFEFFFFFF;
+    private static final String     NULL          = "toast null";
+    private static final String     NOTHING       = "toast nothing";
+    private static final ToastUtils DEFAULT_MAKER = make();
+
+    private static WeakReference<IToast> sWeakToast;
+
+    private String     mMode;
+    private int        mGravity            = -1;
+    private int        mXOffset            = -1;
+    private int        mYOffset            = -1;
+    private int        mBgColor            = COLOR_DEFAULT;
+    private int        mBgResource         = -1;
+    private int        mTextColor          = COLOR_DEFAULT;
+    private int        mTextSize           = -1;
+    private boolean    isLong              = false;
+    private Drawable[] mIcons              = new Drawable[4];
+    private boolean    isNotUseSystemToast = false;
+
+    /**
+     * Make a toast.
+     *
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public static ToastUtils make() {
+        return new ToastUtils();
+    }
+
+    /**
+     * @param mode The mode.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setMode(@MODE String mode) {
+        mMode = mode;
+        return this;
+    }
+
+    /**
+     * Set the gravity.
+     *
+     * @param gravity The gravity.
+     * @param xOffset X-axis offset, in pixel.
+     * @param yOffset Y-axis offset, in pixel.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setGravity(final int gravity, final int xOffset, final int yOffset) {
+        mGravity = gravity;
+        mXOffset = xOffset;
+        mYOffset = yOffset;
+        return this;
+    }
+
+    /**
+     * Set the color of background.
+     *
+     * @param backgroundColor The color of background.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setBgColor(@ColorInt final int backgroundColor) {
+        mBgColor = backgroundColor;
+        return this;
+    }
+
+    /**
+     * Set the resource of background.
+     *
+     * @param bgResource The resource of background.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setBgResource(@DrawableRes final int bgResource) {
+        mBgResource = bgResource;
+        return this;
+    }
+
+    /**
+     * Set the text color of toast.
+     *
+     * @param msgColor The text color of toast.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setTextColor(@ColorInt final int msgColor) {
+        mTextColor = msgColor;
+        return this;
+    }
+
+    /**
+     * Set the text size of toast.
+     *
+     * @param textSize The text size of toast.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setTextSize(final int textSize) {
+        mTextSize = textSize;
+        return this;
+    }
+
+    /**
+     * Set the toast for a long period of time.
+     *
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setDurationIsLong(boolean isLong) {
+        this.isLong = isLong;
+        return this;
+    }
+
+    /**
+     * Set the left icon of toast.
+     *
+     * @param resId The left icon resource identifier.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setLeftIcon(@DrawableRes int resId) {
+        return setLeftIcon(ContextCompat.getDrawable(Utils.getApp(), resId));
+    }
+
+    /**
+     * Set the left icon of toast.
+     *
+     * @param drawable The left icon drawable.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setLeftIcon(@Nullable Drawable drawable) {
+        mIcons[0] = drawable;
+        return this;
+    }
+
+    /**
+     * Set the top icon of toast.
+     *
+     * @param resId The top icon resource identifier.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setTopIcon(@DrawableRes int resId) {
+        return setTopIcon(ContextCompat.getDrawable(Utils.getApp(), resId));
+    }
+
+    /**
+     * Set the top icon of toast.
+     *
+     * @param drawable The top icon drawable.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setTopIcon(@Nullable Drawable drawable) {
+        mIcons[1] = drawable;
+        return this;
+    }
+
+    /**
+     * Set the right icon of toast.
+     *
+     * @param resId The right icon resource identifier.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setRightIcon(@DrawableRes int resId) {
+        return setRightIcon(ContextCompat.getDrawable(Utils.getApp(), resId));
+    }
+
+    /**
+     * Set the right icon of toast.
+     *
+     * @param drawable The right icon drawable.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setRightIcon(@Nullable Drawable drawable) {
+        mIcons[2] = drawable;
+        return this;
+    }
+
+    /**
+     * Set the left bottom of toast.
+     *
+     * @param resId The bottom icon resource identifier.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setBottomIcon(int resId) {
+        return setBottomIcon(ContextCompat.getDrawable(Utils.getApp(), resId));
+    }
+
+    /**
+     * Set the bottom icon of toast.
+     *
+     * @param drawable The bottom icon drawable.
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setBottomIcon(@Nullable Drawable drawable) {
+        mIcons[3] = drawable;
+        return this;
+    }
+
+    /**
+     * Set not use system toast.
+     *
+     * @return the single {@link ToastUtils} instance
+     */
+    @NonNull
+    public final ToastUtils setNotUseSystemToast() {
+        isNotUseSystemToast = true;
+        return this;
+    }
+
+    /**
+     * Return the default {@link ToastUtils} instance.
+     *
+     * @return the default {@link ToastUtils} instance
+     */
+    @NonNull
+    public static ToastUtils getDefaultMaker() {
+        return DEFAULT_MAKER;
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param text The text.
+     */
+    public final void show(@Nullable final CharSequence text) {
+        show(text, getDuration(), this);
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param resId The resource id for text.
+     */
+    public final void show(@StringRes final int resId) {
+        show(UtilsBridge.getString(resId), getDuration(), this);
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param resId The resource id for text.
+     * @param args  The args.
+     */
+    public final void show(@StringRes final int resId, final Object... args) {
+        show(UtilsBridge.getString(resId, args), getDuration(), this);
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param format The format.
+     * @param args   The args.
+     */
+    public final void show(@Nullable final String format, final Object... args) {
+        show(UtilsBridge.format(format, args), getDuration(), this);
+    }
+
+    /**
+     * Show custom toast.
+     */
+    public final void show(@NonNull final View view) {
+        show(view, getDuration(), this);
+    }
+
+    private int getDuration() {
+        return isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
+    }
+
+    private View tryApplyUtilsToastView(final CharSequence text) {
+        if (!MODE.DARK.equals(mMode) && !MODE.LIGHT.equals(mMode)
+                && mIcons[0] == null && mIcons[1] == null && mIcons[2] == null && mIcons[3] == null) {
+            return null;
+        }
+
+        View toastView = UtilsBridge.layoutId2View(R.layout.utils_toast_view);
+        TextView messageTv = toastView.findViewById(android.R.id.message);
+        if (MODE.DARK.equals(mMode)) {
+            GradientDrawable bg = (GradientDrawable) toastView.getBackground().mutate();
+            bg.setColor(Color.parseColor("#BB000000"));
+            messageTv.setTextColor(Color.WHITE);
+        }
+        messageTv.setText(text);
+        if (mIcons[0] != null) {
+            View leftIconView = toastView.findViewById(R.id.utvLeftIconView);
+            ViewCompat.setBackground(leftIconView, mIcons[0]);
+            leftIconView.setVisibility(View.VISIBLE);
+        }
+        if (mIcons[1] != null) {
+            View topIconView = toastView.findViewById(R.id.utvTopIconView);
+            ViewCompat.setBackground(topIconView, mIcons[1]);
+            topIconView.setVisibility(View.VISIBLE);
+        }
+        if (mIcons[2] != null) {
+            View rightIconView = toastView.findViewById(R.id.utvRightIconView);
+            ViewCompat.setBackground(rightIconView, mIcons[2]);
+            rightIconView.setVisibility(View.VISIBLE);
+        }
+        if (mIcons[3] != null) {
+            View bottomIconView = toastView.findViewById(R.id.utvBottomIconView);
+            ViewCompat.setBackground(bottomIconView, mIcons[3]);
+            bottomIconView.setVisibility(View.VISIBLE);
+        }
+        return toastView;
+    }
+
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param text The text.
+     */
+    public static void showShort(@Nullable final CharSequence text) {
+        show(text, Toast.LENGTH_SHORT, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param resId The resource id for text.
+     */
+    public static void showShort(@StringRes final int resId) {
+        show(UtilsBridge.getString(resId), Toast.LENGTH_SHORT, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param resId The resource id for text.
+     * @param args  The args.
+     */
+    public static void showShort(@StringRes final int resId, final Object... args) {
+        show(UtilsBridge.getString(resId, args), Toast.LENGTH_SHORT, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a short period of time.
+     *
+     * @param format The format.
+     * @param args   The args.
+     */
+    public static void showShort(@Nullable final String format, final Object... args) {
+        show(UtilsBridge.format(format, args), Toast.LENGTH_SHORT, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a long period of time.
+     *
+     * @param text The text.
+     */
+    public static void showLong(@Nullable final CharSequence text) {
+        show(text, Toast.LENGTH_LONG, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a long period of time.
+     *
+     * @param resId The resource id for text.
+     */
+    public static void showLong(@StringRes final int resId) {
+        show(UtilsBridge.getString(resId), Toast.LENGTH_LONG, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a long period of time.
+     *
+     * @param resId The resource id for text.
+     * @param args  The args.
+     */
+    public static void showLong(@StringRes final int resId, final Object... args) {
+        show(UtilsBridge.getString(resId, args), Toast.LENGTH_LONG, DEFAULT_MAKER);
+    }
+
+    /**
+     * Show the toast for a long period of time.
+     *
+     * @param format The format.
+     * @param args   The args.
+     */
+    public static void showLong(@Nullable final String format, final Object... args) {
+        show(UtilsBridge.format(format, args), Toast.LENGTH_LONG, DEFAULT_MAKER);
+    }
+
+    /**
+     * Cancel the toast.
+     */
+    public static void cancel() {
+        UtilsBridge.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (sWeakToast != null) {
+                    final IToast iToast = ToastUtils.sWeakToast.get();
+                    if (iToast != null) {
+                        iToast.cancel();
+                    }
+                    sWeakToast = null;
+                }
+            }
+        });
+    }
+
+    private static void show(@Nullable final CharSequence text, final int duration, final ToastUtils utils) {
+        show(null, getToastFriendlyText(text), duration, utils);
+    }
+
+    private static void show(@NonNull final View view, final int duration, final ToastUtils utils) {
+        show(view, null, duration, utils);
+    }
+
+    private static void show(@Nullable final View view,
+                             @Nullable final CharSequence text,
+                             final int duration,
+                             @NonNull final ToastUtils utils) {
+        UtilsBridge.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                cancel();
+                IToast iToast = newToast(utils);
+                ToastUtils.sWeakToast = new WeakReference<>(iToast);
+                if (view != null) {
+                    iToast.setToastView(view);
+                } else {
+                    iToast.setToastView(text);
+                }
+                iToast.show(duration);
+            }
+        });
+    }
+
+    private static CharSequence getToastFriendlyText(CharSequence src) {
+        CharSequence text = src;
+        if (text == null) {
+            text = NULL;
+        } else if (text.length() == 0) {
+            text = NOTHING;
+        }
+        return text;
+    }
+
+    private static IToast newToast(ToastUtils toastUtils) {
+        if (!toastUtils.isNotUseSystemToast) {
+            if (NotificationManagerCompat.from(Utils.getApp()).areNotificationsEnabled()) {
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+                    return new SystemToast(toastUtils);
+                }
+                if (!UtilsBridge.isGrantedDrawOverlays()) {
+                    return new SystemToast(toastUtils);
+                }
+            }
+        }
+
+        // not use system or notification disable
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
+            return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_TOAST);
+        } else if (UtilsBridge.isGrantedDrawOverlays()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+            } else {
+                return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_PHONE);
+            }
+        }
+        return new ActivityToast(toastUtils);
+    }
+
+    static final class SystemToast extends AbsToast {
+
+        SystemToast(ToastUtils toastUtils) {
+            super(toastUtils);
+            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
+                try {
+                    //noinspection JavaReflectionMemberAccess
+                    Field mTNField = Toast.class.getDeclaredField("mTN");
+                    mTNField.setAccessible(true);
+                    Object mTN = mTNField.get(mToast);
+                    Field mTNmHandlerField = mTNField.getType().getDeclaredField("mHandler");
+                    mTNmHandlerField.setAccessible(true);
+                    Handler tnHandler = (Handler) mTNmHandlerField.get(mTN);
+                    mTNmHandlerField.set(mTN, new SafeHandler(tnHandler));
+                } catch (Exception ignored) {/**/}
+            }
+        }
+
+        @Override
+        public void show(int duration) {
+            if (mToast == null) return;
+            mToast.setDuration(duration);
+            mToast.show();
+        }
+
+        static class SafeHandler extends Handler {
+            private Handler impl;
+
+            SafeHandler(Handler impl) {
+                this.impl = impl;
+            }
+
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                impl.handleMessage(msg);
+            }
+
+            @Override
+            public void dispatchMessage(@NonNull Message msg) {
+                try {
+                    impl.dispatchMessage(msg);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    static final class WindowManagerToast extends AbsToast {
+
+        private WindowManager mWM;
+
+        private WindowManager.LayoutParams mParams;
+
+        WindowManagerToast(ToastUtils toastUtils, int type) {
+            super(toastUtils);
+            mParams = new WindowManager.LayoutParams();
+            mWM = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);
+            mParams.type = type;
+        }
+
+        WindowManagerToast(ToastUtils toastUtils, WindowManager wm, int type) {
+            super(toastUtils);
+            mParams = new WindowManager.LayoutParams();
+            mWM = wm;
+            mParams.type = type;
+        }
+
+        @Override
+        public void show(final int duration) {
+            if (mToast == null) return;
+            mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+            mParams.format = PixelFormat.TRANSLUCENT;
+            mParams.windowAnimations = android.R.style.Animation_Toast;
+            mParams.setTitle("ToastWithoutNotification");
+            mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+            mParams.packageName = Utils.getApp().getPackageName();
+
+            mParams.gravity = mToast.getGravity();
+            if ((mParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+                mParams.horizontalWeight = 1.0f;
+            }
+            if ((mParams.gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+                mParams.verticalWeight = 1.0f;
+            }
+
+            mParams.x = mToast.getXOffset();
+            mParams.y = mToast.getYOffset();
+            mParams.horizontalMargin = mToast.getHorizontalMargin();
+            mParams.verticalMargin = mToast.getVerticalMargin();
+
+            try {
+                if (mWM != null) {
+                    mWM.addView(mToastView, mParams);
+                }
+            } catch (Exception ignored) {/**/}
+
+            UtilsBridge.runOnUiThreadDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    cancel();
+                }
+            }, duration == Toast.LENGTH_SHORT ? 2000 : 3500);
+        }
+
+        @Override
+        public void cancel() {
+            try {
+                if (mWM != null) {
+                    mWM.removeViewImmediate(mToastView);
+                    mWM = null;
+                }
+            } catch (Exception ignored) {/**/}
+            super.cancel();
+        }
+    }
+
+    static final class ActivityToast extends AbsToast {
+
+        private static int sShowingIndex = 0;
+
+        private Utils.ActivityLifecycleCallbacks mActivityLifecycleCallbacks;
+        private IToast                           iToast;
+
+        ActivityToast(ToastUtils toastUtils) {
+            super(toastUtils);
+        }
+
+        @Override
+        public void show(int duration) {
+            if (mToast == null) return;
+            if (!UtilsBridge.isAppForeground()) {
+                // try to use system toast
+                iToast = showSystemToast(duration);
+                return;
+            }
+            boolean hasAliveActivity = false;
+            for (final Activity activity : UtilsBridge.getActivityList()) {
+                if (!UtilsBridge.isActivityAlive(activity)) {
+                    continue;
+                }
+                if (!hasAliveActivity) {
+                    hasAliveActivity = true;
+                    iToast = showWithActivityWindow(activity, duration);
+                } else {
+                    showWithActivityView(activity, sShowingIndex, true);
+                }
+            }
+            if (hasAliveActivity) {
+                registerLifecycleCallback();
+                UtilsBridge.runOnUiThreadDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        cancel();
+                    }
+                }, duration == Toast.LENGTH_SHORT ? 2000 : 3500);
+
+                ++sShowingIndex;
+            } else {
+                // try to use system toast
+                iToast = showSystemToast(duration);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            if (isShowing()) {
+                unregisterLifecycleCallback();
+                for (Activity activity : UtilsBridge.getActivityList()) {
+                    if (!UtilsBridge.isActivityAlive(activity)) {
+                        continue;
+                    }
+                    final Window window = activity.getWindow();
+                    if (window != null) {
+                        ViewGroup decorView = (ViewGroup) window.getDecorView();
+                        View toastView = decorView.findViewWithTag(TAG_TOAST + (sShowingIndex - 1));
+                        if (toastView != null) {
+                            try {
+                                decorView.removeView(toastView);
+                            } catch (Exception ignored) {/**/}
+                        }
+                    }
+                }
+            }
+            if (iToast != null) {
+                iToast.cancel();
+                iToast = null;
+            }
+            super.cancel();
+        }
+
+        private IToast showSystemToast(int duration) {
+            SystemToast systemToast = new SystemToast(mToastUtils);
+            systemToast.mToast = mToast;
+            systemToast.show(duration);
+            return systemToast;
+        }
+
+        private IToast showWithActivityWindow(Activity activity, int duration) {
+            WindowManagerToast wmToast = new WindowManagerToast(mToastUtils, activity.getWindowManager(), WindowManager.LayoutParams.LAST_APPLICATION_WINDOW);
+            wmToast.mToastView = getToastViewSnapshot(-1);
+            wmToast.mToast = mToast;
+            wmToast.show(duration);
+            return wmToast;
+        }
+
+        private void showWithActivityView(final Activity activity, final int index, boolean useAnim) {
+            final Window window = activity.getWindow();
+            if (window != null) {
+                final ViewGroup decorView = (ViewGroup) window.getDecorView();
+                FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+                );
+                lp.gravity = mToast.getGravity();
+                lp.bottomMargin = mToast.getYOffset() + UtilsBridge.getNavBarHeight();
+                lp.topMargin = mToast.getYOffset() + UtilsBridge.getStatusBarHeight();
+                lp.leftMargin = mToast.getXOffset();
+                View toastViewSnapshot = getToastViewSnapshot(index);
+                if (useAnim) {
+                    toastViewSnapshot.setAlpha(0);
+                    toastViewSnapshot.animate().alpha(1).setDuration(200).start();
+                }
+                decorView.addView(toastViewSnapshot, lp);
+            }
+        }
+
+        private void registerLifecycleCallback() {
+            final int index = sShowingIndex;
+            mActivityLifecycleCallbacks = new Utils.ActivityLifecycleCallbacks() {
+                @Override
+                public void onActivityCreated(@NonNull Activity activity) {
+                    if (isShowing()) {
+                        showWithActivityView(activity, index, false);
+                    }
+                }
+            };
+            UtilsBridge.addActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
+        }
+
+        private void unregisterLifecycleCallback() {
+            UtilsBridge.removeActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
+            mActivityLifecycleCallbacks = null;
+        }
+
+        private boolean isShowing() {
+            return mActivityLifecycleCallbacks != null;
+        }
+    }
+
+    static abstract class AbsToast implements IToast {
+
+        protected Toast      mToast;
+        protected ToastUtils mToastUtils;
+        protected View       mToastView;
+
+        AbsToast(ToastUtils toastUtils) {
+            mToast = new Toast(Utils.getApp());
+            mToastUtils = toastUtils;
+
+            if (mToastUtils.mGravity != -1 || mToastUtils.mXOffset != -1 || mToastUtils.mYOffset != -1) {
+                mToast.setGravity(mToastUtils.mGravity, mToastUtils.mXOffset, mToastUtils.mYOffset);
+            }
+        }
+
+        @Override
+        public void setToastView(View view) {
+            mToastView = view;
+            mToast.setView(mToastView);
+        }
+
+        @Override
+        public void setToastView(CharSequence text) {
+            View utilsToastView = mToastUtils.tryApplyUtilsToastView(text);
+            if (utilsToastView != null) {
+                setToastView(utilsToastView);
+                processRtlIfNeed();
+                return;
+            }
+
+            mToastView = mToast.getView();
+            if (mToastView == null || mToastView.findViewById(android.R.id.message) == null) {
+                setToastView(UtilsBridge.layoutId2View(R.layout.utils_toast_view));
+            }
+
+            TextView messageTv = mToastView.findViewById(android.R.id.message);
+            messageTv.setText(text);
+            if (mToastUtils.mTextColor != COLOR_DEFAULT) {
+                messageTv.setTextColor(mToastUtils.mTextColor);
+            }
+            if (mToastUtils.mTextSize != -1) {
+                messageTv.setTextSize(mToastUtils.mTextSize);
+            }
+            setBg(messageTv);
+            processRtlIfNeed();
+        }
+
+        private void processRtlIfNeed() {
+            if (UtilsBridge.isLayoutRtl()) {
+                setToastView(getToastViewSnapshot(-1));
+            }
+        }
+
+        private void setBg(final TextView msgTv) {
+            if (mToastUtils.mBgResource != -1) {
+                mToastView.setBackgroundResource(mToastUtils.mBgResource);
+                msgTv.setBackgroundColor(Color.TRANSPARENT);
+            } else if (mToastUtils.mBgColor != COLOR_DEFAULT) {
+                Drawable toastBg = mToastView.getBackground();
+                Drawable msgBg = msgTv.getBackground();
+                if (toastBg != null && msgBg != null) {
+                    toastBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN));
+                    msgTv.setBackgroundColor(Color.TRANSPARENT);
+                } else if (toastBg != null) {
+                    toastBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN));
+                } else if (msgBg != null) {
+                    msgBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN));
+                } else {
+                    mToastView.setBackgroundColor(mToastUtils.mBgColor);
+                }
+            }
+        }
+
+        @Override
+        @CallSuper
+        public void cancel() {
+            if (mToast != null) {
+                mToast.cancel();
+            }
+            mToast = null;
+            mToastView = null;
+        }
+
+        View getToastViewSnapshot(final int index) {
+            Bitmap bitmap = UtilsBridge.view2Bitmap(mToastView);
+            ImageView toastIv = new ImageView(Utils.getApp());
+            toastIv.setTag(TAG_TOAST + index);
+            toastIv.setImageBitmap(bitmap);
+            return toastIv;
+        }
+    }
+
+    interface IToast {
+
+        void setToastView(View view);
+
+        void setToastView(CharSequence text);
+
+        void show(int duration);
+
+        void cancel();
+    }
+
+    public static final class UtilsMaxWidthRelativeLayout extends RelativeLayout {
+
+        private static final int SPACING = UtilsBridge.dp2px(80);
+
+        public UtilsMaxWidthRelativeLayout(Context context) {
+            super(context);
+        }
+
+        public UtilsMaxWidthRelativeLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public UtilsMaxWidthRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            int widthMaxSpec = MeasureSpec.makeMeasureSpec(UtilsBridge.getAppScreenWidth() - SPACING, MeasureSpec.AT_MOST);
+            super.onMeasure(widthMaxSpec, heightMeasureSpec);
+        }
+    }
+}

+ 215 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/TouchUtils.java

@@ -0,0 +1,215 @@
+package com.cooleshow.base.utils;
+
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import androidx.annotation.IntDef;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/08/26
+ *     desc  : utils about touch
+ * </pre>
+ */
+public class TouchUtils {
+
+    public static final int UNKNOWN = 0;
+    public static final int LEFT    = 1;
+    public static final int UP      = 2;
+    public static final int RIGHT   = 4;
+    public static final int DOWN    = 8;
+
+    @IntDef({LEFT, UP, RIGHT, DOWN})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Direction {
+    }
+
+    private TouchUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    public static void setOnTouchListener(final View v, final OnTouchUtilsListener listener) {
+        if (v == null || listener == null) {
+            return;
+        }
+        v.setOnTouchListener(listener);
+    }
+
+    public static abstract class OnTouchUtilsListener implements View.OnTouchListener {
+
+        private static final int STATE_DOWN = 0;
+        private static final int STATE_MOVE = 1;
+        private static final int STATE_STOP = 2;
+
+        private static final int MIN_TAP_TIME = 1000;
+
+        private int touchSlop;
+        private int downX, downY, lastX, lastY;
+        private int             state;
+        private int             direction;
+        private VelocityTracker velocityTracker;
+        private int             maximumFlingVelocity;
+        private int             minimumFlingVelocity;
+
+        public OnTouchUtilsListener() {
+            resetTouch(-1, -1);
+        }
+
+        private void resetTouch(int x, int y) {
+            downX = x;
+            downY = y;
+            lastX = x;
+            lastY = y;
+            state = STATE_DOWN;
+            direction = UNKNOWN;
+            if (velocityTracker != null) {
+                velocityTracker.clear();
+            }
+        }
+
+        public abstract boolean onDown(View view, int x, int y, MotionEvent event);
+
+        public abstract boolean onMove(View view,
+                                       @Direction int direction,
+                                       int x,
+                                       int y,
+                                       int dx,
+                                       int dy,
+                                       int totalX,
+                                       int totalY,
+                                       MotionEvent event);
+
+        public abstract boolean onStop(View view,
+                                       @Direction int direction,
+                                       int x,
+                                       int y,
+                                       int totalX,
+                                       int totalY,
+                                       int vx,
+                                       int vy,
+                                       MotionEvent event);
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            if (touchSlop == 0) {
+                touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+            }
+            if (maximumFlingVelocity == 0) {
+                maximumFlingVelocity =
+                        ViewConfiguration.get(v.getContext()).getScaledMaximumFlingVelocity();
+            }
+            if (minimumFlingVelocity == 0) {
+                minimumFlingVelocity =
+                        ViewConfiguration.get(v.getContext()).getScaledMinimumFlingVelocity();
+            }
+            if (velocityTracker == null) {
+                velocityTracker = VelocityTracker.obtain();
+            }
+            velocityTracker.addMovement(event);
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    return onUtilsDown(v, event);
+                case MotionEvent.ACTION_MOVE:
+                    return onUtilsMove(v, event);
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    return onUtilsStop(v, event);
+                default:
+                    break;
+            }
+            return false;
+        }
+
+        public boolean onUtilsDown(View view, MotionEvent event) {
+            int x = (int) event.getRawX();
+            int y = (int) event.getRawY();
+
+            resetTouch(x, y);
+            view.setPressed(true);
+            return onDown(view, x, y, event);
+        }
+
+        public boolean onUtilsMove(View view, MotionEvent event) {
+            int x = (int) event.getRawX();
+            int y = (int) event.getRawY();
+
+            if (downX == -1) {
+                // not receive down should reset
+                resetTouch(x, y);
+                view.setPressed(true);
+            }
+
+            if (state != STATE_MOVE) {
+                if (Math.abs(x - lastX) < touchSlop && Math.abs(y - lastY) < touchSlop) {
+                    return true;
+                }
+                state = STATE_MOVE;
+                if (Math.abs(x - lastX) >= Math.abs(y - lastY)) {
+                    if (x - lastX < 0) {
+                        direction = LEFT;
+                    } else {
+                        direction = RIGHT;
+                    }
+                } else {
+                    if (y - lastY < 0) {
+                        direction = UP;
+                    } else {
+                        direction = DOWN;
+                    }
+                }
+            }
+
+            boolean consumeMove =
+                    onMove(view, direction, x, y, x - lastX, y - lastY, x - downX, y - downY, event);
+            lastX = x;
+            lastY = y;
+            return consumeMove;
+        }
+
+        public boolean onUtilsStop(View view, MotionEvent event) {
+            int x = (int) event.getRawX();
+            int y = (int) event.getRawY();
+
+            int vx = 0, vy = 0;
+
+            if (velocityTracker != null) {
+                velocityTracker.computeCurrentVelocity(1000, maximumFlingVelocity);
+                vx = (int) velocityTracker.getXVelocity();
+                vy = (int) velocityTracker.getYVelocity();
+                velocityTracker.recycle();
+                if (Math.abs(vx) < minimumFlingVelocity) {
+                    vx = 0;
+                }
+                if (Math.abs(vy) < minimumFlingVelocity) {
+                    vy = 0;
+                }
+                velocityTracker = null;
+            }
+
+            view.setPressed(false);
+            boolean consumeStop = onStop(view, direction, x, y, x - downX, y - downY, vx, vy, event);
+
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                if (state == STATE_DOWN) {
+                    if (event.getEventTime() - event.getDownTime() <= MIN_TAP_TIME) {
+                        view.performClick();
+                    } else {
+                        view.performLongClick();
+                    }
+                }
+            }
+
+            resetTouch(-1, -1);
+
+            return consumeStop;
+        }
+    }
+}

+ 275 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/UiMessageUtils.java

@@ -0,0 +1,275 @@
+package com.cooleshow.base.utils;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+/**
+ * <pre>
+ *     author: blankj
+ *     blog  : http://blankj.com
+ *     time  : 2019/10/20
+ *     desc  : utils about ui message can replace LocalBroadcastManager
+ * </pre>
+ */
+public final class UiMessageUtils implements Handler.Callback {
+
+    private static final String  TAG   = "UiMessageUtils";
+    private static final boolean DEBUG = UtilsBridge.isAppDebug();
+
+    private final Handler   mHandler = new Handler(Looper.getMainLooper(), this);
+    private final UiMessage mMessage = new UiMessage(null);
+
+    private final SparseArray<List<UiMessageCallback>> mListenersSpecific  = new SparseArray<>();
+    private final List<UiMessageCallback>              mListenersUniversal = new ArrayList<>();
+    private final List<UiMessageCallback>              mDefensiveCopyList  = new ArrayList<>();
+
+    public static UiMessageUtils getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    private UiMessageUtils() {
+    }
+
+    /**
+     * Sends an empty Message containing only the message ID.
+     *
+     * @param id The message ID.
+     */
+    public final void send(final int id) {
+        mHandler.sendEmptyMessage(id);
+    }
+
+    /**
+     * Sends a message containing the ID and an arbitrary object.
+     *
+     * @param id  The message ID.
+     * @param obj The object.
+     */
+    public final void send(final int id, @NonNull final Object obj) {
+        mHandler.sendMessage(mHandler.obtainMessage(id, obj));
+    }
+
+    /**
+     * Add listener for specific type of message by its ID.
+     * Don't forget to call {@link #removeListener(UiMessageCallback)} or
+     * {@link #removeListeners(int)}
+     *
+     * @param id       The ID of message that will be only notified to listener.
+     * @param listener The listener.
+     */
+    public void addListener(int id, @NonNull final UiMessageCallback listener) {
+        synchronized (mListenersSpecific) {
+            List<UiMessageCallback> idListeners = mListenersSpecific.get(id);
+            if (idListeners == null) {
+                idListeners = new ArrayList<>();
+                mListenersSpecific.put(id, idListeners);
+            }
+            if (!idListeners.contains(listener)) {
+                idListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Add listener for all messages.
+     *
+     * @param listener The listener.
+     */
+    public void addListener(@NonNull final UiMessageCallback listener) {
+        synchronized (mListenersUniversal) {
+            if (!mListenersUniversal.contains(listener)) {
+                mListenersUniversal.add(listener);
+            } else {
+                if (DEBUG) {
+                    Log.w(TAG, "Listener is already added. " + listener.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove listener for all messages.
+     *
+     * @param listener The listener to remove.
+     */
+    public void removeListener(@NonNull final UiMessageCallback listener) {
+        synchronized (mListenersUniversal) {
+            if (DEBUG && !mListenersUniversal.contains(listener)) {
+                Log.w(TAG, "Trying to remove a listener that is not registered. " + listener.toString());
+            }
+            mListenersUniversal.remove(listener);
+        }
+    }
+
+    /**
+     * Remove all listeners for desired message ID.
+     *
+     * @param id The id of the message to stop listening to.
+     */
+    public void removeListeners(final int id) {
+        if (DEBUG) {
+            final List<UiMessageCallback> callbacks = mListenersSpecific.get(id);
+            if (callbacks == null || callbacks.size() == 0) {
+                Log.w(TAG, "Trying to remove specific listeners that are not registered. ID " + id);
+            }
+        }
+        synchronized (mListenersSpecific) {
+            mListenersSpecific.delete(id);
+        }
+    }
+
+    /**
+     * Remove the specific listener for desired message ID.
+     *
+     * @param id       The id of the message to stop listening to.
+     * @param listener The listener which should be removed.
+     */
+    public void removeListener(final int id, @NonNull final UiMessageCallback listener) {
+        synchronized (mListenersSpecific) {
+            final List<UiMessageCallback> callbacks = this.mListenersSpecific.get(id);
+            if (callbacks != null && !callbacks.isEmpty()) {
+                if (DEBUG) {
+                    if (!callbacks.contains(listener)) {
+                        Log.w(TAG, "Trying to remove specific listener that is not registered. ID " + id + ", " + listener);
+                        return;
+                    }
+                }
+                callbacks.remove(listener);
+            } else {
+                if (DEBUG) {
+                    Log.w(TAG, "Trying to remove specific listener that is not registered. ID " + id + ", " + listener);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        mMessage.setMessage(msg);
+        if (DEBUG) {
+            logMessageHandling(mMessage);
+        }
+
+        // process listeners for specified type of message what
+        synchronized (mListenersSpecific) {
+            final List<UiMessageCallback> idListeners = mListenersSpecific.get(msg.what);
+            if (idListeners != null) {
+                if (idListeners.size() == 0) {
+                    mListenersSpecific.remove(msg.what);
+                } else {
+                    mDefensiveCopyList.addAll(idListeners);
+                    for (final UiMessageCallback callback : mDefensiveCopyList) {
+                        callback.handleMessage(mMessage);
+                    }
+                    mDefensiveCopyList.clear();
+                }
+            }
+        }
+
+        // process universal listeners
+        synchronized (mListenersUniversal) {
+            if (mListenersUniversal.size() > 0) {
+                mDefensiveCopyList.addAll(mListenersUniversal);
+                for (final UiMessageCallback callback : mDefensiveCopyList) {
+                    callback.handleMessage(mMessage);
+                }
+                mDefensiveCopyList.clear();
+            }
+        }
+
+        mMessage.setMessage(null);
+
+        return true;
+    }
+
+    private void logMessageHandling(@NonNull final UiMessage msg) {
+        final List<UiMessageCallback> idListeners = mListenersSpecific.get(msg.getId());
+
+        if ((idListeners == null || idListeners.size() == 0) && mListenersUniversal.size() == 0) {
+            Log.w(TAG, "Delivering FAILED for message ID " + msg.getId() + ". No listeners. " + msg.toString());
+        } else {
+            final StringBuilder stringBuilder = new StringBuilder();
+            stringBuilder.append("Delivering message ID ");
+            stringBuilder.append(msg.getId());
+            stringBuilder.append(", Specific listeners: ");
+            if (idListeners == null || idListeners.size() == 0) {
+                stringBuilder.append(0);
+            } else {
+                stringBuilder.append(idListeners.size());
+                stringBuilder.append(" [");
+                for (int i = 0; i < idListeners.size(); i++) {
+                    stringBuilder.append(idListeners.get(i).getClass().getSimpleName());
+                    if (i < idListeners.size() - 1) {
+                        stringBuilder.append(",");
+                    }
+                }
+                stringBuilder.append("]");
+            }
+
+            stringBuilder.append(", Universal listeners: ");
+            synchronized (mListenersUniversal) {
+                if (mListenersUniversal.size() == 0) {
+                    stringBuilder.append(0);
+                } else {
+                    stringBuilder.append(mListenersUniversal.size());
+                    stringBuilder.append(" [");
+                    for (int i = 0; i < mListenersUniversal.size(); i++) {
+                        stringBuilder.append(mListenersUniversal.get(i).getClass().getSimpleName());
+                        if (i < mListenersUniversal.size() - 1) {
+                            stringBuilder.append(",");
+                        }
+                    }
+                    stringBuilder.append("], Message: ");
+                }
+            }
+            stringBuilder.append(msg.toString());
+
+            Log.v(TAG, stringBuilder.toString());
+        }
+    }
+
+    public static final class UiMessage {
+
+        private Message mMessage;
+
+        private UiMessage(final Message message) {
+            this.mMessage = message;
+        }
+
+        private void setMessage(final Message message) {
+            mMessage = message;
+        }
+
+        public int getId() {
+            return mMessage.what;
+        }
+
+        public Object getObject() {
+            return mMessage.obj;
+        }
+
+        @Override
+        public String toString() {
+            return "{ " +
+                    "id=" + getId() +
+                    ", obj=" + getObject() +
+                    " }";
+        }
+    }
+
+    public interface UiMessageCallback {
+        void handleMessage(@NonNull UiMessage localMessage);
+    }
+
+    private static final class LazyHolder {
+        private static final UiMessageUtils INSTANCE = new UiMessageUtils();
+    }
+}

+ 356 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/UriUtils.java

@@ -0,0 +1,356 @@
+package com.cooleshow.base.utils;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+
+import androidx.core.content.FileProvider;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2018/04/20
+ *     desc  : utils about uri
+ * </pre>
+ */
+public final class UriUtils {
+
+    private UriUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Resource to uri.
+     * <p>res2Uri([res type]/[res name]) -> res2Uri(drawable/icon), res2Uri(raw/icon)</p>
+     * <p>res2Uri([resource_id]) -> res2Uri(R.drawable.icon)</p>
+     *
+     * @param resPath The path of res.
+     * @return uri
+     */
+    public static Uri res2Uri(String resPath) {
+        return Uri.parse("android.resource://" + Utils.getApp().getPackageName() + "/" + resPath);
+    }
+
+    /**
+     * File to uri.
+     *
+     * @param file The file.
+     * @return uri
+     */
+    public static Uri file2Uri(final File file) {
+        if (!UtilsBridge.isFileExists(file)) return null;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            String authority = Utils.getApp().getPackageName() + ".utilcode.fileprovider";
+            return FileProvider.getUriForFile(Utils.getApp(), authority, file);
+        } else {
+            return Uri.fromFile(file);
+        }
+    }
+
+    /**
+     * Uri to file.
+     *
+     * @param uri The uri.
+     * @return file
+     */
+    public static File uri2File(final Uri uri) {
+        if (uri == null) return null;
+        File file = uri2FileReal(uri);
+        if (file != null) return file;
+        return copyUri2Cache(uri);
+    }
+
+    /**
+     * Uri to file.
+     *
+     * @param uri The uri.
+     * @return file
+     */
+    private static File uri2FileReal(final Uri uri) {
+        Log.d("UriUtils", uri.toString());
+        String authority = uri.getAuthority();
+        String scheme = uri.getScheme();
+        String path = uri.getPath();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && path != null) {
+            String[] externals = new String[]{"/external/", "/external_path/"};
+            File file = null;
+            for (String external : externals) {
+                if (path.startsWith(external)) {
+                    file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+                            + path.replace(external, "/"));
+                    if (file.exists()) {
+                        Log.d("UriUtils", uri.toString() + " -> " + external);
+                        return file;
+                    }
+                }
+            }
+            file = null;
+            if (path.startsWith("/files_path/")) {
+                file = new File(Utils.getApp().getFilesDir().getAbsolutePath()
+                        + path.replace("/files_path/", "/"));
+            } else if (path.startsWith("/cache_path/")) {
+                file = new File(Utils.getApp().getCacheDir().getAbsolutePath()
+                        + path.replace("/cache_path/", "/"));
+            } else if (path.startsWith("/external_files_path/")) {
+                file = new File(Utils.getApp().getExternalFilesDir(null).getAbsolutePath()
+                        + path.replace("/external_files_path/", "/"));
+            } else if (path.startsWith("/external_cache_path/")) {
+                file = new File(Utils.getApp().getExternalCacheDir().getAbsolutePath()
+                        + path.replace("/external_cache_path/", "/"));
+            }
+            if (file != null && file.exists()) {
+                Log.d("UriUtils", uri.toString() + " -> " + path);
+                return file;
+            }
+        }
+        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+            if (path != null) return new File(path);
+            Log.d("UriUtils", uri.toString() + " parse failed. -> 0");
+            return null;
+        }// end 0
+        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+                && DocumentsContract.isDocumentUri(Utils.getApp(), uri)) {
+            if ("com.android.externalstorage.documents".equals(authority)) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+                if ("primary".equalsIgnoreCase(type)) {
+                    return new File(Environment.getExternalStorageDirectory() + "/" + split[1]);
+                } else {
+                    // Below logic is how External Storage provider build URI for documents
+                    // http://stackoverflow.com/questions/28605278/android-5-sd-card-label
+                    StorageManager mStorageManager = (StorageManager) Utils.getApp().getSystemService(Context.STORAGE_SERVICE);
+                    try {
+                        Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
+                        Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
+                        Method getUuid = storageVolumeClazz.getMethod("getUuid");
+                        Method getState = storageVolumeClazz.getMethod("getState");
+                        Method getPath = storageVolumeClazz.getMethod("getPath");
+                        Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
+                        Method isEmulated = storageVolumeClazz.getMethod("isEmulated");
+
+                        Object result = getVolumeList.invoke(mStorageManager);
+
+                        final int length = Array.getLength(result);
+                        for (int i = 0; i < length; i++) {
+                            Object storageVolumeElement = Array.get(result, i);
+                            //String uuid = (String) getUuid.invoke(storageVolumeElement);
+
+                            final boolean mounted = Environment.MEDIA_MOUNTED.equals(getState.invoke(storageVolumeElement))
+                                    || Environment.MEDIA_MOUNTED_READ_ONLY.equals(getState.invoke(storageVolumeElement));
+
+                            //if the media is not mounted, we need not get the volume details
+                            if (!mounted) continue;
+
+                            //Primary storage is already handled.
+                            if ((Boolean) isPrimary.invoke(storageVolumeElement)
+                                    && (Boolean) isEmulated.invoke(storageVolumeElement)) {
+                                continue;
+                            }
+
+                            String uuid = (String) getUuid.invoke(storageVolumeElement);
+
+                            if (uuid != null && uuid.equals(type)) {
+                                return new File(getPath.invoke(storageVolumeElement) + "/" + split[1]);
+                            }
+                        }
+                    } catch (Exception ex) {
+                        Log.d("UriUtils", uri.toString() + " parse failed. " + ex.toString() + " -> 1_0");
+                    }
+                }
+                Log.d("UriUtils", uri.toString() + " parse failed. -> 1_0");
+                return null;
+            }// end 1_0
+            else if ("com.android.providers.downloads.documents".equals(authority)) {
+                String id = DocumentsContract.getDocumentId(uri);
+                if (TextUtils.isEmpty(id)) {
+                    Log.d("UriUtils", uri.toString() + " parse failed(id is null). -> 1_1");
+                    return null;
+                }
+                if (id.startsWith("raw:")) {
+                    return new File(id.substring(4));
+                } else if (id.startsWith("msf:")) {
+                    id = id.split(":")[1];
+                }
+
+                long availableId = 0;
+                try {
+                    availableId = Long.parseLong(id);
+                } catch (Exception e) {
+                    return null;
+                }
+
+                String[] contentUriPrefixesToTry = new String[]{
+                        "content://downloads/public_downloads",
+                        "content://downloads/all_downloads",
+                        "content://downloads/my_downloads"
+                };
+
+                for (String contentUriPrefix : contentUriPrefixesToTry) {
+                    Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), availableId);
+                    try {
+                        File file = getFileFromUri(contentUri, "1_1");
+                        if (file != null) {
+                            return file;
+                        }
+                    } catch (Exception ignore) {
+                    }
+                }
+
+                Log.d("UriUtils", uri.toString() + " parse failed. -> 1_1");
+                return null;
+            }// end 1_1
+            else if ("com.android.providers.media.documents".equals(authority)) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+                Uri contentUri;
+                if ("image".equals(type)) {
+                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+                } else if ("video".equals(type)) {
+                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+                } else if ("audio".equals(type)) {
+                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+                } else {
+                    Log.d("UriUtils", uri.toString() + " parse failed. -> 1_2");
+                    return null;
+                }
+                final String selection = "_id=?";
+                final String[] selectionArgs = new String[]{split[1]};
+                return getFileFromUri(contentUri, selection, selectionArgs, "1_2");
+            }// end 1_2
+            else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+                return getFileFromUri(uri, "1_3");
+            }// end 1_3
+            else {
+                Log.d("UriUtils", uri.toString() + " parse failed. -> 1_4");
+                return null;
+            }// end 1_4
+        }// end 1
+        else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+            return getFileFromUri(uri, "2");
+        }// end 2
+        else {
+            Log.d("UriUtils", uri.toString() + " parse failed. -> 3");
+            return null;
+        }// end 3
+    }
+
+    private static File getFileFromUri(final Uri uri, final String code) {
+        return getFileFromUri(uri, null, null, code);
+    }
+
+    private static File getFileFromUri(final Uri uri,
+                                       final String selection,
+                                       final String[] selectionArgs,
+                                       final String code) {
+        if ("com.google.android.apps.photos.content".equals(uri.getAuthority())) {
+            if (!TextUtils.isEmpty(uri.getLastPathSegment())) {
+                return new File(uri.getLastPathSegment());
+            }
+        } else if ("com.tencent.mtt.fileprovider".equals(uri.getAuthority())) {
+            String path = uri.getPath();
+            if (!TextUtils.isEmpty(path)) {
+                File fileDir = Environment.getExternalStorageDirectory();
+                return new File(fileDir, path.substring("/QQBrowser".length(), path.length()));
+            }
+        } else if ("com.huawei.hidisk.fileprovider".equals(uri.getAuthority())) {
+            String path = uri.getPath();
+            if (!TextUtils.isEmpty(path)) {
+                return new File(path.replace("/root", ""));
+            }
+        }
+
+        final Cursor cursor = Utils.getApp().getContentResolver().query(
+                uri, new String[]{"_data"}, selection, selectionArgs, null);
+        if (cursor == null) {
+            Log.d("UriUtils", uri.toString() + " parse failed(cursor is null). -> " + code);
+            return null;
+        }
+        try {
+            if (cursor.moveToFirst()) {
+                final int columnIndex = cursor.getColumnIndex("_data");
+                if (columnIndex > -1) {
+                    return new File(cursor.getString(columnIndex));
+                } else {
+                    Log.d("UriUtils", uri.toString() + " parse failed(columnIndex: " + columnIndex + " is wrong). -> " + code);
+                    return null;
+                }
+            } else {
+                Log.d("UriUtils", uri.toString() + " parse failed(moveToFirst return false). -> " + code);
+                return null;
+            }
+        } catch (Exception e) {
+            Log.d("UriUtils", uri.toString() + " parse failed. -> " + code);
+            return null;
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private static File copyUri2Cache(Uri uri) {
+        Log.d("UriUtils", "copyUri2Cache() called");
+        InputStream is = null;
+        try {
+            is = Utils.getApp().getContentResolver().openInputStream(uri);
+            File file = new File(Utils.getApp().getCacheDir(), "" + System.currentTimeMillis());
+            UtilsBridge.writeFileFromIS(file.getAbsolutePath(), is);
+            return file;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * uri to input stream.
+     *
+     * @param uri The uri.
+     * @return the input stream
+     */
+    public static byte[] uri2Bytes(Uri uri) {
+        if (uri == null) return null;
+        InputStream is = null;
+        try {
+            is = Utils.getApp().getContentResolver().openInputStream(uri);
+            return UtilsBridge.inputStream2Bytes(is);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            Log.d("UriUtils", "uri to bytes failed.");
+            return null;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

Някои файлове не бяха показани, защото твърде много файлове са промени