Parcourir la source

com.airsmart.lib:permission Maven远程仓库代码提交

houjie il y a 4 ans
Parent
commit
baa0468926
100 fichiers modifiés avec 6988 ajouts et 36 suppressions
  1. 8 34
      .gitignore
  2. 10 0
      CONTRIBUTING.md
  3. 202 0
      LICENSE
  4. 55 2
      README.md
  5. 36 0
      build.gradle
  6. 41 0
      config.gradle
  7. BIN
      gradle/wrapper/gradle-wrapper.jar
  8. 5 0
      gradle/wrapper/gradle-wrapper.properties
  9. 160 0
      gradlew
  10. 90 0
      gradlew.bat
  11. 1 0
      permission/.gitignore
  12. 36 0
      permission/build.gradle
  13. 56 0
      permission/src/main/AndroidManifest.xml
  14. 43 0
      permission/src/main/aidl/com/yanzhenjie/permission/bridge/IBridge.aidl
  15. 29 0
      permission/src/main/java/com/yanzhenjie/permission/Action.java
  16. 350 0
      permission/src/main/java/com/yanzhenjie/permission/AndPermission.java
  17. 102 0
      permission/src/main/java/com/yanzhenjie/permission/Boot.java
  18. 383 0
      permission/src/main/java/com/yanzhenjie/permission/FileProvider.java
  19. 33 0
      permission/src/main/java/com/yanzhenjie/permission/Rationale.java
  20. 34 0
      permission/src/main/java/com/yanzhenjie/permission/RequestExecutor.java
  21. 211 0
      permission/src/main/java/com/yanzhenjie/permission/bridge/BridgeActivity.java
  22. 78 0
      permission/src/main/java/com/yanzhenjie/permission/bridge/BridgeRequest.java
  23. 83 0
      permission/src/main/java/com/yanzhenjie/permission/bridge/BridgeService.java
  24. 65 0
      permission/src/main/java/com/yanzhenjie/permission/bridge/Messenger.java
  25. 121 0
      permission/src/main/java/com/yanzhenjie/permission/bridge/RequestExecutor.java
  26. 48 0
      permission/src/main/java/com/yanzhenjie/permission/bridge/RequestManager.java
  27. 49 0
      permission/src/main/java/com/yanzhenjie/permission/checker/CalendarReadTest.java
  28. 73 0
      permission/src/main/java/com/yanzhenjie/permission/checker/CalendarWriteTest.java
  29. 49 0
      permission/src/main/java/com/yanzhenjie/permission/checker/CallLogReadTest.java
  30. 50 0
      permission/src/main/java/com/yanzhenjie/permission/checker/CallLogWriteTest.java
  31. 85 0
      permission/src/main/java/com/yanzhenjie/permission/checker/CameraTest.java
  32. 50 0
      permission/src/main/java/com/yanzhenjie/permission/checker/ContactsReadTest.java
  33. 98 0
      permission/src/main/java/com/yanzhenjie/permission/checker/ContactsWriteTest.java
  34. 41 0
      permission/src/main/java/com/yanzhenjie/permission/checker/DoubleChecker.java
  35. 50 0
      permission/src/main/java/com/yanzhenjie/permission/checker/LocationCoarseTest.java
  36. 51 0
      permission/src/main/java/com/yanzhenjie/permission/checker/LocationFineTest.java
  37. 47 0
      permission/src/main/java/com/yanzhenjie/permission/checker/PermissionChecker.java
  38. 50 0
      permission/src/main/java/com/yanzhenjie/permission/checker/PermissionTest.java
  39. 44 0
      permission/src/main/java/com/yanzhenjie/permission/checker/PhoneStateReadTest.java
  40. 110 0
      permission/src/main/java/com/yanzhenjie/permission/checker/RecordAudioTest.java
  41. 59 0
      permission/src/main/java/com/yanzhenjie/permission/checker/SensorActivityTest.java
  42. 59 0
      permission/src/main/java/com/yanzhenjie/permission/checker/SensorHeartTest.java
  43. 55 0
      permission/src/main/java/com/yanzhenjie/permission/checker/SipTest.java
  44. 50 0
      permission/src/main/java/com/yanzhenjie/permission/checker/SmsReadTest.java
  45. 67 0
      permission/src/main/java/com/yanzhenjie/permission/checker/StandardChecker.java
  46. 45 0
      permission/src/main/java/com/yanzhenjie/permission/checker/StorageReadTest.java
  47. 55 0
      permission/src/main/java/com/yanzhenjie/permission/checker/StorageWriteTest.java
  48. 201 0
      permission/src/main/java/com/yanzhenjie/permission/checker/StrictChecker.java
  49. 113 0
      permission/src/main/java/com/yanzhenjie/permission/install/BaseRequest.java
  50. 54 0
      permission/src/main/java/com/yanzhenjie/permission/install/InstallRequest.java
  51. 34 0
      permission/src/main/java/com/yanzhenjie/permission/install/NRequest.java
  52. 30 0
      permission/src/main/java/com/yanzhenjie/permission/install/NRequestFactory.java
  53. 67 0
      permission/src/main/java/com/yanzhenjie/permission/install/ORequest.java
  54. 30 0
      permission/src/main/java/com/yanzhenjie/permission/install/ORequestFactory.java
  55. 87 0
      permission/src/main/java/com/yanzhenjie/permission/notify/BaseRequest.java
  56. 65 0
      permission/src/main/java/com/yanzhenjie/permission/notify/NRequest.java
  57. 29 0
      permission/src/main/java/com/yanzhenjie/permission/notify/NRequestFactory.java
  58. 77 0
      permission/src/main/java/com/yanzhenjie/permission/notify/Notify.java
  59. 65 0
      permission/src/main/java/com/yanzhenjie/permission/notify/ORequest.java
  60. 29 0
      permission/src/main/java/com/yanzhenjie/permission/notify/ORequestFactory.java
  61. 46 0
      permission/src/main/java/com/yanzhenjie/permission/notify/PermissionRequest.java
  62. 87 0
      permission/src/main/java/com/yanzhenjie/permission/notify/listener/BaseRequest.java
  63. 44 0
      permission/src/main/java/com/yanzhenjie/permission/notify/listener/J1Request.java
  64. 30 0
      permission/src/main/java/com/yanzhenjie/permission/notify/listener/J1RequestFactory.java
  65. 65 0
      permission/src/main/java/com/yanzhenjie/permission/notify/listener/J2Request.java
  66. 30 0
      permission/src/main/java/com/yanzhenjie/permission/notify/listener/J2RequestFactory.java
  67. 46 0
      permission/src/main/java/com/yanzhenjie/permission/notify/listener/ListenerRequest.java
  68. 35 0
      permission/src/main/java/com/yanzhenjie/permission/notify/option/NotifyOption.java
  69. 53 0
      permission/src/main/java/com/yanzhenjie/permission/option/Option.java
  70. 110 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/BaseRequest.java
  71. 65 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/LRequest.java
  72. 30 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/LRequestFactory.java
  73. 65 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/MRequest.java
  74. 30 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/MRequestFactory.java
  75. 45 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/OverlayRequest.java
  76. 140 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/setting/LSettingPage.java
  77. 83 0
      permission/src/main/java/com/yanzhenjie/permission/overlay/setting/MSettingPage.java
  78. 138 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/BaseRequest.java
  79. 82 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/LRequest.java
  80. 29 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/LRequestFactory.java
  81. 118 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/MRequest.java
  82. 29 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/MRequestFactory.java
  83. 226 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/Permission.java
  84. 59 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/PermissionDef.java
  85. 63 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/PermissionRequest.java
  86. 128 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/Runtime.java
  87. 47 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/option/RuntimeOption.java
  88. 37 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/setting/AllRequest.java
  89. 133 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/setting/SettingPage.java
  90. 27 0
      permission/src/main/java/com/yanzhenjie/permission/runtime/setting/SettingRequest.java
  91. 57 0
      permission/src/main/java/com/yanzhenjie/permission/setting/Setting.java
  92. 87 0
      permission/src/main/java/com/yanzhenjie/permission/setting/write/BaseRequest.java
  93. 33 0
      permission/src/main/java/com/yanzhenjie/permission/setting/write/LWriteRequest.java
  94. 30 0
      permission/src/main/java/com/yanzhenjie/permission/setting/write/LWriteRequestFactory.java
  95. 65 0
      permission/src/main/java/com/yanzhenjie/permission/setting/write/MWriteRequest.java
  96. 30 0
      permission/src/main/java/com/yanzhenjie/permission/setting/write/MWriteRequestFactory.java
  97. 45 0
      permission/src/main/java/com/yanzhenjie/permission/setting/write/WriteRequest.java
  98. 56 0
      permission/src/main/java/com/yanzhenjie/permission/source/ActivitySource.java
  99. 67 0
      permission/src/main/java/com/yanzhenjie/permission/source/ContextSource.java
  100. 0 0
      permission/src/main/java/com/yanzhenjie/permission/source/FragmentSource.java

+ 8 - 34
.gitignore

@@ -1,34 +1,8 @@
-# ---> Android
-# Built application files
-*.apk
-*.ap_
-
-# Files for the Dalvik VM
-*.dex
-
-# Java class files
-*.class
-
-# Generated files
-bin/
-gen/
-
-# Gradle files
-.gradle/
-build/
-
-# Local configuration file (sdk path, etc)
-local.properties
-
-# Proguard folder generated by Eclipse
-proguard/
-
-# Log Files
-*.log
-
-# Android Studio Navigation editor temp files
-.navigation/
-
-# Android Studio captures folder
-captures/
-
+*.iml
+/.idea/
+/build/
+.gradle
+/local.properties
+.DS_Store
+/captures
+.externalNativeBuild

+ 10 - 0
CONTRIBUTING.md

@@ -0,0 +1,10 @@
+# Contributing to AndPermission
+First off, thanks for taking the time to contribute.  
+
+The following is a set of guidelines for contributing to AndPermission. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+1. All your actions in AndPermission should be in English, not in other languages.
+2. Please keep AndPermission the existing code style, not according to your habits.
+3. Just modify the code you are sure need to be optimized, not all the different code from your ideas.
+4. Before launching a pull request, you should test your commit code adequately.
+5. Please commit new code to the [dev](https://github.com/yanzhenjie/AndPermission/tree/dev) branch instead of the master branch.

+ 202 - 0
LICENSE

@@ -0,0 +1,202 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2019 Zhenjie Yan
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+

+ 55 - 2
README.md

@@ -1,3 +1,56 @@
-# AndPermission-airsmart
+# AndPermission
+1. Request for runtime permissions.  
+2. Share private files.  
+3. Request to install unknown source apk.  
+  `android.permission.REQUEST_INSTALL_PACKAGES`
+4. Request to draw at the top of other apps.  
+  `android.permission.SYSTEM_ALERT_WINDOW`
+5. Request to show notifications.  
+6. Request to access notifications.  
+  `android.permission.BIND_NOTIFICATION_LISTENER_SERVICE`
+7. Request to modify system setting.  
+  `android.permission.WRITE_SETTINGS`
 
-github 开源仓库clone下来,我们对它做过修改。
+```java
+AndPermission.with(this)
+  .runtime()
+  .permission(Permission.Group.STORAGE)
+  .onGranted(permissions -> {
+    // Storage permission are allowed.
+  })
+  .onDenied(permissions -> {
+    // Storage permission are not allowed.
+  })
+  .start();
+```
+
+For documentation and additional information see [the website](https://yanzhenjie.com/AndPermission).
+
+## Download
+It only supports androidx, add dependencies in your gradle:
+
+```groovy
+implementation 'com.yanzhenjie:permission:2.0.3'
+```
+
+AndPermission requires at minimum Android 4.0(Api level 14) .
+
+## Contributing
+Before submitting pull requests, contributors must abide by the [agreement](CONTRIBUTING.md) .
+
+## License
+```text
+Copyright 2019 Zhenjie Yan
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```

+ 36 - 0
build.gradle

@@ -0,0 +1,36 @@
+apply from: "config.gradle"
+
+
+
+buildscript {
+    repositories {
+        google()
+        jcenter {url 'https://maven.aliyun.com/repository/jcenter'}
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.1'
+        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
+        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter {url 'https://maven.aliyun.com/repository/jcenter'}
+        jcenter()
+        maven { url 'https://maven.aliyun.com/repository/public' }
+        maven {
+            credentials {
+                username 'icYZUR'
+                password 'Xgk5Pc7PcV'
+            }
+            url 'https://repo.rdc.aliyun.com/repository/84521-release-D7jNC2/'
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 41 - 0
config.gradle

@@ -0,0 +1,41 @@
+ext {
+    plugins = [application: 'com.android.application',
+               library    : 'com.android.library',
+               maven      : 'com.github.dcendents.android-maven',
+               bintray    : 'com.jfrog.bintray']
+
+    android = [applicationId    : "com.yanzhenjie.permission.sample",
+               compileSdkVersion: 29,
+               buildToolsVersion: "29.0.2",
+               minSdkVersion    : 14,
+               targetSdkVersion : 29,
+               versionCode      : 108,
+               versionName      : "2.0.3"]
+
+    bintray = [version       : "2.0.3",
+
+               siteUrl       : 'https://github.com/yanzhenjie/AndPermission',
+               gitUrl        : 'https://github.com/yanzhenjie/AndPermission.git',
+
+               group         : "com.yanzhenjie",
+
+               packaging     : 'aar',
+               name          : 'Permission',
+               description   : 'Permission manager for Android',
+
+               licenseName   : 'The Apache Software License, Version 2.0',
+               licenseUrl    : 'http://www.apache.org/licenses/LICENSE-2.0.txt',
+
+               developerId   : 'yanzhenjie',
+               developerName : 'yanzhenjie',
+               developerEmail: 'im.yanzhenjie@gmail.com',
+
+               binrayLibrary : "permission",
+               bintrayRepo   : "maven",
+               bintrayUser   : 'yolanda',
+               bintrayLicense: "Apache-2.0"]
+
+    dependencies = [
+        fragment: 'com.android.support:appcompat-v7:28.0.0'
+    ]
+}

BIN
gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

+ 160 - 0
gradlew

@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
permission/.gitignore

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

+ 36 - 0
permission/build.gradle

@@ -0,0 +1,36 @@
+apply plugin: rootProject.ext.plugins.library
+
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    buildToolsVersion rootProject.ext.android.buildToolsVersion
+
+    defaultConfig {
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+    }
+
+    resourcePrefix 'permission'
+}
+
+uploadArchives {
+    repositories{
+        mavenDeployer {
+            //url 'https://repo.rdc.aliyun.com/repository/84103-release-wrxD8V/'
+            repository(url:"https://repo.rdc.aliyun.com/repository/84521-release-D7jNC2/") {
+                authentication(userName:"icYZUR", password:"Xgk5Pc7PcV")
+            }
+            // 以com.android.support:appcompat-v7:25.1.0为对比
+            pom.version="1.0.2" // 对应版本号 25.1.0
+            pom.artifactId="permission" // 对应 appcompat-v7
+            pom.groupId="com.airsmart.lib" // com.android.support
+        }
+    }
+}
+
+dependencies {
+    compileOnly rootProject.ext.dependencies.fragment
+    //fragment: 'androidx.appcompat:appcompat:1.1.0'
+    //implementation "com.android.support:appcompat-v7:28.0.0"
+}

+ 56 - 0
permission/src/main/AndroidManifest.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    Copyright 2017 Zhenjie Yan
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.yanzhenjie.permission">
+
+    <permission
+        android:name="${applicationId}.andpermission.bridge"
+        android:permissionGroup="${applicationId}.andpermission"
+        android:protectionLevel="signature" />
+
+    <uses-permission android:name="${applicationId}.andpermission.bridge" />
+
+    <application>
+        <service
+            android:name=".bridge.BridgeService"
+            android:exported="false"
+            android:permission="${applicationId}.andpermission.bridge">
+            <intent-filter>
+                <action android:name="${applicationId}.andpermission.bridge" />
+            </intent-filter>
+        </service>
+
+        <activity
+            android:name=".bridge.BridgeActivity"
+            android:configChanges="orientation"
+            android:exported="false"
+            android:permission="${applicationId}.andpermission.bridge"
+            android:theme="@style/Permission.Theme.Activity.Transparent" />
+
+        <provider
+            android:name=".FileProvider"
+            android:authorities="${applicationId}.file.path.share"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/permission_file_paths" />
+        </provider>
+    </application>
+
+</manifest>

+ 43 - 0
permission/src/main/aidl/com/yanzhenjie/permission/bridge/IBridge.aidl

@@ -0,0 +1,43 @@
+package com.yanzhenjie.permission.bridge;
+
+interface IBridge {
+    /**
+     * Request for permissions.
+     */
+    void requestAppDetails(in String suffix);
+
+    /**
+     * Request for permissions.
+     */
+    void requestPermission(in String suffix, in String[] permissions);
+
+    /**
+     * Request for package install.
+     */
+    void requestInstall(in String suffix);
+
+   /**
+    * Request for overlay.
+    */
+    void requestOverlay(in String suffix);
+
+   /**
+    * Request for alert window.
+    */
+    void requestAlertWindow(in String suffix);
+
+   /**
+    * Request for notify.
+    */
+    void requestNotify(in String suffix);
+
+   /**
+    * Request for notification listener.
+    */
+    void requestNotificationListener(in String suffix);
+
+   /**
+    * Request for write system setting.
+    */
+    void requestWriteSetting(in String suffix);
+}

+ 29 - 0
permission/src/main/java/com/yanzhenjie/permission/Action.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/1.
+ */
+public interface Action<T> {
+
+    /**
+     * An action.
+     *
+     * @param data the data.
+     */
+    void onAction(T data);
+}

+ 350 - 0
permission/src/main/java/com/yanzhenjie/permission/AndPermission.java

@@ -0,0 +1,350 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.net.Uri;
+import android.os.Build;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+
+
+import com.yanzhenjie.permission.checker.DoubleChecker;
+import com.yanzhenjie.permission.checker.PermissionChecker;
+import com.yanzhenjie.permission.option.Option;
+import com.yanzhenjie.permission.source.ActivitySource;
+import com.yanzhenjie.permission.source.ContextSource;
+import com.yanzhenjie.permission.source.FragmentSource;
+import com.yanzhenjie.permission.source.Source;
+import com.yanzhenjie.permission.source.XFragmentSource;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2016/9/9.
+ */
+public class AndPermission {
+
+    private static final String ACTION_BRIDGE_SUFFIX = ".andpermission.bridge";
+
+    public static String bridgeAction(Context context, String suffix) {
+        return context.getPackageName() + ACTION_BRIDGE_SUFFIX + (TextUtils.isEmpty(suffix) ? "" : "." + suffix);
+    }
+
+    /**
+     * With context.
+     *
+     * @param context {@link Context}.
+     * @return {@link Option}.
+     */
+    public static Option with(Context context) {
+        return new Boot(getContextSource(context));
+    }
+
+    /**
+     * With {@link Fragment}.
+     *
+     * @param fragment {@link Fragment}.
+     * @return {@link Option}.
+     */
+    public static Option with(Fragment fragment) {
+        return new Boot(new XFragmentSource(fragment));
+    }
+
+    /**
+     * With {@link android.app.Fragment}.
+     *
+     * @param fragment {@link android.app.Fragment}.
+     * @return {@link Option}.
+     */
+    public static Option with(android.app.Fragment fragment) {
+        return new Boot(new FragmentSource(fragment));
+    }
+
+    /**
+     * With activity.
+     *
+     * @param activity {@link Activity}.
+     * @return {@link Option}.
+     */
+    public static Option with(Activity activity) {
+        return new Boot(new ActivitySource(activity));
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param context           {@link Context}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(Context context, List<String> deniedPermissions) {
+        return hasAlwaysDeniedPermission(getContextSource(context), deniedPermissions);
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param fragment          {@link Fragment}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(Fragment fragment, List<String> deniedPermissions) {
+        return hasAlwaysDeniedPermission(new XFragmentSource(fragment), deniedPermissions);
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param fragment          {@link android.app.Fragment}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(android.app.Fragment fragment, List<String> deniedPermissions) {
+        return hasAlwaysDeniedPermission(new FragmentSource(fragment), deniedPermissions);
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param activity          {@link Activity}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(Activity activity, List<String> deniedPermissions) {
+        return hasAlwaysDeniedPermission(new ActivitySource(activity), deniedPermissions);
+    }
+
+    /**
+     * Has always been denied permission.
+     */
+    private static boolean hasAlwaysDeniedPermission(Source source, List<String> deniedPermissions) {
+        for (String permission : deniedPermissions) {
+            if (!source.isShowRationalePermission(permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param context           {@link Context}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(Context context, String... deniedPermissions) {
+        return hasAlwaysDeniedPermission(getContextSource(context), deniedPermissions);
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param fragment          {@link Fragment}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(Fragment fragment, String... deniedPermissions) {
+        return hasAlwaysDeniedPermission(new XFragmentSource(fragment), deniedPermissions);
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param fragment          {@link android.app.Fragment}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(android.app.Fragment fragment, String... deniedPermissions) {
+        return hasAlwaysDeniedPermission(new FragmentSource(fragment), deniedPermissions);
+    }
+
+    /**
+     * Some privileges permanently disabled, may need to set up in the execute.
+     *
+     * @param activity          {@link Activity}.
+     * @param deniedPermissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasAlwaysDeniedPermission(Activity activity, String... deniedPermissions) {
+        return hasAlwaysDeniedPermission(new ActivitySource(activity), deniedPermissions);
+    }
+
+    /**
+     * Has always been denied permission.
+     */
+    private static boolean hasAlwaysDeniedPermission(Source source, String... deniedPermissions) {
+        for (String permission : deniedPermissions) {
+            if (!source.isShowRationalePermission(permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Classic permission checker.
+     */
+    private static final PermissionChecker PERMISSION_CHECKER = new DoubleChecker();
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param context     {@link Context}.
+     * @param permissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(Context context, String... permissions) {
+        return PERMISSION_CHECKER.hasPermission(context, permissions);
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param fragment    {@link Fragment}.
+     * @param permissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(Fragment fragment, String... permissions) {
+        return hasPermissions(fragment.getActivity(), permissions);
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param fragment    {@link android.app.Fragment}.
+     * @param permissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(android.app.Fragment fragment, String... permissions) {
+        return hasPermissions(fragment.getActivity(), permissions);
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param activity    {@link Activity}.
+     * @param permissions one or more permissions.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(Activity activity, String... permissions) {
+        return PERMISSION_CHECKER.hasPermission(activity, permissions);
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param context     {@link Context}.
+     * @param permissions one or more permission groups.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(Context context, String[]... permissions) {
+        for (String[] permission : permissions) {
+            boolean hasPermission = PERMISSION_CHECKER.hasPermission(context, permission);
+            if (!hasPermission) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param fragment    {@link Fragment}.
+     * @param permissions one or more permission groups.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(Fragment fragment, String[]... permissions) {
+        return hasPermissions(fragment.getActivity(), permissions);
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param fragment    {@link android.app.Fragment}.
+     * @param permissions one or more permission groups.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(android.app.Fragment fragment, String[]... permissions) {
+        return hasPermissions(fragment.getActivity(), permissions);
+    }
+
+    /**
+     * Judgment already has the target permission.
+     *
+     * @param activity    {@link Activity}.
+     * @param permissions one or more permission groups.
+     * @return true, other wise is false.
+     */
+    public static boolean hasPermissions(Activity activity, String[]... permissions) {
+        for (String[] permission : permissions) {
+            boolean hasPermission = PERMISSION_CHECKER.hasPermission(activity, permission);
+            if (!hasPermission) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get compatible Android 7.0 and lower versions of Uri.
+     *
+     * @param context {@link Context}.
+     * @param file    apk file.
+     * @return uri.
+     */
+    public static Uri getFileUri(Context context, File file) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            return FileProvider.getUriForFile(context, context.getPackageName() + ".file.path.share", file);
+        }
+        return Uri.fromFile(file);
+    }
+
+    /**
+     * Get compatible Android 7.0 and lower versions of Uri.
+     *
+     * @param fragment {@link Fragment}.
+     * @param file     apk file.
+     * @return uri.
+     */
+    public static Uri getFileUri(Fragment fragment, File file) {
+        return getFileUri(fragment.getContext(), file);
+    }
+
+    /**
+     * Get compatible Android 7.0 and lower versions of Uri.
+     *
+     * @param fragment {@link android.app.Fragment}.
+     * @param file     apk file.
+     * @return uri.
+     */
+    public static Uri getFileUri(android.app.Fragment fragment, File file) {
+        return getFileUri(fragment.getActivity(), file);
+    }
+
+    private static Source getContextSource(Context context) {
+        if (context instanceof Activity) {
+            return new ActivitySource((Activity) context);
+        } else if (context instanceof ContextWrapper) {
+            return getContextSource(((ContextWrapper) context).getBaseContext());
+        }
+        return new ContextSource(context);
+    }
+
+    private AndPermission() {
+    }
+}

+ 102 - 0
permission/src/main/java/com/yanzhenjie/permission/Boot.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission;
+
+import android.os.Build;
+
+import com.yanzhenjie.permission.install.InstallRequest;
+import com.yanzhenjie.permission.install.NRequestFactory;
+import com.yanzhenjie.permission.install.ORequestFactory;
+import com.yanzhenjie.permission.notify.Notify;
+import com.yanzhenjie.permission.notify.option.NotifyOption;
+import com.yanzhenjie.permission.option.Option;
+import com.yanzhenjie.permission.overlay.LRequestFactory;
+import com.yanzhenjie.permission.overlay.MRequestFactory;
+import com.yanzhenjie.permission.overlay.OverlayRequest;
+import com.yanzhenjie.permission.runtime.Runtime;
+import com.yanzhenjie.permission.runtime.option.RuntimeOption;
+import com.yanzhenjie.permission.setting.Setting;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public class Boot implements Option {
+
+    private static final InstallRequestFactory INSTALL_REQUEST_FACTORY;
+    private static final OverlayRequestFactory OVERLAY_REQUEST_FACTORY;
+
+    static {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            INSTALL_REQUEST_FACTORY = new ORequestFactory();
+        } else {
+            INSTALL_REQUEST_FACTORY = new NRequestFactory();
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            OVERLAY_REQUEST_FACTORY = new MRequestFactory();
+        } else {
+            OVERLAY_REQUEST_FACTORY = new LRequestFactory();
+        }
+    }
+
+    public interface InstallRequestFactory {
+
+        /**
+         * Create apk installer request.
+         */
+        InstallRequest create(Source source);
+    }
+
+    public interface OverlayRequestFactory {
+
+        /**
+         * Create overlay request.
+         */
+        OverlayRequest create(Source source);
+    }
+
+    private Source mSource;
+
+    public Boot(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public RuntimeOption runtime() {
+        return new Runtime(mSource);
+    }
+
+    @Override
+    public InstallRequest install() {
+        return INSTALL_REQUEST_FACTORY.create(mSource);
+    }
+
+    @Override
+    public OverlayRequest overlay() {
+        return OVERLAY_REQUEST_FACTORY.create(mSource);
+    }
+
+    @Override
+    public NotifyOption notification() {
+        return new Notify(mSource);
+    }
+
+    @Override
+    public Setting setting() {
+        return new Setting(mSource);
+    }
+}

+ 383 - 0
permission/src/main/java/com/yanzhenjie/permission/FileProvider.java

@@ -0,0 +1,383 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.OpenableColumns;
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+/**
+ * <p>Copied from the support library v27.1.1.</p>
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public class FileProvider extends ContentProvider {
+
+    private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
+
+    private static final String META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
+
+    private static final String TAG_ROOT_PATH = "root-path";
+    private static final String TAG_FILES_PATH = "files-path";
+    private static final String TAG_CACHE_PATH = "cache-path";
+    private static final String TAG_EXTERNAL = "external-path";
+    private static final String TAG_EXTERNAL_FILES = "external-files-path";
+    private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
+    private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
+
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_PATH = "path";
+
+    private static final File DEVICE_ROOT = new File("/");
+
+    private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
+
+    private PathStrategy mStrategy;
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        super.attachInfo(context, info);
+        if (info.exported) {
+            throw new SecurityException("Provider must not be exported");
+        }
+        if (!info.grantUriPermissions) {
+            throw new SecurityException("Provider must grant uri permissions");
+        }
+
+        mStrategy = getPathStrategy(context, info.authority);
+    }
+
+    public static Uri getUriForFile(Context context, String authority, File file) {
+        final PathStrategy strategy = getPathStrategy(context, authority);
+        return strategy.getUriForFile(file);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        final File file = mStrategy.getFileForUri(uri);
+        if (projection == null) {
+            projection = COLUMNS;
+        }
+
+        String[] cols = new String[projection.length];
+        Object[] values = new Object[projection.length];
+        int i = 0;
+        for (String col : projection) {
+            if (OpenableColumns.DISPLAY_NAME.equals(col)) {
+                cols[i] = OpenableColumns.DISPLAY_NAME;
+                values[i++] = file.getName();
+            } else if (OpenableColumns.SIZE.equals(col)) {
+                cols[i] = OpenableColumns.SIZE;
+                values[i++] = file.length();
+            }
+        }
+
+        cols = copyOf(cols, i);
+        values = copyOf(values, i);
+
+        final MatrixCursor cursor = new MatrixCursor(cols, 1);
+        cursor.addRow(values);
+        return cursor;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        final File file = mStrategy.getFileForUri(uri);
+
+        final int lastDot = file.getName().lastIndexOf('.');
+        if (lastDot >= 0) {
+            final String extension = file.getName().substring(lastDot + 1);
+            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+            if (mime != null) {
+                return mime;
+            }
+        }
+        return "application/octet-stream";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException("No external inserts");
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("No external updates");
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        final File file = mStrategy.getFileForUri(uri);
+        return file.delete() ? 1 : 0;
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        final File file = mStrategy.getFileForUri(uri);
+        final int fileMode = modeToMode(mode);
+        return ParcelFileDescriptor.open(file, fileMode);
+    }
+
+    private static PathStrategy getPathStrategy(Context context, String authority) {
+        PathStrategy strategy;
+        synchronized (sCache) {
+            strategy = sCache.get(authority);
+            if (strategy == null) {
+                try {
+                    strategy = parsePathStrategy(context, authority);
+                } catch (IOException e) {
+                    throw new IllegalArgumentException(
+                        "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+                } catch (XmlPullParserException e) {
+                    throw new IllegalArgumentException(
+                        "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+                }
+                sCache.put(authority, strategy);
+            }
+        }
+        return strategy;
+    }
+
+    private static PathStrategy parsePathStrategy(Context context, String authority)
+        throws IOException, XmlPullParserException {
+        final SimplePathStrategy strategy = new SimplePathStrategy(authority);
+        final ProviderInfo info = context.getPackageManager()
+            .resolveContentProvider(authority, PackageManager.GET_META_DATA);
+        final XmlResourceParser in = info.loadXmlMetaData(context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
+        if (in == null) {
+            throw new IllegalArgumentException("Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
+        }
+
+        int type;
+        while ((type = in.next()) != END_DOCUMENT) {
+            if (type == START_TAG) {
+                final String tag = in.getName();
+
+                final String name = in.getAttributeValue(null, ATTR_NAME);
+                String path = in.getAttributeValue(null, ATTR_PATH);
+
+                File target = null;
+                if (TAG_ROOT_PATH.equals(tag)) {
+                    target = DEVICE_ROOT;
+                } else if (TAG_FILES_PATH.equals(tag)) {
+                    target = context.getFilesDir();
+                } else if (TAG_CACHE_PATH.equals(tag)) {
+                    target = context.getCacheDir();
+                } else if (TAG_EXTERNAL.equals(tag)) {
+                    target = Environment.getExternalStorageDirectory();
+                } else if (TAG_EXTERNAL_FILES.equals(tag)) {
+                    File[] externalFilesDirs = getExternalFilesDirs(context, null);
+                    if (externalFilesDirs.length > 0) {
+                        target = externalFilesDirs[0];
+                    }
+                } else if (TAG_EXTERNAL_CACHE.equals(tag)) {
+                    File[] externalCacheDirs = getExternalCacheDirs(context);
+                    if (externalCacheDirs.length > 0) {
+                        target = externalCacheDirs[0];
+                    }
+                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && TAG_EXTERNAL_MEDIA.equals(tag)) {
+                    File[] externalMediaDirs = context.getExternalMediaDirs();
+                    if (externalMediaDirs.length > 0) {
+                        target = externalMediaDirs[0];
+                    }
+                }
+
+                if (target != null) {
+                    strategy.addRoot(name, buildPath(target, path));
+                }
+            }
+        }
+
+        return strategy;
+    }
+
+    interface PathStrategy {
+
+        Uri getUriForFile(File file);
+
+        File getFileForUri(Uri uri);
+    }
+
+    static class SimplePathStrategy implements PathStrategy {
+
+        private final String mAuthority;
+        private final HashMap<String, File> mRoots = new HashMap<String, File>();
+
+        SimplePathStrategy(String authority) {
+            mAuthority = authority;
+        }
+
+        void addRoot(String name, File root) {
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("Name must not be empty");
+            }
+
+            try {
+                root = root.getCanonicalFile();
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Failed to resolve canonical path for " + root, e);
+            }
+
+            mRoots.put(name, root);
+        }
+
+        @Override
+        public Uri getUriForFile(File file) {
+            String path;
+            try {
+                path = file.getCanonicalPath();
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+            }
+
+            Map.Entry<String, File> mostSpecific = null;
+            for (Map.Entry<String, File> root : mRoots.entrySet()) {
+                final String rootPath = root.getValue().getPath();
+                boolean invalidMost = mostSpecific == null ||
+                    rootPath.length() > mostSpecific.getValue().getPath().length();
+                if (path.startsWith(rootPath) && invalidMost) {
+                    mostSpecific = root;
+                }
+            }
+
+            if (mostSpecific == null) {
+                throw new IllegalArgumentException("Failed to find configured root that contains " + path);
+            }
+
+            final String rootPath = mostSpecific.getValue().getPath();
+            if (rootPath.endsWith("/")) {
+                path = path.substring(rootPath.length());
+            } else {
+                path = path.substring(rootPath.length() + 1);
+            }
+
+            path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
+            return new Uri.Builder().scheme("content").authority(mAuthority).encodedPath(path).build();
+        }
+
+        @Override
+        public File getFileForUri(Uri uri) {
+            String path = uri.getEncodedPath();
+
+            final int splitIndex = path.indexOf('/', 1);
+            final String tag = Uri.decode(path.substring(1, splitIndex));
+            path = Uri.decode(path.substring(splitIndex + 1));
+
+            final File root = mRoots.get(tag);
+            if (root == null) {
+                throw new IllegalArgumentException("Unable to find configured root for " + uri);
+            }
+
+            File file = new File(root, path);
+            try {
+                file = file.getCanonicalFile();
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+            }
+
+            if (!file.getPath().startsWith(root.getPath())) {
+                throw new SecurityException("Resolved path jumped beyond configured root");
+            }
+            return file;
+        }
+    }
+
+    private static int modeToMode(String mode) {
+        int modeBits;
+        if ("r".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+        } else if ("w".equals(mode) || "wt".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE |
+                ParcelFileDescriptor.MODE_TRUNCATE;
+        } else if ("wa".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE |
+                ParcelFileDescriptor.MODE_APPEND;
+        } else if ("rw".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE;
+        } else if ("rwt".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE |
+                ParcelFileDescriptor.MODE_TRUNCATE;
+        } else {
+            throw new IllegalArgumentException("Invalid mode: " + mode);
+        }
+        return modeBits;
+    }
+
+    private static File buildPath(File base, String... segments) {
+        File cur = base;
+        for (String segment : segments) {
+            if (segment != null) {
+                cur = new File(cur, segment);
+            }
+        }
+        return cur;
+    }
+
+    private static String[] copyOf(String[] original, int newLength) {
+        final String[] result = new String[newLength];
+        System.arraycopy(original, 0, result, 0, newLength);
+        return result;
+    }
+
+    private static Object[] copyOf(Object[] original, int newLength) {
+        final Object[] result = new Object[newLength];
+        System.arraycopy(original, 0, result, 0, newLength);
+        return result;
+    }
+
+    private static File[] getExternalFilesDirs(Context context, String type) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            return context.getExternalFilesDirs(type);
+        } else {
+            return new File[] {context.getExternalFilesDir(type)};
+        }
+    }
+
+    public static File[] getExternalCacheDirs(Context context) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            return context.getExternalCacheDirs();
+        } else {
+            return new File[] {context.getExternalCacheDir()};
+        }
+    }
+}

+ 33 - 0
permission/src/main/java/com/yanzhenjie/permission/Rationale.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission;
+
+import android.content.Context;
+
+/**
+ * Created by Zhenjie Yan on 2016/9/10.
+ */
+public interface Rationale<T> {
+
+    /**
+     * Show rationale to user.
+     *
+     * @param context context.
+     * @param data the data.
+     * @param executor executor.
+     */
+    void showRationale(Context context, T data, RequestExecutor executor);
+}

+ 34 - 0
permission/src/main/java/com/yanzhenjie/permission/RequestExecutor.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission;
+
+/**
+ * <p>Request executor.</p>
+ * Created by Zhenjie Yan on 2016/9/10.
+ */
+public interface RequestExecutor {
+
+    /**
+     * Go request permission.
+     */
+    void execute();
+
+    /**
+     * Cancel the operation.
+     */
+    void cancel();
+
+}

+ 211 - 0
permission/src/main/java/com/yanzhenjie/permission/bridge/BridgeActivity.java

@@ -0,0 +1,211 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.bridge;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.KeyEvent;
+
+
+import com.yanzhenjie.permission.overlay.setting.LSettingPage;
+import com.yanzhenjie.permission.overlay.setting.MSettingPage;
+import com.yanzhenjie.permission.source.ActivitySource;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * <p>
+ * Request permission.
+ * </p>
+ * Created by Zhenjie Yan on 2017/4/27.
+ */
+public final class BridgeActivity extends Activity {
+
+    private static final String KEY_TYPE = "KEY_TYPE";
+    private static final String KEY_PERMISSIONS = "KEY_PERMISSIONS";
+    private static final String KEY_ACTION_SUFFIX = "KEY_ACTION_SUFFIX";
+
+    /**
+     * Request for permissions.
+     */
+    static void requestAppDetails(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_APP_DETAILS);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for permissions.
+     */
+    static void requestPermission(Source source, String suffix, String[] permissions) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_PERMISSION);
+        intent.putExtra(KEY_PERMISSIONS, permissions);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for package install.
+     */
+    static void requestInstall(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_INSTALL);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for overlay.
+     */
+    static void requestOverlay(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_OVERLAY);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for alert window.
+     */
+    static void requestAlertWindow(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_ALERT_WINDOW);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for notify.
+     */
+    static void requestNotify(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_NOTIFY);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for notification listener.
+     */
+    static void requestNotificationListener(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_NOTIFY_LISTENER);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    /**
+     * Request for write system setting.
+     */
+    static void requestWriteSetting(Source source, String suffix) {
+        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
+        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_WRITE_SETTING);
+        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
+        source.startActivity(intent);
+    }
+
+    private String mActionSuffix;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) return;
+
+        Intent intent = getIntent();
+        int operation = intent.getIntExtra(KEY_TYPE, -1);
+        mActionSuffix = intent.getStringExtra(KEY_ACTION_SUFFIX);
+        switch (operation) {
+            case BridgeRequest.TYPE_APP_DETAILS: {
+                Intent appDetailsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                appDetailsIntent.setData(Uri.fromParts("package", getPackageName(), null));
+                startActivityForResult(appDetailsIntent, BridgeRequest.TYPE_APP_DETAILS);
+                break;
+            }
+            case BridgeRequest.TYPE_PERMISSION: {
+                String[] permissions = intent.getStringArrayExtra(KEY_PERMISSIONS);
+                requestPermissions(permissions, BridgeRequest.TYPE_PERMISSION);
+                break;
+            }
+            case BridgeRequest.TYPE_INSTALL: {
+                Intent manageIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
+                manageIntent.setData(Uri.fromParts("package", getPackageName(), null));
+                startActivityForResult(manageIntent, BridgeRequest.TYPE_INSTALL);
+                break;
+            }
+            case BridgeRequest.TYPE_OVERLAY: {
+                MSettingPage settingPage = new MSettingPage(new ActivitySource(this));
+                settingPage.start(BridgeRequest.TYPE_OVERLAY);
+                break;
+            }
+            case BridgeRequest.TYPE_ALERT_WINDOW: {
+                LSettingPage settingPage = new LSettingPage(new ActivitySource(this));
+                settingPage.start(BridgeRequest.TYPE_ALERT_WINDOW);
+                break;
+            }
+            case BridgeRequest.TYPE_NOTIFY: {
+                Intent settingIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+                settingIntent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
+                settingIntent.setData(Uri.fromParts("package", getPackageName(), null));
+                startActivityForResult(settingIntent, BridgeRequest.TYPE_NOTIFY);
+                break;
+            }
+            case BridgeRequest.TYPE_NOTIFY_LISTENER: {
+                Intent settingIntent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
+                settingIntent.setData(Uri.fromParts("package", getPackageName(), null));
+                startActivityForResult(settingIntent, BridgeRequest.TYPE_NOTIFY_LISTENER);
+                break;
+            }
+            case BridgeRequest.TYPE_WRITE_SETTING: {
+                Intent settingIntent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
+                settingIntent.setData(Uri.fromParts("package", getPackageName(), null));
+                startActivityForResult(settingIntent, BridgeRequest.TYPE_WRITE_SETTING);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        Messenger.send(this, mActionSuffix);
+        finish();
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        super.startActivityForResult(intent, requestCode);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        Messenger.send(this, mActionSuffix);
+        finish();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+}

+ 78 - 0
permission/src/main/java/com/yanzhenjie/permission/bridge/BridgeRequest.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.bridge;
+
+import com.yanzhenjie.permission.source.Source;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2/13/19.
+ */
+public final class BridgeRequest {
+
+    public static final int TYPE_APP_DETAILS = 1;
+    public static final int TYPE_PERMISSION = 2;
+    public static final int TYPE_INSTALL = 3;
+    public static final int TYPE_OVERLAY = 4;
+    public static final int TYPE_ALERT_WINDOW = 5;
+    public static final int TYPE_NOTIFY = 6;
+    public static final int TYPE_NOTIFY_LISTENER = 7;
+    public static final int TYPE_WRITE_SETTING = 8;
+
+    private final Source mSource;
+
+    private int mType;
+    private Callback mCallback;
+    private List<String> mPermissions;
+
+    public BridgeRequest(Source source) {
+        this.mSource = source;
+    }
+
+    public Source getSource() {
+        return mSource;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public void setType(int type) {
+        mType = type;
+    }
+
+    public Callback getCallback() {
+        return mCallback;
+    }
+
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    public List<String> getPermissions() {
+        return mPermissions;
+    }
+
+    public void setPermissions(List<String> permissions) {
+        mPermissions = permissions;
+    }
+
+    public interface Callback {
+
+        void onCallback();
+    }
+}

+ 83 - 0
permission/src/main/java/com/yanzhenjie/permission/bridge/BridgeService.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.bridge;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+
+
+import com.yanzhenjie.permission.source.ContextSource;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2019-08-30.
+ */
+public class BridgeService extends Service {
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mStub.asBinder();
+    }
+
+    private IBridge.Stub mStub = new IBridge.Stub() {
+
+        private Source mSource = new ContextSource(BridgeService.this);
+
+        @Override
+        public void requestAppDetails(String suffix) throws RemoteException {
+            BridgeActivity.requestAppDetails(mSource, suffix);
+        }
+
+        @Override
+        public void requestPermission(String suffix, String[] permissions) throws RemoteException {
+            BridgeActivity.requestPermission(mSource, suffix, permissions);
+        }
+
+        @Override
+        public void requestInstall(String suffix) throws RemoteException {
+            BridgeActivity.requestInstall(mSource, suffix);
+        }
+
+        @Override
+        public void requestOverlay(String suffix) throws RemoteException {
+            BridgeActivity.requestOverlay(mSource, suffix);
+        }
+
+        @Override
+        public void requestAlertWindow(String suffix) throws RemoteException {
+            BridgeActivity.requestAlertWindow(mSource, suffix);
+        }
+
+        @Override
+        public void requestNotify(String suffix) throws RemoteException {
+            BridgeActivity.requestNotify(mSource, suffix);
+        }
+
+        @Override
+        public void requestNotificationListener(String suffix) throws RemoteException {
+            BridgeActivity.requestNotificationListener(mSource, suffix);
+        }
+
+        @Override
+        public void requestWriteSetting(String suffix) throws RemoteException {
+            BridgeActivity.requestWriteSetting(mSource, suffix);
+        }
+    };
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/bridge/Messenger.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.bridge;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.yanzhenjie.permission.AndPermission;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/9.
+ */
+class Messenger extends BroadcastReceiver {
+
+    public static void send(Context context, String suffix) {
+        Intent broadcast = new Intent(AndPermission.bridgeAction(context, suffix));
+        context.sendBroadcast(broadcast);
+    }
+
+    private final Context mContext;
+    private final Callback mCallback;
+
+    public Messenger(Context context, Callback callback) {
+        this.mContext = context;
+        this.mCallback = callback;
+    }
+
+    public void register(String suffix) {
+        IntentFilter filter = new IntentFilter(AndPermission.bridgeAction(mContext, suffix));
+        mContext.registerReceiver(this, filter);
+    }
+
+    public void unRegister() {
+        try {
+            mContext.unregisterReceiver(this);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mCallback.onCallback();
+    }
+
+    public interface Callback {
+
+        void onCallback();
+    }
+}

+ 121 - 0
permission/src/main/java/com/yanzhenjie/permission/bridge/RequestExecutor.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.bridge;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.yanzhenjie.permission.AndPermission;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2/13/19.
+ */
+final class RequestExecutor extends Thread implements Messenger.Callback {
+
+    private BridgeRequest mRequest;
+    private Messenger mMessenger;
+
+    public RequestExecutor(BridgeRequest request) {
+        this.mRequest = request;
+    }
+
+    @Override
+    public void run() {
+        Context context = mRequest.getSource().getContext();
+
+        mMessenger = new Messenger(context, this);
+        mMessenger.register(getName());
+
+        Intent intent = new Intent();
+        intent.setAction(AndPermission.bridgeAction(context, null));
+        intent.setPackage(context.getPackageName());
+        context.bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+            IBridge iBridge = IBridge.Stub.asInterface(iBinder);
+            try {
+                executeCurrent(iBridge);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+        }
+    };
+
+    private void executeCurrent(IBridge iBridge) throws RemoteException {
+        switch (mRequest.getType()) {
+            case BridgeRequest.TYPE_APP_DETAILS: {
+                iBridge.requestAppDetails(getName());
+                break;
+            }
+            case BridgeRequest.TYPE_PERMISSION: {
+                List<String> permissions = mRequest.getPermissions();
+                String[] array = permissions.toArray(new String[0]);
+                iBridge.requestPermission(getName(), array);
+                break;
+            }
+            case BridgeRequest.TYPE_INSTALL: {
+                iBridge.requestInstall(getName());
+                break;
+            }
+            case BridgeRequest.TYPE_OVERLAY: {
+                iBridge.requestOverlay(getName());
+                break;
+            }
+            case BridgeRequest.TYPE_ALERT_WINDOW: {
+                iBridge.requestAlertWindow(getName());
+                break;
+            }
+            case BridgeRequest.TYPE_NOTIFY: {
+                iBridge.requestNotify(getName());
+                break;
+            }
+            case BridgeRequest.TYPE_NOTIFY_LISTENER: {
+                iBridge.requestNotificationListener(getName());
+                break;
+            }
+            case BridgeRequest.TYPE_WRITE_SETTING: {
+                iBridge.requestWriteSetting(getName());
+                break;
+            }
+        }
+    }
+
+
+    @Override
+    public void onCallback() {
+        synchronized (this) {
+            mMessenger.unRegister();
+            mRequest.getCallback().onCallback();
+            mRequest.getSource().getContext().unbindService(mConnection);
+            mMessenger = null;
+            mRequest = null;
+        }
+    }
+}

+ 48 - 0
permission/src/main/java/com/yanzhenjie/permission/bridge/RequestManager.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.bridge;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Created by Zhenjie Yan on 2/13/19.
+ */
+public class RequestManager {
+
+    private static RequestManager sManager;
+
+    public static RequestManager get() {
+        if (sManager == null) {
+            synchronized (RequestManager.class) {
+                if (sManager == null) {
+                    sManager = new RequestManager();
+                }
+            }
+        }
+        return sManager;
+    }
+
+    private final Executor mExecutor;
+
+    private RequestManager() {
+        this.mExecutor = Executors.newCachedThreadPool();
+    }
+
+    public void add(BridgeRequest request) {
+        mExecutor.execute(new RequestExecutor(request));
+    }
+}

+ 49 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/CalendarReadTest.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.CalendarContract;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class CalendarReadTest implements PermissionTest {
+
+    private ContentResolver mResolver;
+
+    CalendarReadTest(Context context) {
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        String[] projection = new String[] {CalendarContract.Calendars._ID, CalendarContract.Calendars.NAME};
+        Cursor cursor = mResolver.query(CalendarContract.Calendars.CONTENT_URI, projection, null, null, null);
+        if (cursor != null) {
+            try {
+                CursorTest.read(cursor);
+            } finally {
+                cursor.close();
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 73 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/CalendarWriteTest.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.graphics.Color;
+import android.net.Uri;
+import android.provider.CalendarContract;
+
+import com.yanzhenjie.permission.util.StringUtils;
+
+import java.util.TimeZone;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/15.
+ */
+class CalendarWriteTest implements PermissionTest {
+
+    private static final String NAME = StringUtils.hexToText("5045524D495353494F4E");
+    private static final String ACCOUNT = StringUtils.hexToText("7065726D697373696F6E40676D61696C2E636F6D");
+
+    private ContentResolver mResolver;
+
+    CalendarWriteTest(Context context) {
+        this.mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        try {
+            TimeZone timeZone = TimeZone.getDefault();
+            ContentValues value = new ContentValues();
+            value.put(CalendarContract.Calendars.NAME, NAME);
+            value.put(CalendarContract.Calendars.ACCOUNT_NAME, ACCOUNT);
+            value.put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL);
+            value.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, NAME);
+            value.put(CalendarContract.Calendars.VISIBLE, 1);
+            value.put(CalendarContract.Calendars.CALENDAR_COLOR, Color.BLUE);
+            value.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER);
+            value.put(CalendarContract.Calendars.SYNC_EVENTS, 1);
+            value.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, timeZone.getID());
+            value.put(CalendarContract.Calendars.OWNER_ACCOUNT, NAME);
+            value.put(CalendarContract.Calendars.CAN_ORGANIZER_RESPOND, 0);
+
+            Uri insertUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
+                .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
+                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, NAME)
+                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
+                .build();
+            Uri resourceUri = mResolver.insert(insertUri, value);
+            return ContentUris.parseId(resourceUri) > 0;
+        } finally {
+            Uri deleteUri = CalendarContract.Calendars.CONTENT_URI.buildUpon().build();
+            mResolver.delete(deleteUri, CalendarContract.Calendars.ACCOUNT_NAME + "=?", new String[]{ACCOUNT});
+        }
+    }
+}

+ 49 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/CallLogReadTest.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.CallLog;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class CallLogReadTest implements PermissionTest {
+
+    private ContentResolver mResolver;
+
+    CallLogReadTest(Context context) {
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        String[] projection = new String[] {CallLog.Calls._ID, CallLog.Calls.NUMBER, CallLog.Calls.TYPE};
+        Cursor cursor = mResolver.query(CallLog.Calls.CONTENT_URI, projection, null, null, null);
+        if (cursor != null) {
+            try {
+                CursorTest.read(cursor);
+            } finally {
+                cursor.close();
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 50 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/CallLogWriteTest.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.CallLog;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/14.
+ */
+class CallLogWriteTest implements PermissionTest {
+
+    private ContentResolver mResolver;
+
+    CallLogWriteTest(Context context) {
+        this.mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        try {
+            ContentValues content = new ContentValues();
+            content.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
+            content.put(CallLog.Calls.NUMBER, "1");
+            content.put(CallLog.Calls.DATE, 20080808);
+            content.put(CallLog.Calls.NEW, "0");
+            Uri resourceUri = mResolver.insert(CallLog.Calls.CONTENT_URI, content);
+            return ContentUris.parseId(resourceUri) > 0;
+        } finally {
+            mResolver.delete(CallLog.Calls.CONTENT_URI, CallLog.Calls.NUMBER + "=?", new String[] {"1"});
+        }
+    }
+}

+ 85 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/CameraTest.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Camera;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/15.
+ */
+class CameraTest implements PermissionTest {
+
+    private Context mContext;
+
+    CameraTest(Context context) {
+        this.mContext = context;
+    }
+
+    /*@Override
+    public boolean test() throws Throwable {
+        Camera camera = null;
+        try {
+            int cameraCount = Camera.getNumberOfCameras();
+            if (cameraCount <= 0) return true;
+
+            camera = Camera.open(cameraCount - 1);
+            Camera.Parameters parameters = camera.getParameters();
+            camera.setParameters(parameters);
+            camera.setPreviewCallback(PREVIEW_CALLBACK);
+            camera.startPreview();
+            return true;
+        } catch (Throwable e) {
+            PackageManager packageManager = mContext.getPackageManager();
+            return !packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+        } finally {
+            if (camera != null) {
+                camera.stopPreview();
+                camera.setPreviewCallback(null);
+                camera.release();
+            }
+        }
+    }*/
+
+    @Override
+    public boolean test() throws Throwable {
+        Camera camera = null;
+        try {
+            camera = Camera.open();
+            Camera.Parameters parameters = camera.getParameters();
+            camera.setParameters(parameters);
+            camera.setPreviewCallback(PREVIEW_CALLBACK);
+            camera.startPreview();
+            return true;
+        } catch (Throwable e) {
+            PackageManager packageManager = mContext.getPackageManager();
+            return !packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+        } finally {
+            if (camera != null) {
+                camera.stopPreview();
+                camera.setPreviewCallback(null);
+                camera.release();
+            }
+        }
+    }
+
+    private static final Camera.PreviewCallback PREVIEW_CALLBACK = new Camera.PreviewCallback() {
+        @Override
+        public void onPreviewFrame(byte[] data, Camera camera) {
+        }
+    };
+}

+ 50 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/ContactsReadTest.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class ContactsReadTest implements PermissionTest {
+
+    private ContentResolver mResolver;
+
+    ContactsReadTest(Context context) {
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        String[] projection = new String[] {ContactsContract.Data._ID, ContactsContract.Data.DATA1};
+        Cursor cursor = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null,
+            null);
+        if (cursor != null) {
+            try {
+                CursorTest.read(cursor);
+            } finally {
+                cursor.close();
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 98 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/ContactsWriteTest.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.annotation.Nullable;
+
+
+/**
+ * Created by Zhenjie Yan on 2018/1/14.
+ */
+class ContactsWriteTest implements PermissionTest {
+
+    private static final String DISPLAY_NAME = "PERMISSION";
+
+    private ContentResolver mResolver;
+
+    ContactsWriteTest(ContentResolver resolver) {
+        this.mResolver = resolver;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        long[] idArray = insert();
+        long rawContactId = idArray[0];
+        long dataId = idArray[1];
+        if (rawContactId > 0 && dataId > 0) {
+            return delete(rawContactId, dataId);
+        }
+        return false;
+    }
+
+    private long[] insert() {
+        ContentValues values = new ContentValues();
+        Uri rawContractUri = mResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
+        long rawContactId = ContentUris.parseId(rawContractUri);
+
+        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
+        values.put(ContactsContract.Data.DATA1, DISPLAY_NAME);
+        values.put(ContactsContract.Data.DATA2, DISPLAY_NAME);
+        values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
+        Uri dataUri = mResolver.insert(ContactsContract.Data.CONTENT_URI, values);
+        long dataId = ContentUris.parseId(dataUri);
+        return new long[]{rawContactId, dataId};
+    }
+
+    private boolean delete(long rawContactId, long dataId) {
+        int dataCount = mResolver.delete(ContactsContract.Data.CONTENT_URI, ContactsContract.Data._ID + "=?",
+            new String[]{Long.toString(dataId)});
+        int rawContactCount = mResolver.delete(ContactsContract.RawContacts.CONTENT_URI, ContactsContract.RawContacts._ID + "=?",
+            new String[]{Long.toString(rawContactId)});
+        return rawContactCount > 0 && dataCount > 0;
+    }
+
+    private boolean update(long rawContactId) {
+        ContentValues values = new ContentValues();
+        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
+        values.put(ContactsContract.Data.DATA1, DISPLAY_NAME);
+        values.put(ContactsContract.Data.DATA2, DISPLAY_NAME);
+        values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
+        Uri dataUri = mResolver.insert(ContactsContract.Data.CONTENT_URI, values);
+        return ContentUris.parseId(dataUri) > 0;
+    }
+
+    @Nullable
+    private long[] query() {
+        Cursor cursor = mResolver.query(ContactsContract.Data.CONTENT_URI,
+            new String[]{ContactsContract.Data.RAW_CONTACT_ID, ContactsContract.Data._ID},
+            ContactsContract.Data.MIMETYPE + "=? and " + ContactsContract.Data.DATA1 + "=?",
+            new String[]{ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, DISPLAY_NAME}, null);
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                long rawContactId = cursor.getLong(cursor.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID));
+                long dataId = cursor.getLong(cursor.getColumnIndex(ContactsContract.Data._ID));
+                return new long[]{rawContactId, dataId};
+            }
+        }
+        return null;
+    }
+}

+ 41 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/DoubleChecker.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/27.
+ */
+public final class DoubleChecker implements PermissionChecker {
+
+    private static final PermissionChecker STANDARD_CHECKER = new StandardChecker();
+    private static final PermissionChecker STRICT_CHECKER = new StrictChecker();
+
+    @Override
+    public boolean hasPermission(Context context, String... permissions) {
+        return STRICT_CHECKER.hasPermission(context, permissions) &&
+            STANDARD_CHECKER.hasPermission(context, permissions);
+    }
+
+    @Override
+    public boolean hasPermission(Context context, List<String> permissions) {
+        return STRICT_CHECKER.hasPermission(context, permissions) &&
+            STANDARD_CHECKER.hasPermission(context, permissions);
+    }
+}

+ 50 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/LocationCoarseTest.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/14.
+ */
+class LocationCoarseTest implements PermissionTest {
+
+    private Context mContext;
+
+    LocationCoarseTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        LocationManager locationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
+        List<String> providers = locationManager.getProviders(true);
+        boolean networkProvider = providers.contains(LocationManager.NETWORK_PROVIDER);
+        if (networkProvider) {
+            return true;
+        }
+
+        PackageManager packageManager = mContext.getPackageManager();
+        boolean networkHardware = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_NETWORK);
+        if (!networkHardware) return true;
+
+        return !locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
+    }
+}

+ 51 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/LocationFineTest.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/14.
+ */
+class LocationFineTest implements PermissionTest {
+
+    private Context mContext;
+
+    LocationFineTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        LocationManager locationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
+        List<String> providers = locationManager.getProviders(true);
+        boolean gpsProvider = providers.contains(LocationManager.GPS_PROVIDER);
+        boolean passiveProvider = providers.contains(LocationManager.PASSIVE_PROVIDER);
+        if (gpsProvider || passiveProvider) {
+            return true;
+        }
+
+        PackageManager packageManager = mContext.getPackageManager();
+        boolean gpsHardware = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
+        if (!gpsHardware) return true;
+
+        return !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+    }
+}

+ 47 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/PermissionChecker.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/7.
+ */
+public interface PermissionChecker {
+
+    /**
+     * Check if the calling context has a set of permissions.
+     *
+     * @param context {@link Context}.
+     * @param permissions one or more permissions.
+     *
+     * @return true, other wise is false.
+     */
+    boolean hasPermission(Context context, String... permissions);
+
+    /**
+     * Check if the calling context has a set of permissions.
+     *
+     * @param context {@link Context}.
+     * @param permissions one or more permissions.
+     *
+     * @return true, other wise is false.
+     */
+    boolean hasPermission(Context context, List<String> permissions);
+
+}

+ 50 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/PermissionTest.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.database.Cursor;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/14.
+ */
+interface PermissionTest {
+
+    boolean test() throws Throwable;
+
+    class CursorTest {
+
+        public static void read(Cursor cursor) {
+            int count = cursor.getCount();
+            if (count > 0) {
+                cursor.moveToFirst();
+                int type = cursor.getType(0);
+                switch (type) {
+                    case Cursor.FIELD_TYPE_BLOB:
+                    case Cursor.FIELD_TYPE_NULL: {
+                        break;
+                    }
+                    case Cursor.FIELD_TYPE_INTEGER:
+                    case Cursor.FIELD_TYPE_FLOAT:
+                    case Cursor.FIELD_TYPE_STRING:
+                    default: {
+                        cursor.getString(0);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}

+ 44 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/PhoneStateReadTest.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class PhoneStateReadTest implements PermissionTest {
+
+    private Context mContext;
+
+    PhoneStateReadTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return true;
+
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) telephonyManager.getDeviceId();
+        else telephonyManager.getDeviceSoftwareVersion();
+        return true;
+    }
+}

+ 110 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/RecordAudioTest.java

@@ -0,0 +1,110 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/14.
+ */
+class RecordAudioTest implements PermissionTest {
+
+    private static final int[] RATES = new int[]{8000, 11025, 22050, 44100};
+
+    private Context mContext;
+
+    RecordAudioTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        AudioRecord audioRecord = null;
+        File file = null;
+        FileOutputStream fos = null;
+        try {
+            int[] params = findAudioParameters();
+            if (params == null) return !existMicrophone(mContext);
+
+            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, params[0], params[1], params[2], params[3]);
+            int state = audioRecord.getState();
+            if (state != AudioRecord.STATE_INITIALIZED) return !existMicrophone(mContext);
+
+            int recordState = audioRecord.getRecordingState();
+            if (recordState != AudioRecord.RECORDSTATE_STOPPED) return true;
+
+            audioRecord.startRecording();
+            if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) return true;
+
+            File cacheDir = new File(mContext.getCacheDir(), "_andpermission_audio_record_test_");
+            cacheDir.mkdirs();
+
+            file = new File(cacheDir, Long.toString(System.currentTimeMillis()));
+            if (file.exists()) file.createNewFile();
+
+            fos = new FileOutputStream(file);
+            byte[] buffer = new byte[params[3]];
+
+            int len = audioRecord.read(buffer, 0, params[3]);
+            fos.write(buffer, 0, len);
+            fos.flush();
+        } catch (Throwable e) {
+            return !existMicrophone(mContext);
+        } finally {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException ignored) {
+                }
+            }
+            if (file != null && file.exists()) {
+                file.delete();
+            }
+            if (audioRecord != null) {
+                audioRecord.release();
+            }
+        }
+        return true;
+    }
+
+    public static boolean existMicrophone(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
+    }
+
+    public static int[] findAudioParameters() {
+        for (int rate : RATES) {
+            for (int channel : new int[]{AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO}) {
+                for (int format : new int[]{AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT}) {
+                    int buffer = AudioRecord.getMinBufferSize(rate, channel, format);
+                    if (buffer != AudioRecord.ERROR_BAD_VALUE) {
+                        return new int[]{rate, channel, format, buffer};
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+}

+ 59 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/SensorActivityTest.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class SensorActivityTest implements PermissionTest {
+
+    private Context mContext;
+
+    SensorActivityTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        SensorManager sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+        try {
+            Sensor heartRateSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
+            sensorManager.registerListener(SENSOR_EVENT_LISTENER, heartRateSensor, 3);
+            sensorManager.unregisterListener(SENSOR_EVENT_LISTENER, heartRateSensor);
+        } catch (Throwable e) {
+            PackageManager packageManager = mContext.getPackageManager();
+            return !packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR);
+        }
+        return true;
+    }
+
+    private static final SensorEventListener SENSOR_EVENT_LISTENER = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    };
+}

+ 59 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/SensorHeartTest.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class SensorHeartTest implements PermissionTest {
+
+    private Context mContext;
+
+    SensorHeartTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        SensorManager sensorManager = (SensorManager)mContext.getSystemService(Context.SENSOR_SERVICE);
+        try {
+            Sensor heartRateSensor = sensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
+            sensorManager.registerListener(SENSOR_EVENT_LISTENER, heartRateSensor, 3);
+            sensorManager.unregisterListener(SENSOR_EVENT_LISTENER, heartRateSensor);
+        } catch (Throwable e) {
+            PackageManager packageManager = mContext.getPackageManager();
+            return !packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE);
+        }
+        return true;
+    }
+
+    private static final SensorEventListener SENSOR_EVENT_LISTENER = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    };
+}

+ 55 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/SipTest.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.Context;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+
+import com.yanzhenjie.permission.util.StringUtils;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class SipTest implements PermissionTest {
+
+    private static final String USER = StringUtils.hexToText("5065726D697373696F6E");
+    private static final String IP = StringUtils.hexToText("3132372E302E302E31");
+    private static final String PASSWORD = StringUtils.textToHex("70617373776F7264");
+
+    private Context mContext;
+
+    SipTest(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        if (!SipManager.isApiSupported(mContext)) {
+            return true;
+        }
+        SipManager manager = SipManager.newInstance(mContext);
+        if (manager == null) {
+            return true;
+        }
+        SipProfile.Builder builder = new SipProfile.Builder(USER, IP);
+        builder.setPassword(PASSWORD);
+        SipProfile profile = builder.build();
+        manager.open(profile);
+        manager.close(profile.getUriString());
+        return true;
+    }
+}

+ 50 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/SmsReadTest.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.Telephony;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class SmsReadTest implements PermissionTest {
+
+    private ContentResolver mResolver;
+
+    SmsReadTest(Context context) {
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        String[] projection = new String[] {Telephony.Sms._ID, Telephony.Sms.ADDRESS, Telephony.Sms.PERSON,
+            Telephony.Sms.BODY};
+        Cursor cursor = mResolver.query(Telephony.Sms.CONTENT_URI, projection, null, null, null);
+        if (cursor != null) {
+            try {
+                CursorTest.read(cursor);
+            } finally {
+                cursor.close();
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 67 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/StandardChecker.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/7.
+ */
+public final class StandardChecker implements PermissionChecker {
+
+    private static final int MODE_ASK = 4;
+    private static final int MODE_COMPAT = 5;
+
+    public StandardChecker() {
+    }
+
+    @Override
+    public boolean hasPermission(Context context, String... permissions) {
+        return hasPermission(context, Arrays.asList(permissions));
+    }
+
+    @Override
+    public boolean hasPermission(Context context, List<String> permissions) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
+
+        AppOpsManager opsManager = null;
+        for (String permission : permissions) {
+            int result = context.checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid());
+            if (result == PackageManager.PERMISSION_DENIED) {
+                return false;
+            }
+
+            String op = AppOpsManager.permissionToOp(permission);
+            if (TextUtils.isEmpty(op)) {
+                continue;
+            }
+
+            if (opsManager == null) opsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+            result = opsManager.checkOpNoThrow(op, android.os.Process.myUid(), context.getPackageName());
+            if (result != AppOpsManager.MODE_ALLOWED && result != MODE_ASK && result != MODE_COMPAT) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 45 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/StorageReadTest.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import java.io.File;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/16.
+ */
+class StorageReadTest implements PermissionTest {
+
+    StorageReadTest() {
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isExternalStorageLegacy()) return true;
+
+        if (!TextUtils.equals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState())) return true;
+
+        File directory = Environment.getExternalStorageDirectory();
+        if (!directory.exists()) return true;
+
+        long modified = directory.lastModified();
+        String[] pathList = directory.list();
+        return modified > 0 && pathList != null;
+    }
+}

+ 55 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/StorageWriteTest.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import java.io.File;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/16.
+ */
+class StorageWriteTest implements PermissionTest {
+
+    StorageWriteTest() {
+    }
+
+    @Override
+    public boolean test() throws Throwable {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isExternalStorageLegacy()) return true;
+
+        if (!TextUtils.equals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState())) return true;
+
+        File directory = Environment.getExternalStorageDirectory();
+        if (!directory.exists()) return true;
+
+        File parent = new File(directory, "Android");
+        if (parent.exists() && parent.isFile()) {
+            if (!parent.delete()) return false;
+        }
+        if (!parent.exists()) {
+            if (!parent.mkdirs()) return false;
+        }
+        File file = new File(parent, "ANDROID.PERMISSION.TEST");
+        if (file.exists()) {
+            return file.delete();
+        } else {
+            return file.createNewFile();
+        }
+    }
+}

+ 201 - 0
permission/src/main/java/com/yanzhenjie/permission/checker/StrictChecker.java

@@ -0,0 +1,201 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.checker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Build;
+
+import com.yanzhenjie.permission.runtime.Permission;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/7.
+ */
+public final class StrictChecker implements PermissionChecker {
+
+    public StrictChecker() {
+    }
+
+    @Override
+    public boolean hasPermission(Context context, String... permissions) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return true;
+
+        for (String permission : permissions) {
+            if (!hasPermission(context, permission)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean hasPermission(Context context, List<String> permissions) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return true;
+
+        for (String permission : permissions) {
+            if (!hasPermission(context, permission)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean hasPermission(Context context, String permission) {
+        try {
+            switch (permission) {
+                case Permission.READ_CALENDAR:
+                    return checkReadCalendar(context);
+                case Permission.WRITE_CALENDAR:
+                    return checkWriteCalendar(context);
+                case Permission.CAMERA:
+                    return checkCamera(context);
+                case Permission.READ_CONTACTS:
+                    return checkReadContacts(context);
+                case Permission.WRITE_CONTACTS:
+                    return checkWriteContacts(context);
+                case Permission.GET_ACCOUNTS:
+                    return true;
+                case Permission.ACCESS_COARSE_LOCATION:
+                    return checkCoarseLocation(context);
+                case Permission.ACCESS_FINE_LOCATION:
+                    return checkFineLocation(context);
+                case Permission.RECORD_AUDIO:
+                    return checkRecordAudio(context);
+                case Permission.READ_PHONE_STATE:
+                    return checkReadPhoneState(context);
+                case Permission.CALL_PHONE:
+                    return true;
+                case Permission.READ_CALL_LOG:
+                    return checkReadCallLog(context);
+                case Permission.WRITE_CALL_LOG:
+                    return checkWriteCallLog(context);
+                case Permission.ADD_VOICEMAIL:
+                    return true;
+                case Permission.USE_SIP:
+                    return checkSip(context);
+                case Permission.PROCESS_OUTGOING_CALLS:
+                    return true;
+                case Permission.BODY_SENSORS:
+                    return checkSensorHeart(context);
+                case Permission.ACTIVITY_RECOGNITION:
+                    return checkSensorActivity(context);
+                case Permission.SEND_SMS:
+                case Permission.RECEIVE_MMS:
+                    return true;
+                case Permission.READ_SMS:
+                    return checkReadSms(context);
+                case Permission.RECEIVE_WAP_PUSH:
+                case Permission.RECEIVE_SMS:
+                    return true;
+                case Permission.READ_EXTERNAL_STORAGE:
+                    return checkReadStorage();
+                case Permission.WRITE_EXTERNAL_STORAGE:
+                    return checkWriteStorage();
+            }
+        } catch (Throwable e) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean checkReadCalendar(Context context) throws Throwable {
+        PermissionTest test = new CalendarReadTest(context);
+        return test.test();
+    }
+
+    private static boolean checkWriteCalendar(Context context) throws Throwable {
+        PermissionTest test = new CalendarWriteTest(context);
+        return test.test();
+    }
+
+    private static boolean checkCamera(Context context) throws Throwable {
+        PermissionTest test = new CameraTest(context);
+        return test.test();
+    }
+
+    private static boolean checkReadContacts(Context context) throws Throwable {
+        PermissionTest test = new ContactsReadTest(context);
+        return test.test();
+    }
+
+    private static boolean checkWriteContacts(Context context) throws Throwable {
+        ContentResolver resolver = context.getContentResolver();
+        PermissionTest test = new ContactsWriteTest(resolver);
+        return test.test();
+    }
+
+    private static boolean checkCoarseLocation(Context context) throws Throwable {
+        PermissionTest test = new LocationCoarseTest(context);
+        return test.test();
+    }
+
+    private static boolean checkFineLocation(Context context) throws Throwable {
+        PermissionTest test = new LocationFineTest(context);
+        return test.test();
+    }
+
+    private static boolean checkRecordAudio(Context context) throws Throwable {
+        PermissionTest test = new RecordAudioTest(context);
+        return test.test();
+    }
+
+    private static boolean checkReadPhoneState(Context context) throws Throwable {
+        PermissionTest test = new PhoneStateReadTest(context);
+        return test.test();
+    }
+
+    private static boolean checkReadCallLog(Context context) throws Throwable {
+        PermissionTest test = new CallLogReadTest(context);
+        return test.test();
+    }
+
+    private static boolean checkWriteCallLog(Context context) throws Throwable {
+        PermissionTest test = new CallLogWriteTest(context);
+        return test.test();
+    }
+
+    private static boolean checkSip(Context context) throws Throwable {
+        PermissionTest test = new SipTest(context);
+        return test.test();
+    }
+
+    private static boolean checkSensorHeart(Context context) throws Throwable {
+        PermissionTest test = new SensorHeartTest(context);
+        return test.test();
+    }
+
+    private static boolean checkSensorActivity(Context context) throws Throwable {
+        PermissionTest test = new SensorActivityTest(context);
+        return test.test();
+    }
+
+    private static boolean checkReadSms(Context context) throws Throwable {
+        PermissionTest test = new SmsReadTest(context);
+        return test.test();
+    }
+
+    private static boolean checkReadStorage() throws Throwable {
+        PermissionTest test = new StorageReadTest();
+        return test.test();
+    }
+
+    private static boolean checkWriteStorage() throws Throwable {
+        PermissionTest test = new StorageWriteTest();
+        return test.test();
+    }
+}

+ 113 - 0
permission/src/main/java/com/yanzhenjie/permission/install/BaseRequest.java

@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.install;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.AndPermission;
+import com.yanzhenjie.permission.Rationale;
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.source.Source;
+
+import java.io.File;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/1.
+ */
+abstract class BaseRequest implements InstallRequest {
+
+    private Source mSource;
+
+    private File mFile;
+    private Rationale<File> mRationale = new Rationale<File>() {
+        @Override
+        public void showRationale(Context context, File data, RequestExecutor executor) {
+            executor.execute();
+        }
+    };
+    private Action<File> mGranted;
+    private Action<File> mDenied;
+
+    BaseRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public final InstallRequest file(File file) {
+        this.mFile = file;
+        return this;
+    }
+
+    @Override
+    public final InstallRequest rationale(Rationale<File> rationale) {
+        this.mRationale = rationale;
+        return this;
+    }
+
+    @Override
+    public final InstallRequest onGranted(Action<File> granted) {
+        this.mGranted = granted;
+        return this;
+    }
+
+    @Override
+    public final InstallRequest onDenied(Action<File> denied) {
+        this.mDenied = denied;
+        return this;
+    }
+
+    /**
+     * Why permissions are required.
+     */
+    final void showRationale(RequestExecutor executor) {
+        mRationale.showRationale(mSource.getContext(), null, executor);
+    }
+
+    /**
+     * Start the installation.
+     */
+    final void install() {
+        if (mFile != null) {
+            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            Uri uri = AndPermission.getFileUri(mSource.getContext(), mFile);
+            intent.setDataAndType(uri, "application/vnd.android.package-archive");
+            mSource.startActivity(intent);
+        }
+    }
+
+    /**
+     * Callback acceptance status.
+     */
+    final void callbackSucceed() {
+        if (mGranted != null) {
+            mGranted.onAction(mFile);
+        }
+    }
+
+    /**
+     * Callback rejected state.
+     */
+    final void callbackFailed() {
+        if (mDenied != null) {
+            mDenied.onAction(mFile);
+        }
+    }
+}

+ 54 - 0
permission/src/main/java/com/yanzhenjie/permission/install/InstallRequest.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.install;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+
+import java.io.File;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public interface InstallRequest {
+
+    /**
+     * The apk file.
+     *
+     * @param file apk file.
+     */
+    InstallRequest file(File file);
+
+    /**
+     * Set request rationale.
+     */
+    InstallRequest rationale(Rationale<File> rationale);
+
+    /**
+     * Action to be taken when all permissions are granted.
+     */
+    InstallRequest onGranted(Action<File> granted);
+
+    /**
+     * Action to be taken when all permissions are denied.
+     */
+    InstallRequest onDenied(Action<File> denied);
+
+    /**
+     * Start install.
+     */
+    void start();
+}

+ 34 - 0
permission/src/main/java/com/yanzhenjie/permission/install/NRequest.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.install;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+class NRequest extends BaseRequest {
+
+    NRequest(Source source) {
+        super(source);
+    }
+
+    @Override
+    public void start() {
+        callbackSucceed();
+        install();
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/install/NRequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.install;
+
+import com.yanzhenjie.permission.Boot;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public class NRequestFactory implements Boot.InstallRequestFactory {
+
+    @Override
+    public InstallRequest create(Source source) {
+        return new NRequest(source);
+    }
+}

+ 67 - 0
permission/src/main/java/com/yanzhenjie/permission/install/ORequest.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.install;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+class ORequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    ORequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (mSource.canRequestPackageInstalls()) {
+            callbackSucceed();
+            install();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_INSTALL);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (mSource.canRequestPackageInstalls()) {
+            callbackSucceed();
+            install();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/install/ORequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.install;
+
+import com.yanzhenjie.permission.Boot;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public class ORequestFactory implements Boot.InstallRequestFactory {
+
+    @Override
+    public InstallRequest create(Source source) {
+        return new ORequest(source);
+    }
+}

+ 87 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/BaseRequest.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import android.content.Context;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/1.
+ */
+abstract class BaseRequest implements PermissionRequest {
+
+    private Source mSource;
+
+    private Rationale<Void> mRationale = new Rationale<Void>() {
+        @Override
+        public void showRationale(Context context, Void data, RequestExecutor executor) {
+            executor.execute();
+        }
+    };
+    private Action<Void> mGranted;
+    private Action<Void> mDenied;
+
+    BaseRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public final PermissionRequest rationale(Rationale<Void> rationale) {
+        this.mRationale = rationale;
+        return this;
+    }
+
+    @Override
+    public final PermissionRequest onGranted(Action<Void> granted) {
+        this.mGranted = granted;
+        return this;
+    }
+
+    @Override
+    public final PermissionRequest onDenied(Action<Void> denied) {
+        this.mDenied = denied;
+        return this;
+    }
+
+    /**
+     * Why permissions are required.
+     */
+    final void showRationale(RequestExecutor executor) {
+        mRationale.showRationale(mSource.getContext(), null, executor);
+    }
+
+    /**
+     * Callback acceptance status.
+     */
+    final void callbackSucceed() {
+        if (mGranted != null) {
+            mGranted.onAction(null);
+        }
+    }
+
+    /**
+     * Callback rejected state.
+     */
+    final void callbackFailed() {
+        if (mDenied != null) {
+            mDenied.onAction(null);
+        }
+    }
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/NRequest.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+class NRequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    NRequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (mSource.canNotify()) {
+            callbackSucceed();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_APP_DETAILS);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (mSource.canNotify()) {
+            callbackSucceed();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 29 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/NRequestFactory.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class NRequestFactory implements Notify.PermissionRequestFactory {
+
+    @Override
+    public PermissionRequest create(Source source) {
+        return new NRequest(source);
+    }
+}

+ 77 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/Notify.java

@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import android.os.Build;
+
+import com.yanzhenjie.permission.notify.listener.J1RequestFactory;
+import com.yanzhenjie.permission.notify.listener.J2RequestFactory;
+import com.yanzhenjie.permission.notify.listener.ListenerRequest;
+import com.yanzhenjie.permission.notify.option.NotifyOption;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2/22/19.
+ */
+public class Notify implements NotifyOption {
+
+    private static final PermissionRequestFactory PERMISSION_REQUEST_FACTORY;
+    private static final ListenerRequestFactory LISTENER_REQUEST_FACTORY;
+
+    static {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            PERMISSION_REQUEST_FACTORY = new ORequestFactory();
+        } else {
+            PERMISSION_REQUEST_FACTORY = new NRequestFactory();
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            LISTENER_REQUEST_FACTORY = new J2RequestFactory();
+        } else {
+            LISTENER_REQUEST_FACTORY = new J1RequestFactory();
+        }
+    }
+
+    public interface PermissionRequestFactory {
+
+        /**
+         * Create notify request.
+         */
+        PermissionRequest create(Source source);
+    }
+
+    public interface ListenerRequestFactory {
+
+        /**
+         * Create notification listener request.
+         */
+        ListenerRequest create(Source source);
+    }
+
+    private Source mSource;
+
+    public Notify(Source source) {
+        this.mSource = source;
+    }
+
+    public PermissionRequest permission() {
+        return PERMISSION_REQUEST_FACTORY.create(mSource);
+    }
+
+    public ListenerRequest listener() {
+        return LISTENER_REQUEST_FACTORY.create(mSource);
+    }
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/ORequest.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+class ORequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    ORequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (mSource.canNotify()) {
+            callbackSucceed();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_NOTIFY);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (mSource.canNotify()) {
+            callbackSucceed();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 29 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/ORequestFactory.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class ORequestFactory implements Notify.PermissionRequestFactory {
+
+    @Override
+    public PermissionRequest create(Source source) {
+        return new ORequest(source);
+    }
+}

+ 46 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/PermissionRequest.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+
+/**
+ * Created by Zhenjie Yan on 2/14/19.
+ */
+public interface PermissionRequest {
+
+    /**
+     * Set request rationale.
+     */
+    PermissionRequest rationale(Rationale<Void> rationale);
+
+    /**
+     * Action to be taken when all permissions are granted.
+     */
+    PermissionRequest onGranted(Action<Void> granted);
+
+    /**
+     * Action to be taken when all permissions are denied.
+     */
+    PermissionRequest onDenied(Action<Void> denied);
+
+    /**
+     * Start install.
+     */
+    void start();
+
+}

+ 87 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/listener/BaseRequest.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.listener;
+
+import android.content.Context;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/1.
+ */
+abstract class BaseRequest implements ListenerRequest {
+
+    private Source mSource;
+
+    private Rationale<Void> mRationale = new Rationale<Void>() {
+        @Override
+        public void showRationale(Context context, Void data, RequestExecutor executor) {
+            executor.execute();
+        }
+    };
+    private Action<Void> mGranted;
+    private Action<Void> mDenied;
+
+    BaseRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public final ListenerRequest rationale(Rationale<Void> rationale) {
+        this.mRationale = rationale;
+        return this;
+    }
+
+    @Override
+    public final ListenerRequest onGranted(Action<Void> granted) {
+        this.mGranted = granted;
+        return this;
+    }
+
+    @Override
+    public final ListenerRequest onDenied(Action<Void> denied) {
+        this.mDenied = denied;
+        return this;
+    }
+
+    /**
+     * Why permissions are required.
+     */
+    final void showRationale(RequestExecutor executor) {
+        mRationale.showRationale(mSource.getContext(), null, executor);
+    }
+
+    /**
+     * Callback acceptance status.
+     */
+    final void callbackSucceed() {
+        if (mGranted != null) {
+            mGranted.onAction(null);
+        }
+    }
+
+    /**
+     * Callback rejected state.
+     */
+    final void callbackFailed() {
+        if (mDenied != null) {
+            mDenied.onAction(null);
+        }
+    }
+}

+ 44 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/listener/J1Request.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.listener;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+class J1Request extends BaseRequest implements RequestExecutor {
+
+    J1Request(Source source) {
+        super(source);
+    }
+
+    @Override
+    public void start() {
+        callbackSucceed();
+    }
+
+    @Override
+    public void execute() {
+        // Nothing.
+    }
+
+    @Override
+    public void cancel() {
+        // Nothing.
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/listener/J1RequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.listener;
+
+import com.yanzhenjie.permission.notify.Notify;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class J1RequestFactory implements Notify.ListenerRequestFactory {
+
+    @Override
+    public ListenerRequest create(Source source) {
+        return new J1Request(source);
+    }
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/listener/J2Request.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.listener;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+class J2Request extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    J2Request(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (mSource.canListenerNotification()) {
+            callbackSucceed();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_NOTIFY_LISTENER);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (mSource.canListenerNotification()) {
+            callbackSucceed();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/listener/J2RequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.listener;
+
+import com.yanzhenjie.permission.notify.Notify;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class J2RequestFactory implements Notify.ListenerRequestFactory {
+
+    @Override
+    public ListenerRequest create(Source source) {
+        return new J2Request(source);
+    }
+}

+ 46 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/listener/ListenerRequest.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.listener;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+
+/**
+ * Created by Zhenjie Yan on 2/14/19.
+ */
+public interface ListenerRequest {
+
+    /**
+     * Set request rationale.
+     */
+    ListenerRequest rationale(Rationale<Void> rationale);
+
+    /**
+     * Action to be taken when all permissions are granted.
+     */
+    ListenerRequest onGranted(Action<Void> granted);
+
+    /**
+     * Action to be taken when all permissions are denied.
+     */
+    ListenerRequest onDenied(Action<Void> denied);
+
+    /**
+     * Start install.
+     */
+    void start();
+
+}

+ 35 - 0
permission/src/main/java/com/yanzhenjie/permission/notify/option/NotifyOption.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.notify.option;
+
+import com.yanzhenjie.permission.notify.PermissionRequest;
+import com.yanzhenjie.permission.notify.listener.ListenerRequest;
+
+/**
+ * Created by Zhenjie Yan on 2/14/19.
+ */
+public interface NotifyOption {
+
+    /**
+     * Handle permissions.
+     */
+    PermissionRequest permission();
+
+    /**
+     * Handle notify listener.
+     */
+    ListenerRequest listener();
+}

+ 53 - 0
permission/src/main/java/com/yanzhenjie/permission/option/Option.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.option;
+
+import com.yanzhenjie.permission.install.InstallRequest;
+import com.yanzhenjie.permission.notify.option.NotifyOption;
+import com.yanzhenjie.permission.overlay.OverlayRequest;
+import com.yanzhenjie.permission.runtime.option.RuntimeOption;
+import com.yanzhenjie.permission.setting.Setting;
+
+/**
+ * Created by Zhenjie Yan on 2/22/19.
+ */
+public interface Option {
+
+    /**
+     * Handle runtime permissions.
+     */
+    RuntimeOption runtime();
+
+    /**
+     * Handle request package install permission.
+     */
+    InstallRequest install();
+
+    /**
+     * Handle overlay permission.
+     */
+    OverlayRequest overlay();
+
+    /**
+     * Handle notification permission.
+     */
+    NotifyOption notification();
+
+    /**
+     * Handle system setting.
+     */
+    Setting setting();
+}

+ 110 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/BaseRequest.java

@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Build;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.R;
+import com.yanzhenjie.permission.Rationale;
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/1.
+ */
+abstract class BaseRequest implements OverlayRequest {
+
+    private Source mSource;
+
+    private Rationale<Void> mRationale = new Rationale<Void>() {
+        @Override
+        public void showRationale(Context context, Void data, RequestExecutor executor) {
+            executor.execute();
+        }
+    };
+    private Action<Void> mGranted;
+    private Action<Void> mDenied;
+
+    BaseRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public final OverlayRequest rationale(Rationale<Void> rationale) {
+        this.mRationale = rationale;
+        return this;
+    }
+
+    @Override
+    public final OverlayRequest onGranted(Action<Void> granted) {
+        this.mGranted = granted;
+        return this;
+    }
+
+    @Override
+    public final OverlayRequest onDenied(Action<Void> denied) {
+        this.mDenied = denied;
+        return this;
+    }
+
+    /**
+     * Why permissions are required.
+     */
+    final void showRationale(RequestExecutor executor) {
+        mRationale.showRationale(mSource.getContext(), null, executor);
+    }
+
+    /**
+     * Callback acceptance status.
+     */
+    final void callbackSucceed() {
+        if (mGranted != null) {
+            mGranted.onAction(null);
+        }
+    }
+
+    /**
+     * Callback rejected state.
+     */
+    final void callbackFailed() {
+        if (mDenied != null) {
+            mDenied.onAction(null);
+        }
+    }
+
+    static boolean tryDisplayDialog(Context context) {
+        Dialog dialog = new Dialog(context, R.style.Permission_Theme_Dialog_Transparent);
+        Window window = dialog.getWindow();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+        } else {
+            window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        }
+        try {
+            dialog.show();
+        } catch (Exception e) {
+            return false;
+        } finally {
+            if (dialog.isShowing()) dialog.dismiss();
+        }
+        return true;
+    }
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/LRequest.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+class LRequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    LRequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (tryDisplayDialog(mSource.getContext())) {
+            callbackSucceed();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_ALERT_WINDOW);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (tryDisplayDialog(mSource.getContext())) {
+            callbackSucceed();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/LRequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay;
+
+import com.yanzhenjie.permission.Boot;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class LRequestFactory implements Boot.OverlayRequestFactory {
+
+    @Override
+    public OverlayRequest create(Source source) {
+        return new LRequest(source);
+    }
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/MRequest.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+class MRequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    MRequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (mSource.canDrawOverlays()) {
+            onCallback();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_OVERLAY);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (mSource.canDrawOverlays() && tryDisplayDialog(mSource.getContext())) {
+            callbackSucceed();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/MRequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay;
+
+import com.yanzhenjie.permission.Boot;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class MRequestFactory implements Boot.OverlayRequestFactory {
+
+    @Override
+    public OverlayRequest create(Source source) {
+        return new MRequest(source);
+    }
+}

+ 45 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/OverlayRequest.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public interface OverlayRequest {
+
+    /**
+     * Set request rationale.
+     */
+    OverlayRequest rationale(Rationale<Void> rationale);
+
+    /**
+     * Action to be taken when all permissions are granted.
+     */
+    OverlayRequest onGranted(Action<Void> granted);
+
+    /**
+     * Action to be taken when all permissions are denied.
+     */
+    OverlayRequest onDenied(Action<Void> denied);
+
+    /**
+     * Start request.
+     */
+    void start();
+}

+ 140 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/setting/LSettingPage.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay.setting;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/29.
+ */
+public class LSettingPage {
+
+    private static final String MARK = Build.MANUFACTURER.toLowerCase();
+
+    private Source mSource;
+
+    public LSettingPage(Source source) {
+        this.mSource = source;
+    }
+
+    public void start(int requestCode) {
+        Intent intent;
+        if (MARK.contains("huawei")) {
+            intent = huaweiApi(mSource.getContext());
+        } else if (MARK.contains("xiaomi")) {
+            intent = xiaomiApi(mSource.getContext());
+        } else if (MARK.contains("oppo")) {
+            intent = oppoApi(mSource.getContext());
+        } else if (MARK.contains("vivo")) {
+            intent = vivoApi(mSource.getContext());
+        } else if (MARK.contains("meizu")) {
+            intent = meizuApi(mSource.getContext());
+        } else {
+            intent = defaultApi(mSource.getContext());
+        }
+
+        try {
+            mSource.startActivityForResult(intent, requestCode);
+        } catch (Exception e) {
+            intent = defaultApi(mSource.getContext());
+            mSource.startActivityForResult(intent, requestCode);
+        }
+    }
+
+    private static Intent defaultApi(Context context) {
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        return intent;
+    }
+
+    private Intent huaweiApi(Context context) {
+        Intent intent = new Intent();
+        intent.putExtra("package", context.getPackageName());
+        intent.putExtra("packageName", context.getPackageName());
+        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        intent.setClassName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private Intent xiaomiApi(Context context) {
+        Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+        intent.putExtra("extra_pkgname", context.getPackageName());
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private Intent oppoApi(Context context) {
+        Intent intent = new Intent();
+        intent.putExtra("packageName", context.getPackageName());
+        intent.setClassName("com.color.safecenter",
+            "com.color.safecenter.permission.floatwindow.FloatWindowListActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.oppo.safe", "com.oppo.safe.permission.PermissionAppListActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private Intent vivoApi(Context context) {
+        Intent intent = new Intent();
+        intent.setClassName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.FloatWindowManager");
+        intent.putExtra("packagename", context.getPackageName());
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.iqoo.secure", "com.iqoo.secure.safeguard.SoftPermissionDetailActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private Intent meizuApi(Context context) {
+        Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
+        intent.putExtra("packageName", context.getPackageName());
+        intent.setComponent(new ComponentName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity"));
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static boolean hasActivity(Context context, Intent intent) {
+        PackageManager packageManager = context.getPackageManager();
+        return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+    }
+}

+ 83 - 0
permission/src/main/java/com/yanzhenjie/permission/overlay/setting/MSettingPage.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.overlay.setting;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/30.
+ */
+public class MSettingPage {
+
+    private static final String MARK = Build.MANUFACTURER.toLowerCase();
+
+    private Source mSource;
+
+    public MSettingPage(Source source) {
+        this.mSource = source;
+    }
+
+    public void start(int requestCode) {
+        Intent intent;
+        if (MARK.contains("meizu")) {
+            intent = meiZuApi(mSource.getContext());
+        } else {
+            intent = defaultApi(mSource.getContext());
+        }
+
+        try {
+            mSource.startActivityForResult(intent, requestCode);
+        } catch (Exception e) {
+            intent = appDetailsApi(mSource.getContext());
+            mSource.startActivityForResult(intent, requestCode);
+        }
+    }
+
+    private static Intent appDetailsApi(Context context) {
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        return intent;
+    }
+
+    private static Intent defaultApi(Context context) {
+        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        if (hasActivity(context, intent)) return intent;
+
+        return appDetailsApi(context);
+    }
+
+    private static Intent meiZuApi(Context context) {
+        Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
+        intent.putExtra("packageName", context.getPackageName());
+        intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static boolean hasActivity(Context context, Intent intent) {
+        PackageManager packageManager = context.getPackageManager();
+        return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+    }
+}

+ 138 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/BaseRequest.java

@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.checker.PermissionChecker;
+import com.yanzhenjie.permission.source.Source;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Created Zhenjie Yan on 2019-10-10.
+ */
+abstract class BaseRequest implements PermissionRequest {
+
+    private Source mSource;
+
+    private Rationale<List<String>> mRationale = new Rationale<List<String>>() {
+        @Override
+        public void showRationale(Context context, List<String> data, RequestExecutor executor) {
+            executor.execute();
+        }
+    };
+    private Action<List<String>> mGranted;
+    private Action<List<String>> mDenied;
+
+    BaseRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public PermissionRequest rationale(@NonNull Rationale<List<String>> rationale) {
+        this.mRationale = rationale;
+        return this;
+    }
+
+    @Override
+    public PermissionRequest onGranted(@NonNull Action<List<String>> granted) {
+        this.mGranted = granted;
+        return this;
+    }
+
+    @Override
+    public PermissionRequest onDenied(@NonNull Action<List<String>> denied) {
+        this.mDenied = denied;
+        return this;
+    }
+
+    /**
+     * Why permissions are required.
+     */
+    final void showRationale(List<String> rationaleList, RequestExecutor executor) {
+        mRationale.showRationale(mSource.getContext(), rationaleList, executor);
+    }
+
+    /**
+     * Callback acceptance status.
+     */
+    final void callbackSucceed(List<String> grantedList) {
+        if (mGranted != null) {
+            mGranted.onAction(grantedList);
+        }
+    }
+
+    /**
+     * Callback rejected state.
+     */
+    final void callbackFailed(List<String> deniedList) {
+        if (mDenied != null) {
+            mDenied.onAction(deniedList);
+        }
+    }
+
+    /**
+     * Filter the permissions you want to apply; remove unsupported and duplicate permissions.
+     */
+    public static List<String> filterPermissions(List<String> permissions) {
+        permissions = new ArrayList<>(new HashSet<>(permissions));
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            permissions.remove(Permission.READ_PHONE_NUMBERS);
+            permissions.remove(Permission.ANSWER_PHONE_CALLS);
+        }
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            permissions.remove(Permission.ACTIVITY_RECOGNITION);
+            permissions.remove(Permission.ACCESS_BACKGROUND_LOCATION);
+        }
+        return permissions;
+    }
+
+    /**
+     * Get denied permissions.
+     */
+    public static List<String> getDeniedPermissions(PermissionChecker checker, Source source, List<String> permissions) {
+        List<String> deniedList = new ArrayList<>(1);
+        for (String permission : permissions) {
+            if (!checker.hasPermission(source.getContext(), permission)) {
+                deniedList.add(permission);
+            }
+        }
+        return deniedList;
+    }
+
+    /**
+     * Get permissions to show rationale.
+     */
+    public static List<String> getRationalePermissions(Source source, List<String> deniedPermissions) {
+        List<String> rationaleList = new ArrayList<>(1);
+        for (String permission : deniedPermissions) {
+            if (source.isShowRationalePermission(permission)) {
+                rationaleList.add(permission);
+            }
+        }
+        return rationaleList;
+    }
+}

+ 82 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/LRequest.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+
+import android.support.annotation.NonNull;
+
+import com.yanzhenjie.permission.checker.PermissionChecker;
+import com.yanzhenjie.permission.checker.StrictChecker;
+import com.yanzhenjie.permission.source.Source;
+import com.yanzhenjie.permission.task.TaskExecutor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/1/25.
+ */
+class LRequest extends BaseRequest {
+
+    private static final PermissionChecker STRICT_CHECKER = new StrictChecker();
+
+    private Source mSource;
+
+    private List<String> mPermissions;
+
+    LRequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public PermissionRequest permission(@NonNull String... permissions) {
+        mPermissions = new ArrayList<>();
+        mPermissions.addAll(Arrays.asList(permissions));
+        return this;
+    }
+
+    @Override
+    public PermissionRequest permission(@NonNull String[]... groups) {
+        mPermissions = new ArrayList<>();
+        for (String[] group : groups) {
+            mPermissions.addAll(Arrays.asList(group));
+        }
+        return this;
+    }
+
+    @Override
+    public void start() {
+        mPermissions = filterPermissions(mPermissions);
+
+        new TaskExecutor<List<String>>(mSource.getContext()) {
+            @Override
+            protected List<String> doInBackground(Void... voids) {
+                return getDeniedPermissions(STRICT_CHECKER, mSource, mPermissions);
+            }
+
+            @Override
+            protected void onFinish(List<String> deniedList) {
+                if (deniedList.isEmpty()) {
+                    callbackSucceed(mPermissions);
+                } else {
+                    callbackFailed(deniedList);
+                }
+            }
+        }.execute();
+    }
+}

+ 29 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/LRequestFactory.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public class LRequestFactory implements Runtime.PermissionRequestFactory {
+
+    @Override
+    public PermissionRequest create(Source source) {
+        return new LRequest(source);
+    }
+}

+ 118 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/MRequest.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+
+import android.support.annotation.NonNull;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.checker.DoubleChecker;
+import com.yanzhenjie.permission.checker.PermissionChecker;
+import com.yanzhenjie.permission.checker.StandardChecker;
+import com.yanzhenjie.permission.source.Source;
+import com.yanzhenjie.permission.task.TaskExecutor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2016/9/9.
+ */
+class MRequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private static final PermissionChecker STANDARD_CHECKER = new StandardChecker();
+    private static final PermissionChecker DOUBLE_CHECKER = new DoubleChecker();
+
+    private Source mSource;
+
+    private List<String> mPermissions;
+
+    private List<String> mDeniedPermissions;
+
+    MRequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public PermissionRequest permission(@NonNull String... permissions) {
+        mPermissions = new ArrayList<>();
+        mPermissions.addAll(Arrays.asList(permissions));
+        return this;
+    }
+
+    @Override
+    public PermissionRequest permission(@NonNull String[]... groups) {
+        mPermissions = new ArrayList<>();
+        for (String[] group : groups) {
+            mPermissions.addAll(Arrays.asList(group));
+        }
+        return this;
+    }
+
+    @Override
+    public void start() {
+        mPermissions = filterPermissions(mPermissions);
+
+        mDeniedPermissions = getDeniedPermissions(STANDARD_CHECKER, mSource, mPermissions);
+        if (mDeniedPermissions.size() > 0) {
+            List<String> rationaleList = getRationalePermissions(mSource, mDeniedPermissions);
+            if (rationaleList.size() > 0) {
+                showRationale(rationaleList, this);
+            } else {
+                execute();
+            }
+        } else {
+            onCallback();
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_PERMISSION);
+        request.setPermissions(mDeniedPermissions);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        onCallback();
+    }
+
+    @Override
+    public void onCallback() {
+        new TaskExecutor<List<String>>(mSource.getContext()) {
+            @Override
+            protected List<String> doInBackground(Void... voids) {
+                return getDeniedPermissions(DOUBLE_CHECKER, mSource, mPermissions);
+            }
+
+            @Override
+            protected void onFinish(List<String> deniedList) {
+                if (deniedList.isEmpty()) {
+                    callbackSucceed(mPermissions);
+                } else {
+                    callbackFailed(deniedList);
+                }
+            }
+        }.execute();
+    }
+}

+ 29 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/MRequestFactory.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/28.
+ */
+public class MRequestFactory implements Runtime.PermissionRequestFactory {
+
+    @Override
+    public PermissionRequest create(Source source) {
+        return new MRequest(source);
+    }
+}

+ 226 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/Permission.java

@@ -0,0 +1,226 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+import android.content.Context;
+import android.os.Build;
+
+import com.yanzhenjie.permission.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <p>Permissions.</p>
+ * Created by Zhenjie Yan on 2017/8/4.
+ */
+public class Permission {
+
+    public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
+    public static final String WRITE_CALENDAR = "android.permission.WRITE_CALENDAR";
+
+    public static final String CAMERA = "android.permission.CAMERA";
+
+    public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
+    public static final String WRITE_CONTACTS = "android.permission.WRITE_CONTACTS";
+    public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+
+    public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+    public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+    public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
+
+    public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+
+    public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+    public static final String CALL_PHONE = "android.permission.CALL_PHONE";
+    public static final String USE_SIP = "android.permission.USE_SIP";
+    public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
+    public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+    public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
+
+    public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
+    public static final String WRITE_CALL_LOG = "android.permission.WRITE_CALL_LOG";
+    public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+
+    public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+    public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
+
+    public static final String SEND_SMS = "android.permission.SEND_SMS";
+    public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
+    public static final String READ_SMS = "android.permission.READ_SMS";
+    public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
+    public static final String RECEIVE_MMS = "android.permission.RECEIVE_MMS";
+
+    public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
+    public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
+
+    public static final class Group {
+
+        public static final String[] CALENDAR = new String[]{Permission.READ_CALENDAR, Permission.WRITE_CALENDAR};
+
+        public static final String[] CAMERA = new String[]{Permission.CAMERA};
+
+        public static final String[] CONTACTS = new String[]{Permission.READ_CONTACTS, Permission.WRITE_CONTACTS, Permission.GET_ACCOUNTS};
+
+        public static final String[] LOCATION = new String[]{Permission.ACCESS_FINE_LOCATION, Permission.ACCESS_COARSE_LOCATION,
+            Permission.ACCESS_BACKGROUND_LOCATION};
+
+        public static final String[] MICROPHONE = new String[]{Permission.RECORD_AUDIO};
+
+        public static final String[] PHONE = new String[]{Permission.READ_PHONE_STATE, Permission.CALL_PHONE, Permission.USE_SIP,
+            Permission.READ_PHONE_NUMBERS, Permission.ANSWER_PHONE_CALLS, Permission.ADD_VOICEMAIL};
+
+        public static final String[] CALL_LOG = new String[]{Permission.READ_CALL_LOG, Permission.WRITE_CALL_LOG,
+            Permission.PROCESS_OUTGOING_CALLS};
+
+        public static final String[] SENSORS = new String[]{Permission.BODY_SENSORS};
+
+        public static final String[] ACTIVITY_RECOGNITION = new String[]{Permission.ACTIVITY_RECOGNITION};
+
+        public static final String[] SMS = new String[]{Permission.SEND_SMS, Permission.RECEIVE_SMS, Permission.READ_SMS,
+            Permission.RECEIVE_WAP_PUSH, Permission.RECEIVE_MMS};
+
+        public static final String[] STORAGE = new String[]{Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE};
+    }
+
+    /**
+     * Turn permissions into text.
+     */
+    public static List<String> transformText(Context context, String... permissions) {
+        return transformText(context, Arrays.asList(permissions));
+    }
+
+    /**
+     * Turn permissions into text.
+     */
+    public static List<String> transformText(Context context, String[]... groups) {
+        List<String> permissionList = new ArrayList<>();
+        for (String[] group : groups) {
+            permissionList.addAll(Arrays.asList(group));
+        }
+        return transformText(context, permissionList);
+    }
+
+    /**
+     * Turn permissions into text.
+     */
+    public static List<String> transformText(Context context, List<String> permissions) {
+        List<String> textList = new ArrayList<>();
+        for (String permission : permissions) {
+            switch (permission) {
+                case Permission.READ_CALENDAR:
+                case Permission.WRITE_CALENDAR: {
+                    String message = context.getString(R.string.permission_name_calendar);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+
+                case Permission.CAMERA: {
+                    String message = context.getString(R.string.permission_name_camera);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.GET_ACCOUNTS:
+                case Permission.READ_CONTACTS:
+                case Permission.WRITE_CONTACTS: {
+                    String message = context.getString(R.string.permission_name_contacts);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.ACCESS_FINE_LOCATION:
+                case Permission.ACCESS_COARSE_LOCATION: {
+                    String message = context.getString(R.string.permission_name_location);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.RECORD_AUDIO: {
+                    String message = context.getString(R.string.permission_name_microphone);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.READ_PHONE_STATE:
+                case Permission.CALL_PHONE:
+                case Permission.ADD_VOICEMAIL:
+                case Permission.USE_SIP:
+                case Permission.READ_PHONE_NUMBERS:
+                case Permission.ANSWER_PHONE_CALLS: {
+                    String message = context.getString(R.string.permission_name_phone);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.READ_CALL_LOG:
+                case Permission.WRITE_CALL_LOG:
+                case Permission.PROCESS_OUTGOING_CALLS: {
+                    int messageId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ?
+                        R.string.permission_name_call_log : R.string.permission_name_phone;
+                    String message = context.getString(messageId);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.BODY_SENSORS: {
+                    String message = context.getString(R.string.permission_name_sensors);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.ACTIVITY_RECOGNITION: {
+                    String message = context.getString(R.string.permission_name_activity_recognition);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.SEND_SMS:
+                case Permission.RECEIVE_SMS:
+                case Permission.READ_SMS:
+                case Permission.RECEIVE_WAP_PUSH:
+                case Permission.RECEIVE_MMS: {
+                    String message = context.getString(R.string.permission_name_sms);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+                case Permission.READ_EXTERNAL_STORAGE:
+                case Permission.WRITE_EXTERNAL_STORAGE: {
+                    String message = context.getString(R.string.permission_name_storage);
+                    if (!textList.contains(message)) {
+                        textList.add(message);
+                    }
+                    break;
+                }
+            }
+        }
+        return textList;
+    }
+
+}

+ 59 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/PermissionDef.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+
+import android.support.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created Zhenjie Yan on 2019-10-10.
+ */
+@StringDef({
+    Permission.READ_CALENDAR,
+    Permission.WRITE_CALENDAR,
+    Permission.CAMERA,
+    Permission.READ_CONTACTS,
+    Permission.WRITE_CONTACTS,
+    Permission.GET_ACCOUNTS,
+    Permission.ACCESS_FINE_LOCATION,
+    Permission.ACCESS_COARSE_LOCATION,
+    Permission.ACCESS_BACKGROUND_LOCATION,
+    Permission.RECORD_AUDIO,
+    Permission.READ_PHONE_STATE,
+    Permission.CALL_PHONE,
+    Permission.ADD_VOICEMAIL,
+    Permission.USE_SIP,
+    Permission.READ_PHONE_NUMBERS,
+    Permission.ANSWER_PHONE_CALLS,
+    Permission.READ_CALL_LOG,
+    Permission.WRITE_CALL_LOG,
+    Permission.PROCESS_OUTGOING_CALLS,
+    Permission.BODY_SENSORS,
+    Permission.ACTIVITY_RECOGNITION,
+    Permission.SEND_SMS,
+    Permission.RECEIVE_SMS,
+    Permission.READ_SMS,
+    Permission.RECEIVE_WAP_PUSH,
+    Permission.RECEIVE_MMS,
+    Permission.READ_EXTERNAL_STORAGE,
+    Permission.WRITE_EXTERNAL_STORAGE
+})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PermissionDef {
+}

+ 63 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/PermissionRequest.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+
+import android.support.annotation.NonNull;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+
+import java.util.List;
+
+/**
+ * <p>Permission request.</p>
+ * Created by Zhenjie Yan on 2016/9/9.
+ */
+public interface PermissionRequest {
+
+    /**
+     * One or more permissions.
+     */
+    PermissionRequest permission(@NonNull @PermissionDef String... permissions);
+
+    /**
+     * One or more permissions group.
+     *
+     * @param groups use constants in {@link Permission.Group}.
+     */
+    PermissionRequest permission(@NonNull String[]... groups);
+
+    /**
+     * Set request rationale.
+     */
+    PermissionRequest rationale(@NonNull Rationale<List<String>> rationale);
+
+    /**
+     * Action to be taken when all permissions are granted.
+     */
+    PermissionRequest onGranted(@NonNull Action<List<String>> granted);
+
+    /**
+     * Action to be taken when all permissions are denied.
+     */
+    PermissionRequest onDenied(@NonNull Action<List<String>> denied);
+
+    /**
+     * Request permission.
+     */
+    void start();
+}

+ 128 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/Runtime.java

@@ -0,0 +1,128 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+
+import com.yanzhenjie.permission.runtime.option.RuntimeOption;
+import com.yanzhenjie.permission.runtime.setting.AllRequest;
+import com.yanzhenjie.permission.runtime.setting.SettingRequest;
+import com.yanzhenjie.permission.source.Source;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/5/2.
+ */
+public class Runtime implements RuntimeOption {
+
+    private static final String ADD_VOICEMAIL_MANIFEST = "android.permission.ADD_VOICEMAIL";
+
+    private static final PermissionRequestFactory FACTORY;
+    private static List<String> sAppPermissions;
+
+    static {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            FACTORY = new MRequestFactory();
+        } else {
+            FACTORY = new LRequestFactory();
+        }
+    }
+
+    public interface PermissionRequestFactory {
+
+        /**
+         * Create permission request.
+         */
+        PermissionRequest create(Source source);
+    }
+
+    private Source mSource;
+
+    public Runtime(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public PermissionRequest permission(@NonNull String... permissions) {
+        checkPermissions(permissions);
+        return FACTORY.create(mSource).permission(permissions);
+    }
+
+    @Override
+    public PermissionRequest permission(@NonNull String[]... groups) {
+        List<String> permissionList = new ArrayList<>();
+        for (String[] group : groups) {
+            checkPermissions(group);
+            permissionList.addAll(Arrays.asList(group));
+        }
+        String[] permissions = permissionList.toArray(new String[0]);
+        return permission(permissions);
+    }
+
+    @Override
+    public SettingRequest setting() {
+        return new AllRequest(mSource);
+    }
+
+    /**
+     * Check if the permissions are valid and each permission has been registered in manifest.xml. This method will
+     * throw a exception if permissions are invalid or there is any permission which is not registered in manifest.xml.
+     *
+     * @param permissions permissions which will be checked.
+     */
+    private void checkPermissions(String... permissions) {
+        if (sAppPermissions == null) {
+            sAppPermissions = new ArrayList<>(getManifestPermissions(mSource.getContext()));
+            if (sAppPermissions.contains(ADD_VOICEMAIL_MANIFEST)) {
+                sAppPermissions.add(Permission.ADD_VOICEMAIL);
+            }
+        }
+
+        if (permissions.length == 0) {
+            throw new IllegalArgumentException("Please enter at least one permission.");
+        }
+
+        for (String target : permissions) {
+            if (!sAppPermissions.contains(target)) {
+                throw new IllegalStateException(String.format("The permission %1$s is not registered in manifest.xml", target));
+            }
+        }
+    }
+
+    /**
+     * Get a list of permissions in the manifest.
+     */
+    public static List<String> getManifestPermissions(Context context) {
+        try {
+            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
+            String[] permissions = packageInfo.requestedPermissions;
+            if (permissions == null || permissions.length == 0) {
+                throw new IllegalStateException("You did not register any permissions in the manifest.xml.");
+            }
+            return Arrays.asList(permissions);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new AssertionError("Package name cannot be found.");
+        }
+    }
+}

+ 47 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/option/RuntimeOption.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime.option;
+
+
+import android.support.annotation.NonNull;
+
+import com.yanzhenjie.permission.runtime.Permission;
+import com.yanzhenjie.permission.runtime.PermissionDef;
+import com.yanzhenjie.permission.runtime.PermissionRequest;
+import com.yanzhenjie.permission.runtime.setting.SettingRequest;
+
+/**
+ * Created by Zhenjie Yan on 2/22/19.
+ */
+public interface RuntimeOption {
+
+    /**
+     * One or more permissions.
+     */
+    PermissionRequest permission(@NonNull @PermissionDef String... permissions);
+
+    /**
+     * One or more permission groups.
+     *
+     * @param groups use constants in {@link Permission.Group}.
+     */
+    PermissionRequest permission(@NonNull String[]... groups);
+
+    /**
+     * Permission settings.
+     */
+    SettingRequest setting();
+}

+ 37 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/setting/AllRequest.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime.setting;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * <p>SettingRequest executor.</p>
+ * Created by Zhenjie Yan on 2016/12/28.
+ */
+public class AllRequest implements SettingRequest {
+
+    private Source mSource;
+
+    public AllRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public void start(int requestCode) {
+        SettingPage setting = new SettingPage(mSource);
+        setting.start(requestCode);
+    }
+}

+ 133 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/setting/SettingPage.java

@@ -0,0 +1,133 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime.setting;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/30.
+ */
+public class SettingPage {
+
+    private static final String MARK = Build.MANUFACTURER.toLowerCase();
+
+    private Source mSource;
+
+    public SettingPage(Source source) {
+        this.mSource = source;
+    }
+
+    /**
+     * Start.
+     *
+     * @param requestCode this code will be returned in onActivityResult() when the activity exits.
+     */
+    public void start(int requestCode) {
+        Intent intent;
+        if (MARK.contains("huawei")) {
+            intent = huaweiApi(mSource.getContext());
+        } else if (MARK.contains("xiaomi")) {
+            intent = xiaomiApi(mSource.getContext());
+        } else if (MARK.contains("oppo")) {
+            intent = oppoApi(mSource.getContext());
+        } else if (MARK.contains("vivo")) {
+            intent = vivoApi(mSource.getContext());
+        } else if (MARK.contains("meizu")) {
+            intent = meizuApi(mSource.getContext());
+        } else {
+            intent = defaultApi(mSource.getContext());
+        }
+        try {
+            mSource.startActivityForResult(intent, requestCode);
+        } catch (Exception e) {
+            intent = defaultApi(mSource.getContext());
+            mSource.startActivityForResult(intent, requestCode);
+        }
+    }
+
+    private static Intent defaultApi(Context context) {
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        return intent;
+    }
+
+    private static Intent huaweiApi(Context context) {
+        Intent intent = new Intent();
+        intent.setClassName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static Intent xiaomiApi(Context context) {
+        Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+        intent.putExtra("extra_pkgname", context.getPackageName());
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static Intent vivoApi(Context context) {
+        Intent intent = new Intent();
+        intent.putExtra("packagename", context.getPackageName());
+        intent.setClassName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.SoftPermissionDetailActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.iqoo.secure", "com.iqoo.secure.safeguard.SoftPermissionDetailActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static Intent oppoApi(Context context) {
+        Intent intent = new Intent();
+        intent.putExtra("packageName", context.getPackageName());
+        intent.setClassName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        intent.setClassName("com.oppo.safe", "com.oppo.safe.permission.PermissionAppListActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static Intent meizuApi(Context context) {
+        Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
+        intent.putExtra("packageName", context.getPackageName());
+        intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity");
+        if (hasActivity(context, intent)) return intent;
+
+        return defaultApi(context);
+    }
+
+    private static boolean hasActivity(Context context, Intent intent) {
+        PackageManager packageManager = context.getPackageManager();
+        return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+    }
+}

+ 27 - 0
permission/src/main/java/com/yanzhenjie/permission/runtime/setting/SettingRequest.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.runtime.setting;
+
+/**
+ * Created by Zhenjie Yan on 2018/4/30.
+ */
+public interface SettingRequest {
+
+    /**
+     * Start the setup.
+     */
+    void start(int requestCode);
+}

+ 57 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/Setting.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting;
+
+import android.os.Build;
+
+import com.yanzhenjie.permission.setting.write.LWriteRequestFactory;
+import com.yanzhenjie.permission.setting.write.MWriteRequestFactory;
+import com.yanzhenjie.permission.setting.write.WriteRequest;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 3/1/19.
+ */
+public class Setting {
+
+    private static final SettingRequestFactory SETTING_REQUEST_FACTORY;
+
+    static {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            SETTING_REQUEST_FACTORY = new MWriteRequestFactory();
+        } else {
+            SETTING_REQUEST_FACTORY = new LWriteRequestFactory();
+        }
+    }
+
+    public interface SettingRequestFactory {
+
+        WriteRequest create(Source source);
+    }
+
+    private Source mSource;
+
+    public Setting(Source source) {
+        this.mSource = source;
+    }
+
+    /**
+     * Handle write system settings.
+     */
+    public WriteRequest write() {
+        return SETTING_REQUEST_FACTORY.create(mSource);
+    }
+}

+ 87 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/write/BaseRequest.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting.write;
+
+import android.content.Context;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/1.
+ */
+abstract class BaseRequest implements WriteRequest {
+
+    private Source mSource;
+
+    private Rationale<Void> mRationale = new Rationale<Void>() {
+        @Override
+        public void showRationale(Context context, Void data, RequestExecutor executor) {
+            executor.execute();
+        }
+    };
+    private Action<Void> mGranted;
+    private Action<Void> mDenied;
+
+    BaseRequest(Source source) {
+        this.mSource = source;
+    }
+
+    @Override
+    public final WriteRequest rationale(Rationale<Void> rationale) {
+        this.mRationale = rationale;
+        return this;
+    }
+
+    @Override
+    public final WriteRequest onGranted(Action<Void> granted) {
+        this.mGranted = granted;
+        return this;
+    }
+
+    @Override
+    public final WriteRequest onDenied(Action<Void> denied) {
+        this.mDenied = denied;
+        return this;
+    }
+
+    /**
+     * Why permissions are required.
+     */
+    final void showRationale(RequestExecutor executor) {
+        mRationale.showRationale(mSource.getContext(), null, executor);
+    }
+
+    /**
+     * Callback acceptance status.
+     */
+    final void callbackSucceed() {
+        if (mGranted != null) {
+            mGranted.onAction(null);
+        }
+    }
+
+    /**
+     * Callback rejected state.
+     */
+    final void callbackFailed() {
+        if (mDenied != null) {
+            mDenied.onAction(null);
+        }
+    }
+}

+ 33 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/write/LWriteRequest.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting.write;
+
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 3/1/19.
+ */
+public class LWriteRequest extends BaseRequest {
+
+    public LWriteRequest(Source source) {
+        super(source);
+    }
+
+    @Override
+    public void start() {
+        callbackSucceed();
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/write/LWriteRequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting.write;
+
+import com.yanzhenjie.permission.setting.Setting;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 3/1/19.
+ */
+public class LWriteRequestFactory implements Setting.SettingRequestFactory {
+
+    @Override
+    public WriteRequest create(Source source) {
+        return new LWriteRequest(source);
+    }
+}

+ 65 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/write/MWriteRequest.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting.write;
+
+import com.yanzhenjie.permission.RequestExecutor;
+import com.yanzhenjie.permission.bridge.BridgeRequest;
+import com.yanzhenjie.permission.bridge.RequestManager;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 3/1/19.
+ */
+public class MWriteRequest extends BaseRequest implements RequestExecutor, BridgeRequest.Callback {
+
+    private Source mSource;
+
+    public MWriteRequest(Source source) {
+        super(source);
+        this.mSource = source;
+    }
+
+    @Override
+    public void start() {
+        if (mSource.canWriteSetting()) {
+            callbackSucceed();
+        } else {
+            showRationale(this);
+        }
+    }
+
+    @Override
+    public void execute() {
+        BridgeRequest request = new BridgeRequest(mSource);
+        request.setType(BridgeRequest.TYPE_WRITE_SETTING);
+        request.setCallback(this);
+        RequestManager.get().add(request);
+    }
+
+    @Override
+    public void cancel() {
+        callbackFailed();
+    }
+
+    @Override
+    public void onCallback() {
+        if (mSource.canWriteSetting()) {
+            callbackSucceed();
+        } else {
+            callbackFailed();
+        }
+    }
+}

+ 30 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/write/MWriteRequestFactory.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting.write;
+
+import com.yanzhenjie.permission.setting.Setting;
+import com.yanzhenjie.permission.source.Source;
+
+/**
+ * Created by Zhenjie Yan on 3/1/19.
+ */
+public class MWriteRequestFactory implements Setting.SettingRequestFactory {
+
+    @Override
+    public WriteRequest create(Source source) {
+        return new MWriteRequest(source);
+    }
+}

+ 45 - 0
permission/src/main/java/com/yanzhenjie/permission/setting/write/WriteRequest.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.setting.write;
+
+import com.yanzhenjie.permission.Action;
+import com.yanzhenjie.permission.Rationale;
+
+/**
+ * Created by Zhenjie Yan on 3/1/19.
+ */
+public interface WriteRequest {
+
+    /**
+     * Set request rationale.
+     */
+    WriteRequest rationale(Rationale<Void> rationale);
+
+    /**
+     * Action to be taken when all permissions are granted.
+     */
+    WriteRequest onGranted(Action<Void> granted);
+
+    /**
+     * Action to be taken when all permissions are denied.
+     */
+    WriteRequest onDenied(Action<Void> denied);
+
+    /**
+     * Start install.
+     */
+    void start();
+}

+ 56 - 0
permission/src/main/java/com/yanzhenjie/permission/source/ActivitySource.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.source;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+/**
+ * <p>Context Wrapper.</p>
+ * Created by Zhenjie Yan on 2017/5/1.
+ */
+public class ActivitySource extends Source {
+
+    private Activity mActivity;
+
+    public ActivitySource(Activity activity) {
+        this.mActivity = activity;
+    }
+
+    @Override
+    public Context getContext() {
+        return mActivity;
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        mActivity.startActivity(intent);
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        mActivity.startActivityForResult(intent, requestCode);
+    }
+
+    @Override
+    public boolean isShowRationalePermission(String permission) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
+
+        return mActivity.shouldShowRequestPermissionRationale(permission);
+    }
+}

+ 67 - 0
permission/src/main/java/com/yanzhenjie/permission/source/ContextSource.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright © Zhenjie Yan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.permission.source;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import java.lang.reflect.Method;
+
+/**
+ * <p>Context Wrapper.</p>
+ * Created by Zhenjie Yan on 2017/5/1.
+ */
+public class ContextSource extends Source {
+
+    private Context mContext;
+
+    public ContextSource(Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        throw new UnsupportedOperationException("Unsupported operation.");
+    }
+
+    @Override
+    public boolean isShowRationalePermission(String permission) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
+
+        PackageManager packageManager = mContext.getPackageManager();
+        Class<?> pkManagerClass = packageManager.getClass();
+        try {
+            Method method = pkManagerClass.getMethod("shouldShowRequestPermissionRationale", String.class);
+            if (!method.isAccessible()) method.setAccessible(true);
+            return (boolean)method.invoke(packageManager, permission);
+        } catch (Exception ignored) {
+            return false;
+        }
+    }
+}

+ 0 - 0
permission/src/main/java/com/yanzhenjie/permission/source/FragmentSource.java


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff