Przeglądaj źródła

app版本管理

everydatestudy 3 lat temu
rodzic
commit
735090b5f5
100 zmienionych plików z 6032 dodań i 1 usunięć
  1. 43 0
      .gitignore
  2. 202 0
      LICENSE
  3. 86 0
      NOTICE
  4. 83 1
      README.md
  5. 34 0
      build.sh
  6. 5 0
      demo/README.md
  7. 15 0
      demo/android/push/SimplePushDemo/.gitignore
  8. 1 0
      demo/android/push/SimplePushDemo/ajpushlibrary/.gitignore
  9. 79 0
      demo/android/push/SimplePushDemo/ajpushlibrary/build.gradle
  10. 0 0
      demo/android/push/SimplePushDemo/ajpushlibrary/consumer-rules.pro
  11. BIN
      demo/android/push/SimplePushDemo/ajpushlibrary/libs/com.heytap.msp.aar
  12. BIN
      demo/android/push/SimplePushDemo/ajpushlibrary/libs/vivo_pushsdk-v2.9.0.0.aar
  13. 21 0
      demo/android/push/SimplePushDemo/ajpushlibrary/proguard-rules.pro
  14. 27 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/androidTest/java/com/anji/plus/ajpushlibrary/ExampleInstrumentedTest.java
  15. 121 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/AndroidManifest.xml
  16. 254 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/AppSpPushConfig.java
  17. 40 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/AppSpPushConstant.java
  18. 45 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/AppSpPushLog.java
  19. 15 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/ActionType.java
  20. 21 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/AppSpPushBaseController.java
  21. 18 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/AppSpPushBaseHandler.java
  22. 53 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/AppSpPushBaseRequest.java
  23. 18 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/BrandType.java
  24. 28 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushCallBack.java
  25. 231 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushHttpClient.java
  26. 26 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushPostData.java
  27. 8 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushRequestMethod.java
  28. 17 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushRequestUrl.java
  29. 14 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushRespCode.java
  30. 168 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/hw/HWPushService.java
  31. 160 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/hw/HWPushUtil.java
  32. 176 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/interfaceToUser/UserInterface.java
  33. 146 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/jpush/PushMessageReceiver.java
  34. 8 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/jpush/PushService.java
  35. 368 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/jpush/TagAliasOperatorHelper.java
  36. 49 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/model/AppSpModel.java
  37. 81 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/model/NotificationMessageModel.java
  38. 71 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/oppo/OppoPushUtil.java
  39. 31 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/AppSpPushController.java
  40. 68 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/AppSpPushHandler.java
  41. 57 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/AppSpPushServiceImpl.java
  42. 26 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/IAppSpCallback.java
  43. 14 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/IAppSpService.java
  44. 220 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/Base64Util.java
  45. 57 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/BrandUtil.java
  46. 137 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/CommonUtil.java
  47. 76 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/MessageUtil.java
  48. 494 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/PhoneUtil.java
  49. 113 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/PushInfoUtil.java
  50. 167 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/RSAUtil.java
  51. 134 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/SPUtil.java
  52. 27 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/vivo/VivoPushReceiver.java
  53. 133 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/vivo/VivoPushUtil.java
  54. 184 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/xm/XMPushReceiver.java
  55. 64 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/xm/XMPushUtil.java
  56. 3 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/main/res/values/strings.xml
  57. 17 0
      demo/android/push/SimplePushDemo/ajpushlibrary/src/test/java/com/anji/plus/ajpushlibrary/ExampleUnitTest.java
  58. 1 0
      demo/android/push/SimplePushDemo/app/.gitignore
  59. 14 0
      demo/android/push/SimplePushDemo/app/agconnect-services.json
  60. 69 0
      demo/android/push/SimplePushDemo/app/build.gradle
  61. 36 0
      demo/android/push/SimplePushDemo/app/proguard-rules.pro
  62. BIN
      demo/android/push/SimplePushDemo/app/pushDemo.jks
  63. 26 0
      demo/android/push/SimplePushDemo/app/src/androidTest/java/com/anji/plus/pushdemo/ExampleInstrumentedTest.java
  64. 78 0
      demo/android/push/SimplePushDemo/app/src/main/AndroidManifest.xml
  65. 41 0
      demo/android/push/SimplePushDemo/app/src/main/java/com/anji/plus/pushdemo/SoundUtils.java
  66. 134 0
      demo/android/push/SimplePushDemo/app/src/main/java/com/anji/plus/pushdemo/activity/MainActivity.java
  67. 73 0
      demo/android/push/SimplePushDemo/app/src/main/java/com/anji/plus/pushdemo/appplication/BaseApplication.java
  68. 30 0
      demo/android/push/SimplePushDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  69. 170 0
      demo/android/push/SimplePushDemo/app/src/main/res/drawable/ic_launcher_background.xml
  70. 12 0
      demo/android/push/SimplePushDemo/app/src/main/res/layout/activity_main.xml
  71. 5 0
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  72. 5 0
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  73. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png
  74. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  75. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png
  76. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  77. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xhdpi/app_logo.png
  78. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  79. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  80. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  81. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  82. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  83. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  84. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/raw/error.mp3
  85. BIN
      demo/android/push/SimplePushDemo/app/src/main/res/raw/hua_notice.mp3
  86. 16 0
      demo/android/push/SimplePushDemo/app/src/main/res/values-night/themes.xml
  87. 10 0
      demo/android/push/SimplePushDemo/app/src/main/res/values/colors.xml
  88. 3 0
      demo/android/push/SimplePushDemo/app/src/main/res/values/strings.xml
  89. 16 0
      demo/android/push/SimplePushDemo/app/src/main/res/values/themes.xml
  90. 17 0
      demo/android/push/SimplePushDemo/app/src/test/java/com/anji/plus/pushdemo/ExampleUnitTest.java
  91. 28 0
      demo/android/push/SimplePushDemo/build.gradle
  92. 17 0
      demo/android/push/SimplePushDemo/gradle.properties
  93. 6 0
      demo/android/push/SimplePushDemo/gradle/wrapper/gradle-wrapper.properties
  94. 172 0
      demo/android/push/SimplePushDemo/gradlew
  95. 84 0
      demo/android/push/SimplePushDemo/gradlew.bat
  96. 3 0
      demo/android/push/SimplePushDemo/settings.gradle
  97. 211 0
      demo/android/version/aj_android_appsp/README.md
  98. 1 0
      demo/android/version/aj_android_appsp/app/.gitignore
  99. BIN
      demo/android/version/aj_android_appsp/app/appsp.jks
  100. 0 0
      demo/android/version/aj_android_appsp/app/build.gradle

+ 43 - 0
.gitignore

@@ -0,0 +1,43 @@
+.idea
+.DS_Store
+target
+*.iml
+**/*.log
+dist
+build
+# Compiled class file
+*.class
+
+.DS_Store
+.idea/
+*.iml
+target/
+ 
+# Log file
+*.log
+
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+*.settings
+*.project
+.settings/
+*.settings 
+.classpath 
+.project 
+*.idea 
+logs 
+log
+.factorypath 
+*.mvn 
+.mvn 
+*.iml 
+*.factorypath 
+*.node_modules 
+node_modules 
+.sts4-cache
+

+ 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 [2021] anji-plus.com, All rights reserved.
+   [Official website] https://gitee.com/anji-plus/appsp
+
+   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.

+ 86 - 0
NOTICE

@@ -0,0 +1,86 @@
+   Copyright [2021] anji-plus.com
+   [Official website] https://gitee.com/anji-plus/appsp
+
+   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.
+
+------
+This product has a bundle mybatis-plus
+The source code of mybatis-plus can be found at https://github.com/baomidou/mybatis-plus.
+
+Copyright (c) 2011-${year}, baomidou (jobob@qq.com).
+
+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.
+
+------
+This product has a bundle Flutter
+opyright 2014 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+------
+This product has a bundle vue-router
+The source code of vue-router can be found at https://github.com/vuejs/vue-router.
+
+MIT License
+
+Copyright (c) 2013-present Evan You
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 83 - 1
README.md

@@ -1,2 +1,84 @@
-# appsp
+
+# **在线体验暂时下线!**
+
+
+### 介绍
+<div class="custom-block tip"> <p>AJ-Appsp由 <a href="http://www.anji-plus.com">安吉加加</a> 开源的App服务平台,如常用的 <span align="center" style="color:green">版本管理</span>、<span align="center" style="color:green">推送管理</span>,界面简洁优雅易用,我们提供Android、IOS、Flutter的SDK集成示例,提供管理后台的前后端源码,帮您快速构建企业内App常用功能。
+我们专注做App公共服务,在移动领域为开发者赋能,减少重复造轮子的成本。AppSp完全开源,有详尽的开发和操作文档,我们将不遗余力,丰富AppSp功能,期待大家的使用并提出宝贵意见。
+</p></div>
+
+
+### 在线体验
+**在线体验暂时下线**
+
+### 功能介绍
+* 应用管理:可以新增、删除、修改应用信息
+    * 版本管理:核心功能, 对版本信息进行CRUD,版本更新主要分为历史版本更新、系统版本更新及灰度发布更新。
+	* 推送管理:核心功能,以极光推送兜底,整合华为、小米、oppo、vivo厂商通道,即使应用进程杀死也能收到消息,提高消息抵达率, 
+			    可进行推送测试、透传测试,可查看推送历史,。
+    * 公告管理:公告管理是通过设置公告的持续时间及对应的公共模板,为APP分发不同的公共。
+    * 成员管理:针对应用单独添加用户进入应用群组,还可单独配置每个用户管理该应用的版本、公告等权限信息。
+* 账号管理:添加用户,为整个服务平台创建用户
+* 文档集成:查看服务平台所有使用方法
+* 基础设置:包含系统(iOS、Android)版本基础配置、公告模型基础配置、角色权限分配
+
+### 核心功能
+#### 版本更新 
+* Android应用内apk更新
+* Android应用外链接更新
+* Android应用外H5页面更新
+* IOS应用外App Store更新
+* IOS应用外H5页面更新
+
+#### 推送管理
+* Android&iOS运行时消息推送
+* Android&iOS运行时消息透传
+* Android&iOS进程结束后消息推送
+* Android&iOS推送历史
+
+### 系统交互
+appSp,app,app-api 三者之间的系统交互 
+
+![组件](https://images.gitee.com/uploads/images/2021/1020/151911_249b528c_1600789.png)
+
+### 技术栈
+
+#### 前端
+
+* `npm`: node.js的包管理工具,用于统一管理我们前端项目中需要用到的包、插件、工具、命令等,便于开发和维护。
+* `webpack`: 用于现代 JavaScript 应用程序的_静态模块打包工具
+* `vue-router`:  Vue提供的前端路由工具,利用其我们实现页面的路由控制,局部刷新及按需加载,构建单页应用,实现前后端分离。
+* `element-ui`: 基于MVVM框架Vue开源出来的一套前端ui组件。
+* `html2canvas`: 通过纯JS对浏览器端经行截屏
+* `qrcodejs2`: 二维码生成插件
+
+
+#### 后端
+
+* `Spring Boot2.2.5.RELEASE`: Spring Boot是一款开箱即用框架,让我们的Spring应用变的更轻量化、更快的入门。 在主程序执行main函数就可以运行。你也可以打包你的应用为jar并通过使用java -jar来运行你的Web应用;
+* `Spring Security`: Spring高度可定制的身份验证和访问控制框架。
+* `Mybatis-plus3.3.2`: MyBatis-plus(简称 MP)是一个 MyBatis (opens new window) 的增强工具。
+* `cn.jpush.api:jpush-client:3.4.7`: 极光推送服务端依赖。
+
+#### Android(原生&Flutter)
+
+* `cn.jiguang.sdk:jpush:3.9.0`: 极光推送客户端SDK。
+* `cn.jiguang.sdk:jcore:2.6.0`: 极光推送客户端SDK。
+* `com.huawei.hms:push:4.0.3.301`: 华为HMS推送套件。
+* `MiPush_SDK_Client_3_8_5.jar`: 小米推送客户端SDK。
+* `vivo_pushsdk-v2.9.0.0.aar`: vivo推送客户端SDK。
+* `com.heytap.msp.aar`: oppo推送客户端SDK
+
+#### iOS(原生&Flutter)
+
+* JPush:3.2.4-noidfa: 极光推送客户端SDK。
+* JCore:2.1.4-noidfa: 极光推送客户端SDK。
+
+### 技术支持微信群
+![09](https://images.gitee.com/uploads/images/2021/0810/103007_60cb17c4_1600789.jpeg)   
+
+
+### 开源不易,劳烦各位star ☺
+
+
 

+ 34 - 0
build.sh

@@ -0,0 +1,34 @@
+#!/bin/bash
+
+#判断node.js mvn是否存在
+command -v npm >/dev/null 2>&1 || { echo >&2 "I require node.js v14.16.0+ but it's not installed.  Aborting."; sleep 5; exit 1; }
+command -v mvn >/dev/null 2>&1 || { echo >&2 "I require maven 3.5 + but it's not installed.  Aborting."; sleep 5; exit 1; }
+
+cd `dirname $0`
+BuildDir=`pwd` #工程根目录
+echo $BuildDir
+
+
+echo "build web"
+cd $BuildDir/web
+npm install >/dev/null 2>&1
+npm run build:prod >/dev/null 2>&1
+
+echo "publish web to springboot src/main/resources/static"
+
+mkdir -p $BuildDir/java/sp-app/src/main/resources/static
+mv $BuildDir/web/dist/* $BuildDir/java/sp-app/src/main/resources/static/
+
+
+echo "build springboot"
+cd $BuildDir/java
+mvn clean >/dev/null 2>&1
+mvn package -Dmaven.test.skip=true -Dfile.encoding=UTF-8 >/dev/null 2>&1
+
+echo "zip finish in build dir"
+if [ ! -d "$BuildDir/build" ]; then
+    mkdir $BuildDir/build
+fi
+mv $BuildDir/java/sp-app/target/sp-app-*.zip $BuildDir/build/
+rm -rf $BuildDir/java/sp-app/src/main/resources/static/*;
+

+ 5 - 0
demo/README.md

@@ -0,0 +1,5 @@
+# AJ-Appsp/demo
+
+#### 介绍
+移动服务平台 APP demo集成文件夹 
+

+ 15 - 0
demo/android/push/SimplePushDemo/.gitignore

@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 1 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/.gitignore

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

+ 79 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/build.gradle

@@ -0,0 +1,79 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.huawei.agconnect'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.3"
+
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 29
+
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles 'consumer-rules.pro'
+
+        ndk {
+            //选择要添加的对应 cpu 类型的 .so 库。
+            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
+            // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
+        }
+
+
+    }
+
+    packagingOptions {
+        doNotStrip '*/mips/*.so'
+        doNotStrip '*/mips64/*.so'
+        merge 'classes.jar'
+        merge 'res/values/values.xml'
+        merge 'AndroidManifest.xml'
+        merge 'R.txt'
+    }
+
+    buildTypes {
+
+        debug {
+        }
+
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    sourceSets {
+        main {
+            jniLibs.srcDir 'libs'
+            jni.srcDirs = []    //disable automatic ndk-build
+        }
+    }
+    compileOptions {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+
+    // push kit
+    implementation 'com.huawei.hms:push:4.0.3.301'
+//
+    implementation files('libs\\MiPush_SDK_Client_3_8_5.jar')
+    implementation files('libs\\com.heytap.msp.aar')
+    implementation files('libs\\vivo_pushsdk-v2.9.0.0.aar')
+
+    implementation 'cn.jiguang.sdk:jpush:3.9.0'  // 此处以JPush 3.9.0 版本为例。
+    implementation 'cn.jiguang.sdk:jcore:2.6.0'  // 此处以JCore 2.6.0 版本为例。
+
+}

+ 0 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/consumer-rules.pro


BIN
demo/android/push/SimplePushDemo/ajpushlibrary/libs/com.heytap.msp.aar


BIN
demo/android/push/SimplePushDemo/ajpushlibrary/libs/vivo_pushsdk-v2.9.0.0.aar


+ 21 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 27 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/androidTest/java/com/anji/plus/ajpushlibrary/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.anji.plus.ajpushlibrary;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        assertEquals("com.anji.plus.ajpushlibrary.test", appContext.getPackageName());
+    }
+}

+ 121 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/AndroidManifest.xml

@@ -0,0 +1,121 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.anji.plus.ajpushlibrary">
+    <!-- 这个权限用于进行网络定位 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <!--小米所需要添加的权限-->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+
+    <permission
+        android:name="${JPUSH_PKGNAME}.permission.MIPUSH_RECEIVE"
+        android:protectionLevel="signature" />
+    <uses-permission android:name="${JPUSH_PKGNAME}.permission.MIPUSH_RECEIVE" /><!--这里com.xiaomi.mipushdemo改成app的包名-->
+
+    <application>
+        <!--华为-->
+        <service
+            android:name=".hw.HWPushService"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.huawei.push.action.MESSAGING_EVENT" />
+            </intent-filter>
+        </service>
+
+        <!--小米-->
+        <service
+            android:name="com.xiaomi.push.service.XMJobService"
+            android:enabled="true"
+            android:exported="false"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:process=":pushservice" />
+        <service
+            android:name="com.xiaomi.push.service.XMPushService"
+            android:enabled="true"
+            android:process=":pushservice" />
+        <service
+            android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
+            android:enabled="true"
+            android:exported="true" />
+        <service
+            android:name="com.xiaomi.mipush.sdk.MessageHandleService"
+            android:enabled="true" />
+
+        <receiver
+            android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name="com.xiaomi.push.service.receivers.PingReceiver"
+            android:exported="false"
+            android:process=":pushservice">
+            <intent-filter>
+                <action android:name="com.xiaomi.push.PING_TIMER" />
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name=".xm.XMPushReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.xiaomi.mipush.ERROR" />
+            </intent-filter>
+        </receiver>
+
+        <!--vivo-->
+        <!-- Vivo Push需要配置的service、activity -->
+        <service
+            android:name="com.vivo.push.sdk.service.CommandClientService"
+            android:exported="true" />
+        <!-- push应用定义消息receiver声明 -->
+        <receiver
+            android:name=".vivo.VivoPushReceiver"
+            android:exported="false">
+            <intent-filter>
+                <!-- 接收push消息 -->
+                <action android:name="com.vivo.pushclient.action.RECEIVE" />
+            </intent-filter>
+        </receiver>
+
+        <!--极光-->
+        <!-- User defined.  For test only  用户自定义接收消息器,3.0.7开始支持,目前新tag/alias接口设置结果会在该广播接收器对应的方法中回调 -->
+        <!-- since 3.3.0 接收JPush相关事件 -->
+        <receiver
+            android:name=".jpush.PushMessageReceiver"
+            android:enabled="true"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
+
+                <category android:name="${applicationId}" />
+            </intent-filter>
+        </receiver>
+        <!-- since 3.3.0 Required SDK 核心功能 -->
+        <!-- 可配置android:process参数将PushService放在其他进程中 -->
+        <!-- User defined.  For test only 继承自cn.jpush.android.service.JCommonService -->
+        <service
+            android:name=".jpush.PushService"
+            android:exported="false"
+            android:process=":pushcore">
+            <intent-filter>
+                <action android:name="cn.jiguang.user.service.action" />
+            </intent-filter>
+        </service>
+
+
+    </application>
+
+</manifest>

+ 254 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/AppSpPushConfig.java

@@ -0,0 +1,254 @@
+package com.anji.plus.ajpushlibrary;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.anji.plus.ajpushlibrary.http.AppSpPushRequestUrl;
+import com.anji.plus.ajpushlibrary.hw.HWPushUtil;
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+import com.anji.plus.ajpushlibrary.model.NotificationMessageModel;
+import com.anji.plus.ajpushlibrary.oppo.OppoPushUtil;
+import com.anji.plus.ajpushlibrary.service.AppSpPushController;
+import com.anji.plus.ajpushlibrary.service.IAppSpCallback;
+import com.anji.plus.ajpushlibrary.util.BrandUtil;
+import com.anji.plus.ajpushlibrary.util.PushInfoUtil;
+import com.anji.plus.ajpushlibrary.vivo.VivoPushUtil;
+import com.heytap.msp.push.callback.ICallBackResultService;
+import com.vivo.push.PushClient;
+import com.xiaomi.mipush.sdk.MiPushClient;
+
+import java.util.List;
+
+import cn.jpush.android.api.JPushInterface;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 1,入口类,初始化,获取接口回调
+ * 2,单例
+ * 3,为了集成的便携性和兼容性,SDK没有引入第三方jar包,
+ * 所以可以看到,json的解析,也是采用的原始方式
+ * 4,服务端校验数据完整性,加入sign参数,采用RSA加密方式,前端和后端约定公钥
+ * </p>
+ */
+public final class AppSpPushConfig {
+
+    private static volatile AppSpPushConfig appSpPushConfig;
+    private Context context;
+    public static String appKey;
+    public static String secretKey;
+    public static String requestUrl = "";//请求自家服务器的接口
+
+
+    private ICallBackResultService mPushCallback = new ICallBackResultService() {
+        @Override
+        public void onRegister(int responseCode, final String registerID) {
+            //注册的结果,如果注册成功,registerID就是客户端的唯一身份标识
+            if (responseCode == 0) {
+                AppSpPushConstant.pushToken = registerID;
+                AppSpPushConstant.brandType = AppSpPushConstant.OPPO;
+                AppSpPushLog.d("Oppo registerID ==》 " + registerID);
+                AppSpPushConfig.getInstance().sendRegTokenToServer(new IAppSpCallback() {
+                    @Override
+                    public void pushInfo(AppSpModel<String> appSpModel) {
+                        Log.i("注册成功", registerID + "---" + appSpModel.getRepData());
+                    }
+
+                    @Override
+                    public void error(String code, String msg) {
+                        Log.i("注册成功", registerID + "---" + msg);
+                    }
+
+                });
+                AppSpPushLog.e("注册成功, registerId:" + registerID);
+            } else {
+                AppSpPushLog.e("注册失败," + "code=" + responseCode + ",msg=" + registerID);
+            }
+        }
+
+        @Override
+        public void onUnRegister(int code) {
+            //反注册的结果
+            if (code == 0) {
+                AppSpPushLog.e("注销成功, code:" + code);
+            } else {
+                AppSpPushLog.e("注销失败, code:" + code);
+            }
+        }
+
+        @Override
+        public void onGetPushStatus(final int code, int status) {
+            //获取当前的push状态返回,根据返回码判断当前的push状态
+            if (code == 0 && status == 0) {
+                AppSpPushLog.e("Push状态正常," + "code=" + code + ",status=" + status);
+            } else {
+                AppSpPushLog.e("Push状态错误," + "code=" + code + ",status=" + status);
+            }
+        }
+
+        @Override
+        public void onGetNotificationStatus(final int code, final int status) {
+            //获取当前通知栏状态
+            if (code == 0 && status == 0) {
+                AppSpPushLog.e("通知状态正常," + "code=" + code + ",status=" + status);
+            } else {
+                AppSpPushLog.e("通知状态错误," + "code=" + code + ",status=" + status);
+            }
+        }
+
+        @Override
+        public void onSetPushTime(final int code, final String s) {
+            //获取设置推送时间的执行结果
+            AppSpPushLog.e("SetPushTime," + "code=" + code + ",result=" + s);
+        }
+
+    };
+
+    private AppSpPushConfig() {
+    }
+
+    public static synchronized AppSpPushConfig getInstance() {
+        if (appSpPushConfig == null) {
+            appSpPushConfig = new AppSpPushConfig();
+        }
+        return appSpPushConfig;
+    }
+
+    /**
+     * @param host 请求的基本地址
+     */
+    public AppSpPushConfig setHost(String host) {
+        AppSpPushRequestUrl.Host = host;
+        return appSpPushConfig;
+    }
+
+    /**
+     * 设置是否打开debug开关
+     *
+     * @param debug true表示打开debug
+     */
+    public AppSpPushConfig setDebuggable(boolean debug) {
+        AppSpPushLog.setDebugged(debug);
+        return appSpPushConfig;
+    }
+
+    /**
+     * 初始化
+     *
+     * @param context 可以是 activity/fragment/view
+     * @param appKey  用于标识哪个APP,唯一标识
+     * @param path    是用户自己公司的接口
+     */
+    public void init(Context context, String appKey, String secretKey, String path) {
+        requestUrl = path;
+        if (TextUtils.isEmpty(appKey)) {
+            AppSpPushLog.e("appKey must be not Empty !");
+            return;
+        }
+        if (TextUtils.isEmpty(path)) {
+            AppSpPushLog.e("request host must be not Empty !");
+            return;
+        }
+        if (TextUtils.isEmpty(secretKey)) {
+            AppSpPushLog.e("appspSecretKey must be not Empty !");
+            return;
+        }
+        this.context = context;
+        this.appKey = appKey;
+        this.secretKey = secretKey;
+
+        //传递在各大平台注册后的信息到自家公司服务器
+        pushInit();
+
+    }
+
+    /**
+     * 根据手机品牌初始化推送
+     */
+    public void pushInit() {
+
+        // 设置开启日志,发布时请关闭日志
+        JPushInterface.setDebugMode(true);
+        // 极光推送初始化
+        JPushInterface.init(context);
+        if (BrandUtil.getInstance(context).isHuawei()) {
+            //获取token并发送给后端
+            AppSpPushConstant.brandType = AppSpPushConstant.HW;
+//            new HWPushUtil(context).getToken();
+            new HWPushUtil(context).getToken();
+        } else if (BrandUtil.getInstance(context).isXiaomi()) {
+            // 注册push服务,注册成功后会向XMPushReceiver发送广播
+            AppSpPushConstant.brandType = AppSpPushConstant.XM;
+            if (shouldInit()) {
+                MiPushClient.registerPush(context, AppSpPushConstant.xmAppId, AppSpPushConstant.xmAppKey);
+            }
+        } else if (BrandUtil.getInstance(context).isOPPO()) {
+            AppSpPushConstant.brandType = AppSpPushConstant.OPPO;
+            try {
+                OppoPushUtil oppoPushUtil = new OppoPushUtil(context);
+                oppoPushUtil.init();
+                if (oppoPushUtil.isSupportPush()) {
+                    oppoPushUtil.register(mPushCallback);//setPushCallback接口也可设置callback
+                    oppoPushUtil.requestNotificationPermission();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        } else if (BrandUtil.getInstance(context).isVIVO()) {
+            AppSpPushConstant.brandType = AppSpPushConstant.VIVO;
+            PushClient.getInstance(context).initialize();
+            new VivoPushUtil(context).bind();
+        } else {
+            AppSpPushConstant.brandType = AppSpPushConstant.OTHER;
+        }
+
+    }
+
+    //初始化推送服务
+    private boolean shouldInit() {
+        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
+        String mainProcessName = context.getPackageName();
+        int myPid = Process.myPid();
+        for (ActivityManager.RunningAppProcessInfo info : processInfos) {
+            if (info.pid == myPid && mainProcessName.equals(info.processName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //点击通知栏后获取信息
+    public NotificationMessageModel getNotifyMessage(Intent intent) {
+        //获取数据
+        if (BrandUtil.getInstance(context).isHuawei()) {
+            return PushInfoUtil.getHWIntentData(intent);
+        } else if (BrandUtil.getInstance(context).isXiaomi()) {
+            return PushInfoUtil.getXMIntentData(intent);
+        } else if (BrandUtil.getInstance(context).isVIVO()) {
+            return PushInfoUtil.getVivoIntentData(intent);
+        } else if (BrandUtil.getInstance(context).isOPPO()) {
+            return PushInfoUtil.getOppoIntentData(intent);
+        } else {
+            return PushInfoUtil.getJPushIntentData(intent);
+        }
+    }
+
+    /**
+     * push注册成功后将数据传给服务器
+     *
+     * @param iAppSpCallback
+     */
+    public void sendRegTokenToServer(IAppSpCallback iAppSpCallback) {
+        AppSpPushController appSpController = new AppSpPushController(context, appKey);
+        appSpController.putPushInfo(iAppSpCallback);
+    }
+
+}

+ 40 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/AppSpPushConstant.java

@@ -0,0 +1,40 @@
+package com.anji.plus.ajpushlibrary;
+
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.d
+ * <p>
+ * 在平台上开启服务成功后获取AppId、Appkey和AppSecret
+ * </p>
+ */
+
+public class AppSpPushConstant {
+    public static final String HNOTIFICATIONMESSAGE = "notificationMessage";
+    public static final String OTHER = "0";
+    public static final String HW = "1";
+    public static final String XM = "2";
+    public static final String OPPO = "3";
+    public static final String VIVO = "4";
+
+    public static final String MESSAGE_RECEIVED_ACTION = "com.anji.plus.ajpushlibrary.PushMessageReceiver";
+    public static final String KEY_TITLE = "title";
+    public static final String KEY_MESSAGE = "message";
+    public static final String KEY_EXTRAS = "extras";
+
+    public static String jPushRegId = "jPushRegId";//极光唯一标识,regID,当做存储的key
+
+    public static String brandType = "";//手机品牌
+    public static String packageName = "";//包名
+    public static String pushToken = "";//四大厂商注册推送服务成功后生成的唯一标识token
+
+    public static String appspHost = "";
+    public static String appspAppKey = "a1b49e089de04915b124ebdec715761d";
+    public static String appspSecretKey = "e7ded5c2d4934d6683e72f6d9fde2b4d";
+    public static String oppoAppKey = "c160669462604212962064ffa2df36af";
+    public static String oppoAppSecret = "b77b1509f99043e3b695795366e929ef";
+    public static String xmAppId = "2882303761518880022";
+    public static String xmAppKey = "5161888025022";
+}

+ 45 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/AppSpPushLog.java

@@ -0,0 +1,45 @@
+package com.anji.plus.ajpushlibrary;
+
+import android.util.Log;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.d
+ * <p>
+ * 日志输出
+ * </p>
+ */
+public class AppSpPushLog {
+    private static final String Tag = "APP-SP-PUSH";
+    private static boolean Debug = true;
+
+    public static void setDebugged(boolean debug) {
+        Debug = debug;
+    }
+
+    public static void i(Object txt) {
+        if (Debug && txt != null) {
+            Log.i(Tag, txt.toString());
+        }
+    }
+
+    public static void d(Object txt) {
+        if (Debug && txt != null) {
+            Log.d(Tag, txt.toString());
+        }
+    }
+
+    public static void w(Object txt) {
+        if (Debug && txt != null) {
+            Log.w(Tag, txt.toString());
+        }
+    }
+
+    public static void e(Object txt) {
+        if (Debug && txt != null) {
+            Log.e(Tag, txt.toString());
+        }
+    }
+}

+ 15 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/ActionType.java

@@ -0,0 +1,15 @@
+package com.anji.plus.ajpushlibrary.base;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * Appsp-Push 对消息处理的方式,点击或者不处理
+ * </p>
+ */
+public class ActionType {
+    public static final int NONE = 0x01;
+    public static final int CLICK = 0x02;
+}

+ 21 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/AppSpPushBaseController.java

@@ -0,0 +1,21 @@
+package com.anji.plus.ajpushlibrary.base;
+
+import android.content.Context;
+
+public class AppSpPushBaseController {
+    protected Context context;
+    protected String appKey;
+
+    public AppSpPushBaseController(Context mContext, String appKey) {
+        this.context = mContext;
+        this.appKey = appKey;
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+}

+ 18 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/AppSpPushBaseHandler.java

@@ -0,0 +1,18 @@
+package com.anji.plus.ajpushlibrary.base;
+
+public class AppSpPushBaseHandler {
+    protected String getStringElement(Object object) {
+        if (object instanceof String) {
+            return (String) object;
+        }
+        return "";
+    }
+
+    protected Boolean getBooleanElement(Object object) {
+        if (object instanceof Boolean) {
+            return (Boolean) object;
+        }
+        return false;
+    }
+
+}

+ 53 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/AppSpPushBaseRequest.java

@@ -0,0 +1,53 @@
+package com.anji.plus.ajpushlibrary.base;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.http.AppSpPushPostData;
+import com.anji.plus.ajpushlibrary.util.PhoneUtil;
+import com.anji.plus.ajpushlibrary.util.SPUtil;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * Appsp
+ * </p>
+ */
+public class AppSpPushBaseRequest {
+    protected Context context;
+    protected String appKey;
+
+    public AppSpPushBaseRequest(Context context, String appKey) {
+        this.context = context;
+        this.appKey = appKey;
+    }
+
+    protected AppSpPushPostData getPostEncryptData() {
+        AppSpPushPostData appSpPushPostData = new AppSpPushPostData();
+        String regId = (String) SPUtil.get(context, AppSpPushConstant.jPushRegId, "");
+//        String regId = AppSpPushConstant.jPushRegId;
+        Log.d("dengmin"," getPostEncryptData regId "+regId);
+        try {
+            String deviceId = PhoneUtil.getDeviceId(context);
+
+            Log.i("dengmin", deviceId);
+            appSpPushPostData.put("appKey", appKey);
+            appSpPushPostData.put("manuToken", AppSpPushConstant.pushToken);
+            appSpPushPostData.put("deviceType", AppSpPushConstant.brandType);
+            appSpPushPostData.put("registrationId", regId);
+            appSpPushPostData.put("deviceId", deviceId);
+            appSpPushPostData.put("alias", "");
+            appSpPushPostData.put("brand", Build.MODEL);
+            appSpPushPostData.put("osVersion", Build.VERSION.RELEASE);
+        } catch (Exception e) {
+
+        }
+
+        return appSpPushPostData;
+    }
+}

+ 18 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/base/BrandType.java

@@ -0,0 +1,18 @@
+package com.anji.plus.ajpushlibrary.base;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * Appsp-Push 支持的手机类型
+ * </p>
+ */
+public class BrandType {
+    public static final int HUAWEI = 0x01;
+    public static final int XIAOMI = 0x02;
+    public static final int OPPO = 0x03;
+    public static final int VIVO = 0x04;
+    public static final int OTHER = 0x05;
+}

+ 28 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushCallBack.java

@@ -0,0 +1,28 @@
+package com.anji.plus.ajpushlibrary.http;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 请求结果的回调,成功会把数据全部返回,失败把状态码和错误信息返回
+ * </p>
+ */
+public interface AppSpPushCallBack {
+    /**
+     * 请求成功且业务代码正常
+     *
+     * @param data 返回处理后数据
+     */
+    void onSuccess(String data);
+
+    /**
+     * 在请求失败或者业务代码异常时调用
+     *
+     * @param code 状态码,分为业务码和网络状态码
+     *             业务码分为
+     * @param msg  错误日志
+     */
+    void onError(String code, String msg);
+}

+ 231 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushHttpClient.java

@@ -0,0 +1,231 @@
+package com.anji.plus.ajpushlibrary.http;
+
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 请求处理类,用基本的HttpURLConnection
+ * 避免依赖其他library
+ * 我们已经处理了https的握手场景
+ * </p>
+ */
+public class AppSpPushHttpClient {
+    private static final String TAG = AppSpPushHttpClient.class.getSimpleName();
+
+    public void request(String url, final AppSpPushCallBack appSpPushCallBack) {
+        request(url, null, AppSpPushRequestMethod.POST, appSpPushCallBack);
+    }
+
+    public void request(String url, Object data, final AppSpPushCallBack appSpPushCallBack) {
+        request(url, data, AppSpPushRequestMethod.POST, appSpPushCallBack);
+    }
+
+    public void request(String url, Object data, AppSpPushRequestMethod appSpPushRequestMethod, final AppSpPushCallBack appSpPushCallBack) {
+        if (appSpPushCallBack == null) {
+            return;
+        }
+        new RequestWrapper(url, data, appSpPushRequestMethod, appSpPushCallBack).requestWrapper();
+    }
+
+    class RequestWrapper {
+        private String baseUrl;
+        private Object params;
+        private AppSpPushRequestMethod appSpPushRequestMethod;
+        private AppSpPushCallBack appSpPushCallBack;
+
+        public RequestWrapper(String baseUrl, Object params, AppSpPushRequestMethod appSpPushRequestMethod, AppSpPushCallBack appSpPushCallBack) {
+            this.baseUrl = baseUrl;
+            this.params = params;
+            this.appSpPushRequestMethod = appSpPushRequestMethod;
+            this.appSpPushCallBack = appSpPushCallBack;
+        }
+
+
+        Thread thread = new Thread() {
+            @Override
+            public void run() {
+                requestSync();
+            }
+        };
+
+        private void requestSync() {
+            AppSpPushLog.d("请求地址 " + baseUrl);
+            try {
+                HttpURLConnection urlConn = getConnection();
+
+                if (urlConn == null) {
+                    return;
+                }
+                String paramsStr = params.toString();
+                AppSpPushLog.d("请求内容" + paramsStr);
+                // 请求的参数转换为byte数组
+                byte[] postData = paramsStr.getBytes();
+                // 发送请求参数
+                DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream());
+                dos.write(postData);
+                dos.flush();
+                dos.close();
+                // 判断请求成功
+                if (urlConn.getResponseCode() == 200) {
+                    // 获取返回的数据
+                    String result = streamToString(urlConn.getInputStream());
+                    String respData = result;
+                    AppSpPushLog.d("请求成功,结果为: " + result);
+                    //appKey为空
+                    requestSuccess(respData);
+                } else {
+                    //请求失败,将请求状态码返回
+                    fail(String.valueOf(urlConn.getResponseCode()), urlConn.getResponseMessage());
+                    AppSpPushLog.d("请求失败 ");
+                }
+                // 关闭连接
+                urlConn.disconnect();
+            } catch (Exception e) {
+                AppSpPushLog.e("请求异常 " + e.toString());
+                fail(AppSpPushRespCode.REQUEST_EXCEPTION, e.toString());
+            }
+        }
+
+        public void requestWrapper() {
+            thread.start();
+        }
+
+        private HttpURLConnection getConnection() {
+            HttpURLConnection urlConn = null;
+            try {
+                // 新建一个URL对象
+                URL url = new URL(baseUrl);
+                trustAllHosts();
+                // 打开一个HttpURLConnection连接
+                HttpsURLConnection https;
+                if (url.getProtocol().toLowerCase().equals("https")) {
+                    https = (HttpsURLConnection) url.openConnection();
+                    https.setHostnameVerifier(DO_NOT_VERIFY);
+                    urlConn = https;
+                } else {
+                    urlConn = (HttpURLConnection) url.openConnection();
+                }
+                // 设置连接超时时间
+                urlConn.setConnectTimeout(10 * 1000);
+                //设置从主机读取数据超时
+                urlConn.setReadTimeout(10 * 1000);
+                // Post请求必须设置允许输出 默认false
+                urlConn.setDoOutput(true);
+                //设置请求允许输入 默认是true
+                urlConn.setDoInput(true);
+                // Post请求不能使用缓存
+                urlConn.setUseCaches(false);
+                String method = "POST";
+                // 默认为Post请求
+                if (AppSpPushRequestMethod.POST == appSpPushRequestMethod) {
+                    method = "POST";
+                } else if (AppSpPushRequestMethod.GET == appSpPushRequestMethod) {
+                    method = "GET";
+                } else if (AppSpPushRequestMethod.PUT == appSpPushRequestMethod) {
+                    method = "PUT";
+                } else if (AppSpPushRequestMethod.DELETE == appSpPushRequestMethod) {
+                    method = "DELETE";
+                }
+                urlConn.setRequestMethod(method);
+                // 配置请求Content-Type
+                urlConn.setRequestProperty("Content-Type", "application/json");
+                // 开始连接
+                urlConn.connect();
+            } catch (Exception e) {
+
+            }
+            return urlConn;
+        }
+
+        private void fail(final String code, final String msg) {
+            appSpPushCallBack.onError(code, msg);
+        }
+
+        private void requestSuccess(String data) {
+            appSpPushCallBack.onSuccess(data);
+        }
+
+        final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
+
+            public boolean verify(String hostname, SSLSession session) {
+                return true;
+            }
+        };
+
+        private void trustAllHosts() {
+            // Create a trust manager that does not validate certificate chains
+            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+
+                public X509Certificate[] getAcceptedIssuers() {
+                    return new X509Certificate[]{};
+                }
+
+                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+                    AppSpPushLog.d("checkClientTrusted");
+                }
+
+                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+                    AppSpPushLog.d("checkServerTrusted");
+                }
+            }};
+
+            // Install the all-trusting trust manager
+            try {
+                SSLContext sc = SSLContext.getInstance("TLS");
+                sc.init(null, trustAllCerts, new java.security.SecureRandom());
+                HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        /**
+         * 将输入流转换成字符串
+         *
+         * @param is 从网络获取的输入流
+         * @return
+         */
+        public String streamToString(InputStream is) {
+            try {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                byte[] buffer = new byte[1024];
+                int len = 0;
+                while ((len = is.read(buffer)) != -1) {
+                    baos.write(buffer, 0, len);
+                }
+                baos.close();
+                is.close();
+                byte[] byteArray = baos.toByteArray();
+                return new String(byteArray);
+            } catch (Exception e) {
+                AppSpPushLog.e("从网络获取的输入流 请求异常 " + e.toString());
+                return null;
+            }
+        }
+    }
+
+
+}
+
+
+

+ 26 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushPostData.java

@@ -0,0 +1,26 @@
+package com.anji.plus.ajpushlibrary.http;
+
+import org.json.JSONObject;
+
+import java.util.LinkedHashMap;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 请求基础实体
+ * </p>
+ */
+public class AppSpPushPostData extends LinkedHashMap {
+    private static final long serialVersionUID = -3918611306392239969L;
+
+    @Override
+    public String toString() {
+        String regLeft = "\"" + "\\{";
+        String regRight = "\\}" + "\"";
+        return new JSONObject(this).toString().replaceAll("\\\\", "")
+                .replaceAll(regLeft, "{").replaceAll(regRight, "}");
+    }
+}

+ 8 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushRequestMethod.java

@@ -0,0 +1,8 @@
+package com.anji.plus.ajpushlibrary.http;
+
+public enum AppSpPushRequestMethod {
+    POST,
+    GET,
+    DELETE,
+    PUT
+}

+ 17 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushRequestUrl.java

@@ -0,0 +1,17 @@
+package com.anji.plus.ajpushlibrary.http;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 接口地址
+ * </p>
+ */
+
+public class AppSpPushRequestUrl {
+    //默认基础地址,可改变
+    public static String Host = "http://uatappsp.anji-plus.com";//uat环境
+    public static final String putPushInfo = "/sp/push/init";
+}

+ 14 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/http/AppSpPushRespCode.java

@@ -0,0 +1,14 @@
+package com.anji.plus.ajpushlibrary.http;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 请求返回状态码,0000为正常
+ * </p>
+ */
+public class AppSpPushRespCode {
+    public static final String REQUEST_EXCEPTION = "1001";//请求异常
+}

+ 168 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/hw/HWPushService.java

@@ -0,0 +1,168 @@
+/*
+ *  Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+ *
+ *  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.anji.plus.ajpushlibrary.hw;
+
+import android.content.Intent;
+import android.text.TextUtils;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.huawei.hms.push.HmsMessageService;
+import com.huawei.hms.push.RemoteMessage;
+import com.huawei.hms.push.SendException;
+
+import java.util.Arrays;
+
+public class HWPushService extends HmsMessageService {
+    private final static String CODELABS_ACTION = "com.huawei.codelabpush.action";
+    /**
+     * When an app calls the getToken method to apply for a token from the server,
+     * if the server does not return the token during current method calling, the server can return the token through this method later.
+     * This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
+     * 当getToken()返回为空的时候,会自动通过onNewToken()重新获取Token
+     *
+     * @param token token
+     */
+    @Override
+    public void onNewToken(String token) {
+        AppSpPushLog.i("received refresh token:" + token);
+        // send the token to your app server.
+        if (!TextUtils.isEmpty(token)) {
+            // This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
+            refreshedTokenToServer(token);
+        }
+
+        Intent intent = new Intent();
+        intent.setAction(CODELABS_ACTION);
+        intent.putExtra("method", "onNewToken");
+        intent.putExtra("msg", "onNewToken called, token: " + token);
+
+        sendBroadcast(intent);
+    }
+
+    private void refreshedTokenToServer(String token) {
+        AppSpPushConstant.pushToken = token;
+        AppSpPushLog.i("sending token to server. token:" + token);
+    }
+
+    /**
+     * This method is used to receive downstream data messages.
+     * This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
+     * 订阅主题消息、服务主动推送的透传消息(包括基于前台应用的通知弹出功能中设置通知消息由应用处理)
+     *
+     * @param message RemoteMessage
+     */
+    @Override
+    public void onMessageReceived(RemoteMessage message) {
+        AppSpPushLog.i("onMessageReceived is called");
+
+        //判断消息是否为空setDefaultPushNotificationBuilder
+        if (message == null) {
+            AppSpPushLog.e("Received message entity is null!");
+            return;
+        }
+
+        //获取消息内容
+        AppSpPushLog.i("getCollapseKey: " + message.getCollapseKey()
+                + "\n getData: " + message.getData()
+                + "\n getFrom: " + message.getFrom()
+                + "\n getTo: " + message.getTo()
+                + "\n getMessageId: " + message.getMessageId()
+                + "\n getSendTime: " + message.getSentTime()
+                + "\n getDataMap: " + message.getDataOfMap()
+                + "\n getMessageType: " + message.getMessageType()
+                + "\n getTtl: " + message.getTtl()
+                + "\n getToken: " + message.getToken());
+
+
+        // 获取消息内容
+        RemoteMessage.Notification notification = message.getNotification();
+        if (notification != null) {
+            AppSpPushLog.i("\n getImageUrl: " + notification.getImageUrl()
+                    + "\n getTitle: " + notification.getTitle()
+                    + "\n getTitleLocalizationKey: " + notification.getTitleLocalizationKey()
+                    + "\n getTitleLocalizationArgs: " + Arrays.toString(notification.getTitleLocalizationArgs())
+                    + "\n getBody: " + notification.getBody()
+                    + "\n getBodyLocalizationKey: " + notification.getBodyLocalizationKey()
+                    + "\n getBodyLocalizationArgs: " + Arrays.toString(notification.getBodyLocalizationArgs())
+                    + "\n getIcon: " + notification.getIcon()
+                    + "\n getSound: " + notification.getSound()
+                    + "\n getTag: " + notification.getTag()
+                    + "\n getColor: " + notification.getColor()
+                    + "\n getClickAction: " + notification.getClickAction()
+                    + "\n getChannelId: " + notification.getChannelId()
+                    + "\n getLink: " + notification.getLink()
+                    + "\n getNotifyId: " + notification.getNotifyId());
+        }
+
+        Intent intent = new Intent();
+        intent.setAction(CODELABS_ACTION);
+        intent.putExtra("method", "onMessageReceived");
+        intent.putExtra("msg", "onMessageReceived called, message id:" + message.getMessageId() + ", payload data:"
+                + message.getData());
+
+        sendBroadcast(intent);
+
+        Boolean judgeWhetherIn10s = false;
+
+        // 如果消息在10秒内没有处理,需要您自己创建新任务处理
+        if (judgeWhetherIn10s) {
+            startWorkManagerJob(message);
+        } else {
+            // 10秒内处理消息
+            processWithin10s(message);
+        }
+    }
+
+    private void startWorkManagerJob(RemoteMessage message) {
+        AppSpPushLog.d("Start new Job processing.");
+    }
+
+    private void processWithin10s(RemoteMessage message) {
+        AppSpPushLog.d("Processing now.");
+    }
+
+    @Override
+    public void onMessageSent(String msgId) {
+        AppSpPushLog.i("onMessageSent called, Message id:" + msgId);
+        Intent intent = new Intent();
+        intent.setAction(CODELABS_ACTION);
+        intent.putExtra("method", "onMessageSent");
+        intent.putExtra("msg", "onMessageSent called, Message id:" + msgId);
+
+        sendBroadcast(intent);
+    }
+
+    @Override
+    public void onSendError(String msgId, Exception exception) {
+        AppSpPushLog.i("onSendError called, message id:" + msgId + ", ErrCode:"
+                + ((SendException) exception).getErrorCode() + ", description:" + exception.getMessage());
+
+        Intent intent = new Intent();
+        intent.setAction(CODELABS_ACTION);
+        intent.putExtra("method", "onSendError");
+        intent.putExtra("msg", "onSendError called, message id:" + msgId + ", ErrCode:"
+                + ((SendException) exception).getErrorCode() + ", description:" + exception.getMessage());
+
+        sendBroadcast(intent);
+    }
+
+    @Override
+    public void onTokenError(Exception e) {
+        super.onTokenError(e);
+    }
+}

+ 160 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/hw/HWPushUtil.java

@@ -0,0 +1,160 @@
+package com.anji.plus.ajpushlibrary.hw;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+import com.anji.plus.ajpushlibrary.service.IAppSpCallback;
+import com.huawei.agconnect.config.AGConnectServicesConfig;
+import com.huawei.hmf.tasks.OnCompleteListener;
+import com.huawei.hmf.tasks.Task;
+import com.huawei.hms.aaid.HmsInstanceId;
+import com.huawei.hms.common.ApiException;
+import com.huawei.hms.push.HmsMessaging;
+
+public class HWPushUtil {
+    private Context context;
+
+    public HWPushUtil(Context context) {
+        this.context = context;
+    }
+
+    //获取token
+    public void getToken() {
+        // 创建一个新线程
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    // 从agconnect-service.json文件中读取appId
+                    String appId = AGConnectServicesConfig.fromContext(context).getString("client/app_id");
+
+                    // 输入token标识"HCM"
+                    String tokenScope = "HCM";
+                    String token = HmsInstanceId.getInstance(context).getToken(appId, tokenScope);
+                    AppSpPushLog.i("get token: " + token);
+
+                    // 判断token是否为空
+                    if (!TextUtils.isEmpty(token)) {
+                        AppSpPushConstant.pushToken = token;
+                        //将获取的pushToken上传给服务器
+                        AppSpPushConfig.getInstance().sendRegTokenToServer(new IAppSpCallback() {
+                            @Override
+                            public void pushInfo(AppSpModel<String> appSpModel) {
+
+                            }
+
+                            @Override
+                            public void error(String code, String msg) {
+
+                            }
+                        });
+                    }
+                } catch (ApiException e) {
+                    AppSpPushLog.e("get token failed, " + e);
+                }
+            }
+        }.start();
+    }
+
+    //注销token
+    public void deleteToken() {
+        // 创建一个新线程
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    // 从agconnect-service.json文件中读取appId
+                    String appId = AGConnectServicesConfig.fromContext(context).getString("client/app_id");
+
+                    // 输入token标识"HCM"
+                    String tokenScope = "HCM";
+
+                    // 注销Token
+                    HmsInstanceId.getInstance(context).deleteToken(appId, tokenScope);
+                    AppSpPushLog.i("token deleted successfully");
+                } catch (ApiException e) {
+                    AppSpPushLog.e("deleteToken failed." + e);
+                }
+            }
+        }.start();
+    }
+
+    //显示通知栏消息(系统默认是允许显示通知栏消息)
+    public void TurnOnPush() {
+        // 设置显示通知栏消息
+        HmsMessaging.getInstance(context).turnOnPush().addOnCompleteListener(new OnCompleteListener<Void>() {
+            @Override
+            public void onComplete(Task<Void> task) {
+                // 获取结果
+                if (task.isSuccessful()) {
+                    AppSpPushLog.i("turnOnPush Complete");
+                } else {
+                    AppSpPushLog.e("turnOnPush failed: ret=" + task.getException().getMessage());
+                }
+            }
+        });
+    }
+
+    //隐藏通知栏消息
+    public void turnOffPush() {
+        HmsMessaging.getInstance(context).turnOffPush().addOnCompleteListener(new OnCompleteListener<Void>() {
+            @Override
+            public void onComplete(Task<Void> task) {
+                // 获取结果
+                if (task.isSuccessful()) {
+                    AppSpPushLog.i("turnOffPush Complete");
+                } else {
+                    AppSpPushLog.e("turnOffPush failed: ret=" + task.getException().getMessage());
+                }
+            }
+        });
+    }
+
+    //订阅主题
+    public void subscribe(String topic) {
+        try {
+            // 主题订阅
+            HmsMessaging.getInstance(context).subscribe(topic)
+                    .addOnCompleteListener(new OnCompleteListener<Void>() {
+                        @Override
+                        public void onComplete(Task<Void> task) {
+                            // 获取主题订阅的结果
+                            if (task.isSuccessful()) {
+                                AppSpPushLog.i("subscribe topic successfully");
+                            } else {
+                                AppSpPushLog.e("subscribe topic failed, return value is " + task.getException().getMessage());
+                            }
+                        }
+                    });
+        } catch (Exception e) {
+            AppSpPushLog.e("subscribe failed, catch exception : " + e.getMessage());
+        }
+    }
+
+    //退订主题
+    public void unsubscribe(String topic) {
+        try {
+            // 取消主题订阅
+            HmsMessaging.getInstance(context).unsubscribe(topic)
+                    .addOnCompleteListener(new OnCompleteListener<Void>() {
+                        @Override
+                        public void onComplete(Task<Void> task) {
+                            // 获取取消主题订阅的结果
+                            if (task.isSuccessful()) {
+                                AppSpPushLog.i("unsubscribe topic successfully");
+                            } else {
+                                AppSpPushLog.e("subscribe topic failed, return value is " + task.getException().getMessage());
+                            }
+                        }
+                    });
+        } catch (Exception e) {
+            AppSpPushLog.e("subscribe failed, catch exception : " + e.getMessage());
+        }
+    }
+}
+
+

+ 176 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/interfaceToUser/UserInterface.java

@@ -0,0 +1,176 @@
+package com.anji.plus.ajpushlibrary.interfaceToUser;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.http.AppSpPushCallBack;
+import com.anji.plus.ajpushlibrary.http.AppSpPushHttpClient;
+import com.anji.plus.ajpushlibrary.http.AppSpPushPostData;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * author : zhouyang01
+ * e-mail : xxx@xx
+ * phone  :
+ * time   : 2021/03/17
+ * desc   :
+ */
+public class UserInterface {
+    /**
+     * @param path          请求地址
+     * @param deviceIds     设备id集
+     * @param title         通知标题
+     * @param content       通知内容
+     * @param pushType      推送类型 1透传消息; 0普通消息(默认0)
+     * @param extras        可选参数    推送透传消息内容
+     * @param androidConfig 可选参数    Android其他配置例如:传声音{"sound":"xxx"}
+     * @param iosConfig     可选参数      iOS其他配置例如:传声音{"sound":"xxx.caf"}
+     */
+    public static void pushBeachByDevicesId(
+            Context context,
+            String path,
+            List<String> deviceIds,
+            String title,
+            String content,
+            String pushType,
+            Map<String, String> extras,
+            Map<String, String> androidConfig,
+            Map<String, String> iosConfig,
+            MyCallBack myCallBack
+    ) {
+        if (TextUtils.isEmpty(path)) {
+            Toast.makeText(context, "请求地址不能为空", Toast.LENGTH_LONG).show();
+            return;
+        }
+        if (deviceIds == null && deviceIds.size() == 0) {
+            Toast.makeText(context, "设备Id不能为空", Toast.LENGTH_LONG).show();
+            return;
+        }
+
+
+        if (TextUtils.isEmpty(pushType)) {
+            pushType = "0";
+        }
+
+        if (extras == null) {
+            extras = new HashMap<>();
+        }
+        if (androidConfig == null) {
+            androidConfig = new HashMap<>();
+        }
+        if (iosConfig == null) {
+            iosConfig = new HashMap<>();
+        }
+
+        AppSpPushPostData appSpPushPostData = new AppSpPushPostData();
+        try {
+            appSpPushPostData.put("appKey", AppSpPushConfig.appKey);
+            appSpPushPostData.put("secretKey", AppSpPushConfig.secretKey);
+            appSpPushPostData.put("title", title);
+            appSpPushPostData.put("content", content);
+            appSpPushPostData.put("pushType", pushType);
+            appSpPushPostData.put("deviceIds", deviceIds);
+            appSpPushPostData.put("extras", extras);
+            appSpPushPostData.put("androidConfig", androidConfig);
+            appSpPushPostData.put("iosConfig", iosConfig);
+        } catch (Exception e) {
+
+        }
+        AppSpPushHttpClient client = new AppSpPushHttpClient();
+        client.request(path, appSpPushPostData, new AppSpPushCallBack() { //周阳修改
+            @Override
+            public void onSuccess(String data) {
+                myCallBack.success(data);
+            }
+
+            @Override
+            public void onError(String code, String msg) {
+                myCallBack.error(code, msg);
+            }
+        });
+    }
+
+    /**
+     * 向所有设备推送信息
+     *
+     * @param path 请求地址
+     */
+    public static void pushAll(String path, MyCallBack myCallBack) {
+        AppSpPushPostData appSpPushPostData = new AppSpPushPostData();
+        try {
+            appSpPushPostData.put("appKey", AppSpPushConfig.appKey);
+            appSpPushPostData.put("secretKey", AppSpPushConfig.secretKey);
+            appSpPushPostData.put("title", "周sir在测试批量推送");
+            appSpPushPostData.put("content", "i am content");
+            appSpPushPostData.put("pushType", "0");
+        } catch (Exception e) {
+
+        }
+        AppSpPushHttpClient client = new AppSpPushHttpClient();
+        client.request(path, appSpPushPostData, new AppSpPushCallBack() { //周阳修改
+            @Override
+            public void onSuccess(String data) {
+                myCallBack.success(data);
+            }
+
+            @Override
+            public void onError(String code, String msg) {
+                myCallBack.error(code, msg);
+            }
+        });
+    }
+
+    /**
+     * 查询某部手机收到的 历史推送消失
+     *
+     * @param path  请求地址
+     * @param msgId 从批量推送返回的消息id
+     */
+    public static void selectPushHistoryInfo(Context context, String path, String msgId, MyCallBack myCallBack) {
+        if (TextUtils.isEmpty(path)) {
+            Toast.makeText(context, "请求地址不能为空", Toast.LENGTH_LONG).show();
+            return;
+        }
+        if (TextUtils.isEmpty(msgId)) {
+            Toast.makeText(context, "msgId不能为空", Toast.LENGTH_LONG).show();
+            return;
+        }
+
+        AppSpPushPostData appSpPushPostData = new AppSpPushPostData();
+        try {
+            appSpPushPostData.put("appKey", AppSpPushConfig.appKey);
+            appSpPushPostData.put("secretKey", AppSpPushConfig.secretKey);
+            appSpPushPostData.put("msgId", msgId);
+        } catch (Exception e) {
+
+        }
+        AppSpPushHttpClient client = new AppSpPushHttpClient();
+        client.request(path, appSpPushPostData, new AppSpPushCallBack() {
+            @Override
+            public void onSuccess(String data) {
+                myCallBack.success(data);
+            }
+
+            @Override
+            public void onError(String code, String msg) {
+                myCallBack.error(code, msg);
+            }
+        });
+    }
+
+
+    MyCallBack myCallBack;
+
+    public interface MyCallBack {
+        void success(String message);
+
+        void error(String code, String message);
+    }
+
+
+}

+ 146 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/jpush/PushMessageReceiver.java

@@ -0,0 +1,146 @@
+package com.anji.plus.ajpushlibrary.jpush;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+import com.anji.plus.ajpushlibrary.service.IAppSpCallback;
+import com.anji.plus.ajpushlibrary.util.MessageUtil;
+import com.anji.plus.ajpushlibrary.util.SPUtil;
+
+import cn.jpush.android.api.CmdMessage;
+import cn.jpush.android.api.CustomMessage;
+import cn.jpush.android.api.JPushInterface;
+import cn.jpush.android.api.JPushMessage;
+import cn.jpush.android.api.NotificationMessage;
+import cn.jpush.android.service.JPushMessageReceiver;
+
+/**
+ * 用户自定义接收消息器,3.0.7开始支持,目前新tag/alias接口设置结果会在该广播接收器对应的方法中回调
+ */
+public class PushMessageReceiver extends JPushMessageReceiver {
+
+    @Override
+    public void onMessage(Context context, CustomMessage customMessage) {
+        AppSpPushLog.e("[onMessage] " + customMessage);
+        //获取透传信息
+        processCustomMessage(context, customMessage);
+    }
+
+    @Override
+    public void onNotifyMessageOpened(Context context, NotificationMessage message) {
+        AppSpPushLog.e("[onNotifyMessageOpened] " + message);
+        // 点击通知栏消息,发送广播给打开自定义页面
+//        notificationMessageModel.setMessageType(3);//小米极光点击通知栏跳转页面
+        MessageUtil.onMessageClicked(context, message.notificationTitle, message.notificationContent, message.notificationExtras);
+    }
+
+    @Override
+    public void onMultiActionClicked(Context context, Intent intent) {
+        AppSpPushLog.e("[onMultiActionClicked] 用户点击了通知栏按钮");
+        String nActionExtra = intent.getExtras().getString(JPushInterface.EXTRA_NOTIFICATION_ACTION_EXTRA);
+
+        //开发者根据不同 Action 携带的 extra 字段来分配不同的动作。
+        if (nActionExtra == null) {
+            AppSpPushLog.d("ACTION_NOTIFICATION_CLICK_ACTION nActionExtra is null");
+            return;
+        }
+        if (nActionExtra.equals("my_extra1")) {
+            AppSpPushLog.e("[onMultiActionClicked] 用户点击通知栏按钮一");
+        } else if (nActionExtra.equals("my_extra2")) {
+            AppSpPushLog.e("[onMultiActionClicked] 用户点击通知栏按钮二");
+        } else if (nActionExtra.equals("my_extra3")) {
+            AppSpPushLog.e("[onMultiActionClicked] 用户点击通知栏按钮三");
+        } else {
+            AppSpPushLog.e("[onMultiActionClicked] 用户点击通知栏按钮未定义");
+        }
+    }
+
+    @Override
+    public void onNotifyMessageArrived(Context context, NotificationMessage message) {
+        AppSpPushLog.e("[onNotifyMessageArrived] " + message);
+        // 消息到达时,将声音信息发给接收声音信息的广播
+//        notificationMessageModel.setMessageType(2);//极光通知栏声音
+        MessageUtil.onMessageArrived(context, message.msgId, message.notificationTitle, message.notificationContent, message.notificationExtras);
+    }
+
+    @Override
+    public void onNotifyMessageDismiss(Context context, NotificationMessage message) {
+        AppSpPushLog.e("[onNotifyMessageDismiss] " + message);
+    }
+
+    @Override
+    public void onRegister(Context context, String registrationId) {
+        AppSpPushLog.i("[onRegister] " + registrationId);
+
+        Log.i("极光回调结果", registrationId);
+        //將极光的registrationId保存再sp中
+        SPUtil.put(context, AppSpPushConstant.jPushRegId, registrationId);
+//        AppSpPushConstant.jPushRegId = registrationId;
+        //send the Registration Id to your server...
+        AppSpPushConfig.getInstance().sendRegTokenToServer(new IAppSpCallback() {
+            @Override
+            public void pushInfo(AppSpModel<String> appSpModel) {
+
+            }
+
+            @Override
+            public void error(String code, String msg) {
+
+            }
+        });
+
+    }
+
+    @Override
+    public void onConnected(Context context, boolean isConnected) {
+        AppSpPushLog.i("[onConnected] " + isConnected);
+    }
+
+    @Override
+    public void onCommandResult(Context context, CmdMessage cmdMessage) {
+        AppSpPushLog.i("[onCommandResult] " + cmdMessage);
+    }
+
+    @Override
+    public void onTagOperatorResult(Context context, JPushMessage jPushMessage) {
+        TagAliasOperatorHelper.getInstance().onTagOperatorResult(context, jPushMessage);
+        super.onTagOperatorResult(context, jPushMessage);
+    }
+
+    @Override
+    public void onCheckTagOperatorResult(Context context, JPushMessage jPushMessage) {
+        TagAliasOperatorHelper.getInstance().onCheckTagOperatorResult(context, jPushMessage);
+        super.onCheckTagOperatorResult(context, jPushMessage);
+    }
+
+    @Override
+    public void onAliasOperatorResult(Context context, JPushMessage jPushMessage) {
+        TagAliasOperatorHelper.getInstance().onAliasOperatorResult(context, jPushMessage);
+        super.onAliasOperatorResult(context, jPushMessage);
+    }
+
+    @Override
+    public void onMobileNumberOperatorResult(Context context, JPushMessage jPushMessage) {
+        TagAliasOperatorHelper.getInstance().onMobileNumberOperatorResult(context, jPushMessage);
+        super.onMobileNumberOperatorResult(context, jPushMessage);
+    }
+
+    //send msg to MainActivity
+    private void processCustomMessage(Context context, CustomMessage customMessage) {
+        //获取透传信息后将获取的数据通过广播形式发送
+//        notificationMessageModel.setMessageType(1);//极光透传
+        MessageUtil.onMessageArrived(context, customMessage.messageId, customMessage.title, customMessage.message, customMessage.extra);
+    }
+
+    @Override
+    public void onNotificationSettingsCheck(Context context, boolean isOn, int source) {
+        super.onNotificationSettingsCheck(context, isOn, source);
+        AppSpPushLog.e("[onNotificationSettingsCheck] isOn:" + isOn + ",source:" + source);
+    }
+
+}

+ 8 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/jpush/PushService.java

@@ -0,0 +1,8 @@
+package com.anji.plus.ajpushlibrary.jpush;
+
+
+import cn.jpush.android.service.JCommonService;
+
+public class PushService extends JCommonService {
+
+}

+ 368 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/jpush/TagAliasOperatorHelper.java

@@ -0,0 +1,368 @@
+package com.anji.plus.ajpushlibrary.jpush;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.SparseArray;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.util.CommonUtil;
+
+import java.util.Locale;
+import java.util.Set;
+
+import cn.jpush.android.api.JPushInterface;
+import cn.jpush.android.api.JPushMessage;
+
+/**
+ * 处理tagalias相关的逻辑
+ */
+public class TagAliasOperatorHelper {
+    private static final String TAG = "JIGUANG-TagAliasHelper";
+    public static int sequence = 1;
+    /**
+     * 增加
+     */
+    public static final int ACTION_ADD = 1;
+    /**
+     * 覆盖
+     */
+    public static final int ACTION_SET = 2;
+    /**
+     * 删除部分
+     */
+    public static final int ACTION_DELETE = 3;
+    /**
+     * 删除所有
+     */
+    public static final int ACTION_CLEAN = 4;
+    /**
+     * 查询
+     */
+    public static final int ACTION_GET = 5;
+
+    public static final int ACTION_CHECK = 6;
+
+    public static final int DELAY_SEND_ACTION = 1;
+
+    public static final int DELAY_SET_MOBILE_NUMBER_ACTION = 2;
+
+    private Context context;
+
+    private static TagAliasOperatorHelper mInstance;
+
+    private TagAliasOperatorHelper() {
+    }
+
+    public static TagAliasOperatorHelper getInstance() {
+        if (mInstance == null) {
+            synchronized (TagAliasOperatorHelper.class) {
+                if (mInstance == null) {
+                    mInstance = new TagAliasOperatorHelper();
+                }
+            }
+        }
+        return mInstance;
+    }
+
+    public void init(Context context) {
+        if (context != null) {
+            this.context = context.getApplicationContext();
+        }
+    }
+
+    private SparseArray<Object> setActionCache = new SparseArray<Object>();
+
+    public Object get(int sequence) {
+        return setActionCache.get(sequence);
+    }
+
+    public Object remove(int sequence) {
+        return setActionCache.get(sequence);
+    }
+
+    public void put(int sequence, Object tagAliasBean) {
+        setActionCache.put(sequence, tagAliasBean);
+    }
+
+    private Handler delaySendHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DELAY_SEND_ACTION:
+                    if (msg.obj != null && msg.obj instanceof TagAliasBean) {
+                        AppSpPushLog.i("on delay time");
+                        sequence++;
+                        TagAliasBean tagAliasBean = (TagAliasBean) msg.obj;
+                        setActionCache.put(sequence, tagAliasBean);
+                        if (context != null) {
+                            handleAction(context, sequence, tagAliasBean);
+                        } else {
+                            AppSpPushLog.e("#unexcepted - context was null");
+                        }
+                    } else {
+                        AppSpPushLog.w("#unexcepted - msg obj was incorrect");
+                    }
+                    break;
+                case DELAY_SET_MOBILE_NUMBER_ACTION:
+                    if (msg.obj != null && msg.obj instanceof String) {
+                        AppSpPushLog.i("retry set mobile number");
+                        sequence++;
+                        String mobileNumber = (String) msg.obj;
+                        setActionCache.put(sequence, mobileNumber);
+                        if (context != null) {
+                            handleAction(context, sequence, mobileNumber);
+                        } else {
+                            AppSpPushLog.e("#unexcepted - context was null");
+                        }
+                    } else {
+                        AppSpPushLog.w("#unexcepted - msg obj was incorrect");
+                    }
+                    break;
+            }
+        }
+    };
+
+    public void handleAction(Context context, int sequence, String mobileNumber) {
+        put(sequence, mobileNumber);
+        AppSpPushLog.d("sequence:" + sequence + ",mobileNumber:" + mobileNumber);
+        JPushInterface.setMobileNumber(context, sequence, mobileNumber);
+    }
+
+    /**
+     * 处理设置tag
+     */
+    public void handleAction(Context context, int sequence, TagAliasBean tagAliasBean) {
+        init(context);
+        if (tagAliasBean == null) {
+            AppSpPushLog.w("tagAliasBean was null");
+            return;
+        }
+        put(sequence, tagAliasBean);
+        if (tagAliasBean.isAliasAction) {
+            switch (tagAliasBean.action) {
+                case ACTION_GET:
+                    JPushInterface.getAlias(context, sequence);
+                    break;
+                case ACTION_DELETE:
+                    JPushInterface.deleteAlias(context, sequence);
+                    break;
+                case ACTION_SET:
+                    JPushInterface.setAlias(context, sequence, tagAliasBean.alias);
+                    break;
+                default:
+                    AppSpPushLog.w("unsupport alias action type");
+                    return;
+            }
+        } else {
+            switch (tagAliasBean.action) {
+                case ACTION_ADD:
+                    JPushInterface.addTags(context, sequence, tagAliasBean.tags);
+                    break;
+                case ACTION_SET:
+                    JPushInterface.setTags(context, sequence, tagAliasBean.tags);
+                    break;
+                case ACTION_DELETE:
+                    JPushInterface.deleteTags(context, sequence, tagAliasBean.tags);
+                    break;
+                case ACTION_CHECK:
+                    //一次只能check一个tag
+                    String tag = (String) tagAliasBean.tags.toArray()[0];
+                    JPushInterface.checkTagBindState(context, sequence, tag);
+                    break;
+                case ACTION_GET:
+                    JPushInterface.getAllTags(context, sequence);
+                    break;
+                case ACTION_CLEAN:
+                    JPushInterface.cleanTags(context, sequence);
+                    break;
+                default:
+                    AppSpPushLog.w("unsupport tag action type");
+                    return;
+            }
+        }
+    }
+
+    private boolean RetryActionIfNeeded(int errorCode, TagAliasBean tagAliasBean) {
+        if (!CommonUtil.isConnected(context)) {
+            AppSpPushLog.w("no network");
+            return false;
+        }
+        //返回的错误码为6002 超时,6014 服务器繁忙,都建议延迟重试
+        if (errorCode == 6002 || errorCode == 6014) {
+            AppSpPushLog.d("need retry");
+            if (tagAliasBean != null) {
+                Message message = new Message();
+                message.what = DELAY_SEND_ACTION;
+                message.obj = tagAliasBean;
+                delaySendHandler.sendMessageDelayed(message, 1000 * 60);
+                String logs = getRetryStr(tagAliasBean.isAliasAction, tagAliasBean.action, errorCode);
+                CommonUtil.showToast(logs, context);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean RetrySetMObileNumberActionIfNeeded(int errorCode, String mobileNumber) {
+        if (!CommonUtil.isConnected(context)) {
+            AppSpPushLog.w("no network");
+            return false;
+        }
+        //返回的错误码为6002 超时,6024 服务器内部错误,建议稍后重试
+        if (errorCode == 6002 || errorCode == 6024) {
+            AppSpPushLog.d("need retry");
+            Message message = new Message();
+            message.what = DELAY_SET_MOBILE_NUMBER_ACTION;
+            message.obj = mobileNumber;
+            delaySendHandler.sendMessageDelayed(message, 1000 * 60);
+            String str = "Failed to set mobile number due to %s. Try again after 60s.";
+            str = String.format(Locale.ENGLISH, str, (errorCode == 6002 ? "timeout" : "server internal error”"));
+            CommonUtil.showToast(str, context);
+            return true;
+        }
+        return false;
+
+    }
+
+    private String getRetryStr(boolean isAliasAction, int actionType, int errorCode) {
+        String str = "Failed to %s %s due to %s. Try again after 60s.";
+        str = String.format(Locale.ENGLISH, str, getActionStr(actionType), (isAliasAction ? "alias" : " tags"), (errorCode == 6002 ? "timeout" : "server too busy"));
+        return str;
+    }
+
+    private String getActionStr(int actionType) {
+        switch (actionType) {
+            case ACTION_ADD:
+                return "add";
+            case ACTION_SET:
+                return "set";
+            case ACTION_DELETE:
+                return "delete";
+            case ACTION_GET:
+                return "get";
+            case ACTION_CLEAN:
+                return "clean";
+            case ACTION_CHECK:
+                return "check";
+        }
+        return "unkonw operation";
+    }
+
+    public void onTagOperatorResult(Context context, JPushMessage jPushMessage) {
+        int sequence = jPushMessage.getSequence();
+        AppSpPushLog.i("action - onTagOperatorResult, sequence:" + sequence + ",tags:" + jPushMessage.getTags());
+        AppSpPushLog.i("tags size:" + jPushMessage.getTags().size());
+        init(context);
+        //根据sequence从之前操作缓存中获取缓存记录
+        TagAliasBean tagAliasBean = (TagAliasBean) setActionCache.get(sequence);
+        if (tagAliasBean == null) {
+            CommonUtil.showToast("获取缓存记录失败", context);
+            return;
+        }
+        if (jPushMessage.getErrorCode() == 0) {
+            AppSpPushLog.i("action - modify tag Success,sequence:" + sequence);
+            setActionCache.remove(sequence);
+            String logs = getActionStr(tagAliasBean.action) + " tags success";
+            AppSpPushLog.i(logs);
+            CommonUtil.showToast(logs, context);
+        } else {
+            String logs = "Failed to " + getActionStr(tagAliasBean.action) + " tags";
+            if (jPushMessage.getErrorCode() == 6018) {
+                //tag数量超过限制,需要先清除一部分再add
+                logs += ", tags is exceed limit need to clean";
+            }
+            logs += ", errorCode:" + jPushMessage.getErrorCode();
+            AppSpPushLog.e(logs);
+            if (!RetryActionIfNeeded(jPushMessage.getErrorCode(), tagAliasBean)) {
+                CommonUtil.showToast(logs, context);
+            }
+        }
+    }
+
+    public void onCheckTagOperatorResult(Context context, JPushMessage jPushMessage) {
+        int sequence = jPushMessage.getSequence();
+        AppSpPushLog.i("action - onCheckTagOperatorResult, sequence:" + sequence + ",checktag:" + jPushMessage.getCheckTag());
+        init(context);
+        //根据sequence从之前操作缓存中获取缓存记录
+        TagAliasBean tagAliasBean = (TagAliasBean) setActionCache.get(sequence);
+        if (tagAliasBean == null) {
+            CommonUtil.showToast("获取缓存记录失败", context);
+            return;
+        }
+        if (jPushMessage.getErrorCode() == 0) {
+            AppSpPushLog.i("tagBean:" + tagAliasBean);
+            setActionCache.remove(sequence);
+            String logs = getActionStr(tagAliasBean.action) + " tag " + jPushMessage.getCheckTag() + " bind state success,state:" + jPushMessage.getTagCheckStateResult();
+            AppSpPushLog.i(logs);
+            CommonUtil.showToast(logs, context);
+        } else {
+            String logs = "Failed to " + getActionStr(tagAliasBean.action) + " tags, errorCode:" + jPushMessage.getErrorCode();
+            AppSpPushLog.e(logs);
+            if (!RetryActionIfNeeded(jPushMessage.getErrorCode(), tagAliasBean)) {
+                CommonUtil.showToast(logs, context);
+            }
+        }
+    }
+
+    public void onAliasOperatorResult(Context context, JPushMessage jPushMessage) {
+        int sequence = jPushMessage.getSequence();
+        AppSpPushLog.i("action - onAliasOperatorResult, sequence:" + sequence + ",alias:" + jPushMessage.getAlias());
+        init(context);
+        //根据sequence从之前操作缓存中获取缓存记录
+        TagAliasBean tagAliasBean = (TagAliasBean) setActionCache.get(sequence);
+        if (tagAliasBean == null) {
+            CommonUtil.showToast("获取缓存记录失败", context);
+            return;
+        }
+        if (jPushMessage.getErrorCode() == 0) {
+            AppSpPushLog.i("action - modify alias Success,sequence:" + sequence);
+            setActionCache.remove(sequence);
+            String logs = getActionStr(tagAliasBean.action) + " alias success";
+            AppSpPushLog.i(logs);
+            CommonUtil.showToast(logs, context);
+        } else {
+            String logs = "Failed to " + getActionStr(tagAliasBean.action) + " alias, errorCode:" + jPushMessage.getErrorCode();
+            AppSpPushLog.e(logs);
+            if (!RetryActionIfNeeded(jPushMessage.getErrorCode(), tagAliasBean)) {
+                CommonUtil.showToast(logs, context);
+            }
+        }
+    }
+
+    //设置手机号码回调
+    public void onMobileNumberOperatorResult(Context context, JPushMessage jPushMessage) {
+        int sequence = jPushMessage.getSequence();
+        AppSpPushLog.i("action - onMobileNumberOperatorResult, sequence:" + sequence + ",mobileNumber:" + jPushMessage.getMobileNumber());
+        init(context);
+        if (jPushMessage.getErrorCode() == 0) {
+            AppSpPushLog.i("action - set mobile number Success,sequence:" + sequence);
+            setActionCache.remove(sequence);
+        } else {
+            String logs = "Failed to set mobile number, errorCode:" + jPushMessage.getErrorCode();
+            AppSpPushLog.e(logs);
+            if (!RetrySetMObileNumberActionIfNeeded(jPushMessage.getErrorCode(), jPushMessage.getMobileNumber())) {
+                CommonUtil.showToast(logs, context);
+            }
+        }
+    }
+
+    public static class TagAliasBean {
+        int action;
+        Set<String> tags;
+        String alias;
+        boolean isAliasAction;
+
+        @Override
+        public String toString() {
+            return "TagAliasBean{" +
+                    "action=" + action +
+                    ", tags=" + tags +
+                    ", alias='" + alias + '\'' +
+                    ", isAliasAction=" + isAliasAction +
+                    '}';
+        }
+    }
+
+
+}

+ 49 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/model/AppSpModel.java

@@ -0,0 +1,49 @@
+package com.anji.plus.ajpushlibrary.model;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 后端返回的结果
+ * </p>
+ */
+public class AppSpModel<T> {
+    private String repCode;
+    private String repMsg;
+    private T repData;
+
+    public String getRepCode() {
+        return repCode;
+    }
+
+    public void setRepCode(String repCode) {
+        this.repCode = repCode;
+    }
+
+    public String getRepMsg() {
+        return repMsg;
+    }
+
+    public void setRepMsg(String repMsg) {
+        this.repMsg = repMsg;
+    }
+
+    public T getRepData() {
+        return repData;
+    }
+
+    public void setRepData(T repData) {
+        this.repData = repData;
+    }
+
+    @Override
+    public String toString() {
+        return "AppSpModel{" +
+                "repCode='" + repCode + '\'' +
+                ", repMsg='" + repMsg + '\'' +
+                ", repData=" + repData +
+                '}';
+    }
+}

+ 81 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/model/NotificationMessageModel.java

@@ -0,0 +1,81 @@
+package com.anji.plus.ajpushlibrary.model;
+
+import java.io.Serializable;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 对推送消息做的封装成统一model
+ * </p>
+ */
+public class NotificationMessageModel implements Serializable {
+    private String title;
+    private String content;
+    private int actionType ;//1,无操作,2,点击通知栏消息
+    private int brandType;//设备类型,1,华为,2,小米,3,oppo,4,vivo,5,其他
+    private String notificationExtras;//自定义参数
+    private int messageType;//消息类型  1、极光透传,2、极光通知栏声音,3小米极光点击通知栏跳转页面
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getNotificationExtras() {
+        return notificationExtras;
+    }
+
+    public void setNotificationExtras(String notificationExtras) {
+        this.notificationExtras = notificationExtras;
+    }
+
+    public int getBrandType() {
+        return brandType;
+    }
+
+    public void setBrandType(int brandType) {
+        this.brandType = brandType;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+
+    public void setMessageType(int messageType) {
+        this.messageType = messageType;
+    }
+
+    public int getActionType() {
+        return actionType;
+    }
+
+    public void setActionType(int actionType) {
+        this.actionType = actionType;
+    }
+
+    @Override
+    public String toString() {
+        return "NotificationMessageModel{" +
+                "title='" + title + '\'' +
+                ", content='" + content + '\'' +
+                ", actionType=" + actionType +
+                ", brandType=" + brandType +
+                ", notificationExtras='" + notificationExtras + '\'' +
+                ", messageType=" + messageType +
+                '}';
+    }
+}

+ 71 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/oppo/OppoPushUtil.java

@@ -0,0 +1,71 @@
+package com.anji.plus.ajpushlibrary.oppo;
+
+import android.content.Context;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.heytap.msp.push.HeytapPushManager;
+import com.heytap.msp.push.callback.ICallBackResultService;
+
+public class OppoPushUtil {
+    private Context context;
+
+    public OppoPushUtil(Context context) {
+        this.context = context;
+    }
+
+    //判断是否手机平台是否支持PUSH
+    public boolean isSupportPush() {
+        return HeytapPushManager.isSupportPush();
+    }
+
+    //sdk初始化
+    public void init() {
+        HeytapPushManager.init(context, true);
+    }
+
+    //注册OPPO PUSH推送服务
+    public void register(ICallBackResultService mPushCallback) {
+        HeytapPushManager.register(context, AppSpPushConstant.oppoAppKey, AppSpPushConstant.oppoAppSecret, mPushCallback);//setPushCallback接口也可设置callback
+
+    }
+
+    //解注册OPPO PUSH推送服务
+    public void unRegister() {
+        HeytapPushManager.unRegister();
+
+    }
+
+    //获取注册OPPO PUSH推送服务的注册ID
+    public String getRegisterID() {
+        return HeytapPushManager.getRegisterID();
+
+    }
+
+    // 暂停接收OPPO PUSH服务推送的消息
+    public void pausePush() {
+        HeytapPushManager.pausePush();
+    }
+
+    // 恢复接收OPPO PUSH服务推送的消息,这时服务器会把暂停时期的推送消息重新推送过来
+    public void resumePush() {
+        HeytapPushManager.resumePush();
+    }
+
+    // 弹出通知栏权限弹窗(ColorOS 5.0以上有效)
+    public void requestNotificationPermission() {
+        HeytapPushManager.requestNotificationPermission();
+    }
+
+    //打开通知栏设置界面
+    public void openNotificationSettings() {
+        HeytapPushManager.openNotificationSettings();
+    }
+
+    // 获取通知栏状态,从callbackresultservice回调结果
+    public void getNotificationStatus() {
+        HeytapPushManager.getNotificationStatus();
+    }
+
+}
+
+

+ 31 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/AppSpPushController.java

@@ -0,0 +1,31 @@
+package com.anji.plus.ajpushlibrary.service;
+
+import android.content.Context;
+
+import com.anji.plus.ajpushlibrary.base.AppSpPushBaseController;
+
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ *
+ * </p>
+ */
+public class AppSpPushController extends AppSpPushBaseController {
+    public AppSpPushController(Context mContext, String appKey) {
+        super(mContext, appKey);
+    }
+
+    /**
+     * @param iAppSpCallback 结果回调接口
+     */
+    public void putPushInfo(IAppSpCallback iAppSpCallback) {
+        AppSpPushHandler appSpHandler = new AppSpPushHandler();
+        appSpHandler.setAppSpCallback(iAppSpCallback);
+        IAppSpService appspVersionService = new AppSpPushServiceImpl(getContext(), getAppKey(), appSpHandler);
+        appspVersionService.putPushInfo();
+    }
+}

+ 68 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/AppSpPushHandler.java

@@ -0,0 +1,68 @@
+package com.anji.plus.ajpushlibrary.service;
+
+import android.text.TextUtils;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.base.AppSpPushBaseHandler;
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 解析服务器返回的json串
+ * </p>
+ */
+public class AppSpPushHandler extends AppSpPushBaseHandler {
+    private IAppSpCallback appSpCallback;
+
+    public void setAppSpCallback(IAppSpCallback appSpNoticeCallback) {
+        this.appSpCallback = appSpNoticeCallback;
+    }
+
+    /**
+     * 网络请求成功成功
+     */
+    public void handleSuccess(String data) {
+        if (appSpCallback != null) {
+            synchronized (appSpCallback) {
+                //Notice
+                try {
+                    AppSpModel<String> spVersionModel = new AppSpModel<>();
+                    JSONObject jsonObject = new JSONObject(data);
+                    spVersionModel.setRepCode(getStringElement(jsonObject.opt("repCode")));
+                    spVersionModel.setRepMsg(getStringElement(jsonObject.opt("repMsg")));
+                    Object repDtaObj = jsonObject.opt("repData");
+                    if (repDtaObj != null && !TextUtils.isEmpty(repDtaObj.toString())
+                            && !"null".equalsIgnoreCase(repDtaObj.toString())) {
+                        JSONArray repData = new JSONArray(repDtaObj.toString());
+                    }
+                    AppSpPushLog.d("通知返回客户端数据 " + spVersionModel);
+                } catch (Exception e) {
+                    AppSpPushLog.d("通知返回客户端数据 Exception e " + e.toString());
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 数据上传异常
+     *
+     * @param code
+     * @param msg
+     */
+    public void handleExcption(String code, String msg) {
+        if (appSpCallback != null) {
+            synchronized (appSpCallback) {
+                appSpCallback.error(code, msg);
+            }
+        }
+    }
+
+}

+ 57 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/AppSpPushServiceImpl.java

@@ -0,0 +1,57 @@
+package com.anji.plus.ajpushlibrary.service;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.base.AppSpPushBaseRequest;
+import com.anji.plus.ajpushlibrary.http.AppSpPushCallBack;
+import com.anji.plus.ajpushlibrary.http.AppSpPushHttpClient;
+import com.anji.plus.ajpushlibrary.http.AppSpPushPostData;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 将厂商token和极光的regId上传给服务器
+ * </p>
+ */
+public class AppSpPushServiceImpl extends AppSpPushBaseRequest implements IAppSpService {
+    private AppSpPushHandler appSpHandler;
+
+    public AppSpPushServiceImpl(Context mContext, String appKey, AppSpPushHandler appSpHandler) {
+        super(mContext, appKey);
+        this.appSpHandler = appSpHandler;
+    }
+
+    @Override
+    public void putPushInfo() {
+        if (TextUtils.isEmpty(appKey)) {
+            AppSpPushLog.e("putPushInfo Appkey is null or empty");
+            return;
+        }
+        AppSpPushPostData appSpPushPostData = getPostEncryptData();
+        AppSpPushHttpClient client = new AppSpPushHttpClient();
+//        client.request(AppSpRequestUrl.Host + AppSpRequestUrl.putPushInfo, appSpPostData, new AppSpCallBack() {
+        client.request(AppSpPushConfig.requestUrl, appSpPushPostData, new AppSpPushCallBack() {
+            @Override
+            public void onSuccess(String data) {
+                if (appSpHandler != null) {
+                    appSpHandler.handleSuccess(data);
+                }
+            }
+
+            @Override
+            public void onError(String code, String msg) {
+                if (appSpHandler != null) {
+                    appSpHandler.handleExcption(code, msg);
+                }
+            }
+        });
+
+    }
+
+}

+ 26 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/IAppSpCallback.java

@@ -0,0 +1,26 @@
+package com.anji.plus.ajpushlibrary.service;
+
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * <p>
+ * 对外的接口,方便集成
+ * </p>
+ */
+public interface IAppSpCallback {
+
+    void pushInfo(AppSpModel<String> appSpModel);
+
+    /**
+     * 请求错误,未能获取后端数据
+     *
+     * @param code 状态码
+     * @param msg  错误信息
+     */
+    void error(String code, String msg);
+}

+ 14 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/service/IAppSpService.java

@@ -0,0 +1,14 @@
+package com.anji.plus.ajpushlibrary.service;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ *
+ * </p>
+ */
+public interface IAppSpService {
+    void putPushInfo();
+}

+ 220 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/Base64Util.java

@@ -0,0 +1,220 @@
+package com.anji.plus.ajpushlibrary.util;
+
+public final class Base64Util {
+    private static final int BASELENGTH = 128;
+    private static final int LOOKUPLENGTH = 64;
+    private static final int TWENTYFOURBITGROUP = 24;
+    private static final int EIGHTBIT = 8;
+    private static final int SIXTEENBIT = 16;
+    private static final int FOURBYTE = 4;
+    private static final int SIGN = -128;
+    private static char PAD = '=';
+    private static byte[] base64Alphabet = new byte[BASELENGTH];
+    private static char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
+
+    static {
+        for (int i = 0; i < BASELENGTH; ++i) {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+        for (int i = 0; i <= 25; i++) {
+            lookUpBase64Alphabet[i] = (char) ('A' + i);
+        }
+        for (int i = 26, j = 0; i <= 51; i++, j++) {
+            lookUpBase64Alphabet[i] = (char) ('a' + j);
+        }
+        for (int i = 52, j = 0; i <= 61; i++, j++) {
+            lookUpBase64Alphabet[i] = (char) ('0' + j);
+        }
+        lookUpBase64Alphabet[62] = (char) '+';
+        lookUpBase64Alphabet[63] = (char) '/';
+    }
+
+    private static boolean isWhiteSpace(char octect) {
+        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+    }
+
+    private static boolean isPad(char octect) {
+        return (octect == PAD);
+    }
+
+    private static boolean isData(char octect) {
+        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
+    }
+
+    /**
+     * 把64位byte数组转换成String
+     */
+    public static String encode(byte[] binaryData) {
+        if (binaryData == null) {
+            return null;
+        }
+        int lengthDataBits = binaryData.length * EIGHTBIT;
+        if (lengthDataBits == 0) {
+            return "";
+        }
+        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1
+                : numberTriplets;
+        char encodedData[] = null;
+        encodedData = new char[numberQuartet * 4];
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        for (int i = 0; i < numberTriplets; i++) {
+            b1 = binaryData[dataIndex++];
+            b2 = binaryData[dataIndex++];
+            b3 = binaryData[dataIndex++];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)
+                    : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4)
+                    : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6)
+                    : (byte) ((b3) >> 6 ^ 0xfc);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
+        }
+        // form integral number of 6-bit groups
+        if (fewerThan24bits == EIGHTBIT) {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)
+                    : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex++] = PAD;
+            encodedData[encodedIndex++] = PAD;
+        } else if (fewerThan24bits == SIXTEENBIT) {
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)
+                    : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4)
+                    : (byte) ((b2) >> 4 ^ 0xf0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex++] = PAD;
+        }
+        return new String(encodedData);
+    }
+
+    /**
+     * 把Base64位编码转换成byte数组
+     */
+    public static byte[] decode(String encoded) {
+        if (encoded == null) {
+            return null;
+        }
+        char[] base64Data = encoded.toCharArray();
+        // remove white spaces
+        int len = removeWhiteSpace(base64Data);
+        if (len % FOURBYTE != 0) {
+            return null;// should be divisible by four
+        }
+        int numberQuadruple = (len / FOURBYTE);
+        if (numberQuadruple == 0) {
+            return new byte[0];
+        }
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+        int i = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        decodedData = new byte[(numberQuadruple) * 3];
+        for (; i < numberQuadruple - 1; i++) {
+            if (!isData((d1 = base64Data[dataIndex++]))
+                    || !isData((d2 = base64Data[dataIndex++]))
+                    || !isData((d3 = base64Data[dataIndex++]))
+                    || !isData((d4 = base64Data[dataIndex++]))) {
+                return null;
+            }// if found "no data" just return null
+            b1 = base64Alphabet[d1];
+            b2 = base64Alphabet[d2];
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+        if (!isData((d1 = base64Data[dataIndex++]))
+                || !isData((d2 = base64Data[dataIndex++]))) {
+            return null;// if found "no data" just return null
+        }
+        b1 = base64Alphabet[d1];
+        b2 = base64Alphabet[d2];
+        d3 = base64Data[dataIndex++];
+        d4 = base64Data[dataIndex++];
+        if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters
+            if (isPad(d3) && isPad(d4)) {
+                if ((b2 & 0xf) != 0)// last 4 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 1];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                return tmp;
+            } else if (!isPad(d3) && isPad(d4)) {
+                b3 = base64Alphabet[d3];
+                if ((b3 & 0x3) != 0)// last 2 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 2];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                return tmp;
+            } else {
+                return null;
+            }
+        } else { // No PAD e.g 3cQl
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+        return decodedData;
+    }
+
+    /**
+     * remove WhiteSpace from MIME containing encoded Base64Util data.
+     *
+     * @param data the byte array of base64 data (with WS)
+     * @return the new length
+     */
+    private static int removeWhiteSpace(char[] data) {
+        if (data == null) {
+            return 0;
+        }
+        // count characters that's not whitespace
+        int newSize = 0;
+        int len = data.length;
+        for (int i = 0; i < len; i++) {
+            if (!isWhiteSpace(data[i])) {
+                data[newSize++] = data[i];
+            }
+        }
+        return newSize;
+    }
+}

+ 57 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/BrandUtil.java

@@ -0,0 +1,57 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import android.content.Context;
+import android.os.Build;
+
+public class BrandUtil {
+    private static volatile BrandUtil instance;
+    private Context mContext;
+
+    public BrandUtil(Context mContext) {
+        this.mContext = mContext;
+    }
+
+    /**
+     * 单例模式 获取实例的方法
+     */
+
+    public static BrandUtil getInstance(Context context) {
+        if (instance == null) {
+            synchronized (BrandUtil.class) {
+                instance = new BrandUtil(context);
+            }
+        }
+        return instance;
+    }
+
+    //华为
+    public boolean isHuawei() {
+        if (Build.BRAND == null) {
+            return false;
+        } else {
+            return Build.BRAND.toLowerCase().equals("huawei") || Build.BRAND.toLowerCase().equals("honor");
+        }
+    }
+
+    //小米
+    public boolean isXiaomi() {
+        return Build.BRAND != null && Build.BRAND.toLowerCase().equals("xiaomi");
+    }
+
+    //oppo
+    public static boolean isOPPO() {
+        return Build.BRAND != null && Build.BRAND.toLowerCase().equals("oppo");
+    }
+
+    //vivo
+    public static boolean isVIVO() {
+        return Build.BRAND != null && Build.BRAND.toLowerCase().equals("vivo");
+    }
+
+    //魅族
+    public boolean isMeizu() {
+        return Build.BRAND != null && Build.BRAND.toLowerCase().equals("meizu");
+    }
+
+
+}

+ 137 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/CommonUtil.java

@@ -0,0 +1,137 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Looper;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import cn.jpush.android.api.JPushInterface;
+
+    public class CommonUtil {
+    public static final String PREFS_NAME = "JPUSH_EXAMPLE";
+    public static final String PREFS_DAYS = "JPUSH_EXAMPLE_DAYS";
+    public static final String PREFS_START_TIME = "PREFS_START_TIME";
+    public static final String PREFS_END_TIME = "PREFS_END_TIME";
+    public static final String KEY_APP_KEY = "JPUSH_APPKEY";
+
+    public static boolean isEmpty(String s) {
+        if (null == s)
+            return true;
+        if (s.length() == 0)
+            return true;
+        if (s.trim().length() == 0)
+            return true;
+        return false;
+    }
+
+    /**
+     * 只能以 “+” 或者 数字开头;后面的内容只能包含 “-” 和 数字。
+     */
+    private final static String MOBILE_NUMBER_CHARS = "^[+0-9][-0-9]{1,}$";
+
+    public static boolean isValidMobileNumber(String s) {
+        if (TextUtils.isEmpty(s)) return true;
+        Pattern p = Pattern.compile(MOBILE_NUMBER_CHARS);
+        Matcher m = p.matcher(s);
+        return m.matches();
+    }
+
+    // 校验Tag Alias 只能是数字,英文字母和中文
+    public static boolean isValidTagAndAlias(String s) {
+        Pattern p = Pattern.compile("^[\u4E00-\u9FA50-9a-zA-Z_!@#$&*+=.|]+$");
+        Matcher m = p.matcher(s);
+        return m.matches();
+    }
+
+    // 取得AppKey
+    public static String getAppKey(Context context) {
+        Bundle metaData = null;
+        String appKey = null;
+        try {
+            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(
+                    context.getPackageName(), PackageManager.GET_META_DATA);
+            if (null != ai)
+                metaData = ai.metaData;
+            if (null != metaData) {
+                appKey = metaData.getString(KEY_APP_KEY);
+                if ((null == appKey) || appKey.length() != 24) {
+                    appKey = null;
+                }
+            }
+        } catch (NameNotFoundException e) {
+
+        }
+        return appKey;
+    }
+
+    // 取得版本号
+    public static String GetVersion(Context context) {
+        try {
+            PackageInfo manager = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), 0);
+            return manager.versionName;
+        } catch (NameNotFoundException e) {
+            return "Unknown";
+        }
+    }
+
+    public static void showToast(final String toast, final Context context) {
+        new Thread(new Runnable() {
+
+            @Override
+            public void run() {
+                Looper.prepare();
+                Toast.makeText(context, toast, Toast.LENGTH_SHORT).show();
+                Looper.loop();
+            }
+        }).start();
+    }
+
+    public static boolean isConnected(Context context) {
+        ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = conn.getActiveNetworkInfo();
+        return (info != null && info.isConnected());
+    }
+
+    public static String getImei(Context context, String imei) {
+        String ret = null;
+        try {
+            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+            ret = telephonyManager.getDeviceId();
+        } catch (Exception e) {
+            AppSpPushLog.e(e.getMessage());
+        }
+        if (isReadableASCII(ret)) {
+            return ret;
+        } else {
+            return imei;
+        }
+    }
+
+    private static boolean isReadableASCII(CharSequence string) {
+        if (TextUtils.isEmpty(string)) return false;
+        try {
+            Pattern p = Pattern.compile("[\\x20-\\x7E]+");
+            return p.matcher(string).matches();
+        } catch (Throwable e) {
+            return true;
+        }
+    }
+
+    public static String getDeviceId(Context context) {
+        return JPushInterface.getUdid(context);
+    }
+}

+ 76 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/MessageUtil.java

@@ -0,0 +1,76 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.base.ActionType;
+import com.anji.plus.ajpushlibrary.model.NotificationMessageModel;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 消息处理,监听到达的消息和处理点击消息事件
+ * </p>
+ */
+public class MessageUtil {
+    /**
+     * @param context
+     * @param title   消息标题
+     * @param message 消息内容
+     * @param extra   其他配置,比如{“key1":”value1“,“key2":”value2“},以json格式返回,详情见appsp官网推送
+     */
+    public static void onMessageArrived(Context context, String messageId, String title, String message, String extra) {
+        AppSpPushLog.i("onMessageArrived is called. " + message.toString());
+        try {
+            //消息去重处理
+            String cachedMesssageId = (String) SPUtil.get(context, "messsageId", "");
+            if (cachedMesssageId == messageId) {
+                return;
+            }
+            SPUtil.put(context, "messsageId", cachedMesssageId);
+            Intent intent = new Intent();
+            intent.setAction(AppSpPushConstant.MESSAGE_RECEIVED_ACTION);
+            Bundle bundle = new Bundle();
+            NotificationMessageModel notificationMessageModel = new NotificationMessageModel();
+            notificationMessageModel.setTitle(title);
+            notificationMessageModel.setContent(message);
+            notificationMessageModel.setNotificationExtras(extra);
+            notificationMessageModel.setActionType(ActionType.NONE);
+            bundle.putSerializable(AppSpPushConstant.HNOTIFICATIONMESSAGE, notificationMessageModel);
+            intent.putExtras(bundle);
+            context.sendBroadcast(intent);
+        } catch (Throwable throwable) {
+
+        }
+    }
+
+    /**
+     * @param context
+     * @param title   消息标题
+     * @param message 消息内容
+     * @param extra   其他配置,比如{“key1":”value1“,“key2":”value2“},以json格式返回,详情见appsp官网推送
+     */
+    public static void onMessageClicked(Context context, String title, String message, String extra) {
+        try {
+            Intent intent = new Intent();
+            intent.setAction(AppSpPushConstant.MESSAGE_RECEIVED_ACTION);
+            Bundle bundle = new Bundle();
+            NotificationMessageModel notificationMessageModel = new NotificationMessageModel();
+            notificationMessageModel.setTitle(title);
+            notificationMessageModel.setContent(message);
+            notificationMessageModel.setNotificationExtras(extra);
+            notificationMessageModel.setActionType(ActionType.CLICK);
+            bundle.putSerializable(AppSpPushConstant.HNOTIFICATIONMESSAGE, notificationMessageModel);
+            intent.putExtras(bundle);
+            context.sendBroadcast(intent);
+        } catch (Throwable throwable) {
+
+        }
+    }
+}

+ 494 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/PhoneUtil.java

@@ -0,0 +1,494 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.lang.reflect.Method;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+import java.util.Locale;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 获取设备信息,这里可能会牵扯到安全的问题,
+ * 务必在《隐私政策》表述搜集mac、imei等消息的用途
+ * </p>
+ */
+public class PhoneUtil {
+    public static String getDeviceId(Context context) {
+        return getDeviceId(context, 128);
+    }
+
+    /**
+     * Android_id + Wifi-MAC
+     * 最多获取128位
+     *
+     * @param context
+     * @param length  最大位数
+     * @return
+     */
+    public static String getDeviceId(Context context, int length) {
+        String androidId = getAndroidId(context);
+        String mac = getMac(context);
+        AppSpPushLog.d(" getDeviceId androidId " + androidId + " mac " + mac);
+        String deviceId = androidId + mac;
+        //截取前128位
+        if (TextUtils.isEmpty(deviceId)) {
+            deviceId = getPhoneUUID();
+        } else if (deviceId.length() >= length) {
+            deviceId = deviceId.substring(0, length);
+        }
+        return deviceId;
+    }
+
+    @SuppressLint("MissingPermission")
+    public static String[] getNetworkAccessMode(Context paramContext) {
+        String[] arrayOfString = {"", ""};
+        if (paramContext == null) {
+            return arrayOfString;
+        }
+        try {
+            if (!checkPermission(paramContext, Manifest.permission.ACCESS_NETWORK_STATE)) {
+                arrayOfString[0] = "";
+                return arrayOfString;
+            }
+            ConnectivityManager localConnectivityManager = (ConnectivityManager) paramContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            if (localConnectivityManager == null) {
+                arrayOfString[0] = "";
+                return arrayOfString;
+            }
+            NetworkInfo localNetworkInfo1 = localConnectivityManager.getNetworkInfo(1);
+            if ((localNetworkInfo1 != null) &&
+                    (localNetworkInfo1.getState() == NetworkInfo.State.CONNECTED)) {
+                arrayOfString[0] = "Wi-Fi";
+                return arrayOfString;
+            }
+            NetworkInfo localNetworkInfo2 = localConnectivityManager.getNetworkInfo(0);
+            if ((localNetworkInfo2 != null) &&
+                    (localNetworkInfo2.getState() == NetworkInfo.State.CONNECTED)) {
+                arrayOfString[0] = "2G/3G";
+                arrayOfString[1] = localNetworkInfo2.getSubtypeName();
+                return arrayOfString;
+            }
+        } catch (Throwable localThrowable) {
+        }
+        return arrayOfString;
+    }
+
+    /**
+     * 获取WIFI Mac地址
+     *
+     * @param paramContext
+     * @return
+     */
+    public static String getMac(Context paramContext) {
+        String str = "";
+        if (paramContext == null) {
+            return str;
+        }
+        if (Build.VERSION.SDK_INT < 23) {
+            str = getMacBySystemInterface(paramContext);
+        } else if (Build.VERSION.SDK_INT == 23) {
+            str = getMacByJavaAPI();
+            if (TextUtils.isEmpty(str)) {
+                str = getMacShell();
+            }
+        } else {
+            str = getMacByJavaAPI();
+            if (TextUtils.isEmpty(str)) {
+                str = getMacBySystemInterface(paramContext);
+            }
+        }
+        return str;
+    }
+
+    @SuppressLint("MissingPermission")
+    private static String getMacBySystemInterface(Context paramContext) {
+        if (paramContext == null) {
+            return "";
+        }
+        try {
+            WifiManager localWifiManager = (WifiManager) paramContext.getSystemService(Context.WIFI_SERVICE);
+            if (checkPermission(paramContext, Manifest.permission.ACCESS_WIFI_STATE)) {
+                WifiInfo localWifiInfo = localWifiManager.getConnectionInfo();
+                return localWifiInfo.getMacAddress();
+            }
+            return "";
+        } catch (Throwable localThrowable) {
+        }
+        return "";
+    }
+
+
+    private static String getMacByJavaAPI() {
+        try {
+            Enumeration localEnumeration = NetworkInterface.getNetworkInterfaces();
+            while (localEnumeration.hasMoreElements()) {
+                NetworkInterface localNetworkInterface = (NetworkInterface) localEnumeration.nextElement();
+                if (("wlan0".equals(localNetworkInterface.getName())) || ("eth0".equals(localNetworkInterface.getName()))) {
+                    byte[] arrayOfByte1 = localNetworkInterface.getHardwareAddress();
+                    if ((arrayOfByte1 == null) || (arrayOfByte1.length == 0)) {
+                        return "";
+                    }
+                    StringBuilder localStringBuilder = new StringBuilder();
+                    for (byte b : arrayOfByte1) {
+                        localStringBuilder.append(String.format("%02X:", new Object[]{Byte.valueOf(b)}));
+                    }
+                    if (localStringBuilder.length() > 0) {
+                        localStringBuilder.deleteCharAt(localStringBuilder.length() - 1);
+                    }
+                    return localStringBuilder.toString().toLowerCase(Locale.getDefault());
+                }
+            }
+        } catch (Throwable localThrowable) {
+        }
+        return "";
+    }
+
+    private static String getMacShell() {
+        try {
+            String[] arrayOfString = {"/sys/class/net/wlan0/address", "/sys/class/net/eth0/address", "/sys/devices/virtual/net/wlan0/address"};
+            for (int i = 0; i < arrayOfString.length; i++) {
+                try {
+                    String str = reaMac(arrayOfString[i]);
+                    if (str != null) {
+                        return str;
+                    }
+                } catch (Throwable localThrowable2) {
+                }
+            }
+        } catch (Throwable localThrowable1) {
+        }
+        return "";
+    }
+
+    private static String reaMac(String paramString) {
+        String str = null;
+        try {
+            FileReader localFileReader = new FileReader(paramString);
+            BufferedReader localBufferedReader = null;
+            if (localFileReader != null) {
+                try {
+                    localBufferedReader = new BufferedReader(localFileReader, 1024);
+                    str = localBufferedReader.readLine();
+                } finally {
+                    if (localFileReader != null) {
+                        try {
+                            localFileReader.close();
+                        } catch (Throwable localThrowable4) {
+                        }
+                    }
+                    if (localBufferedReader != null) {
+                        try {
+                            localBufferedReader.close();
+                        } catch (Throwable localThrowable5) {
+                        }
+                    }
+                }
+            }
+            return str;
+        } catch (Throwable localThrowable1) {
+        }
+        return str;
+    }
+
+    /**
+     * 获取IMEI
+     *
+     * @param paramContext
+     * @return
+     */
+    @SuppressLint({"MissingPermission"})
+    public static String getImei(Context paramContext) {
+        String str = "";
+        try {
+            if (paramContext != null) {
+                TelephonyManager localTelephonyManager = (TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE);
+                if ((localTelephonyManager != null) &&
+                        (checkPermission(paramContext, Manifest.permission.READ_PHONE_STATE))) {
+                    if (Build.VERSION.SDK_INT >= 26) {
+                        try {
+                            Method localMethod = localTelephonyManager.getClass().getMethod("getImei", new Class[0]);
+                            localMethod.setAccessible(true);
+                            str = (String) localMethod.invoke(localTelephonyManager, new Object[0]);
+                        } catch (Exception localException2) {
+                        }
+                        if (TextUtils.isEmpty(str)) {
+                            str = localTelephonyManager.getDeviceId();
+                        }
+                    } else {
+                        str = localTelephonyManager.getDeviceId();
+                    }
+                }
+            }
+        } catch (Exception localException1) {
+        }
+        return str;
+    }
+
+    /**
+     * 获取IMSI
+     *
+     * @param paramContext
+     * @return
+     */
+    @SuppressLint({"MissingPermission"})
+    public static String getImsi(Context paramContext) {
+        if (paramContext == null) {
+            return null;
+        }
+        TelephonyManager localTelephonyManager = (TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE);
+        String str = null;
+        if (checkPermission(paramContext, Manifest.permission.READ_PHONE_STATE)) {
+            str = localTelephonyManager.getSubscriberId();
+        }
+        return str;
+    }
+
+    /**
+     * 获取MEID
+     *
+     * @param paramContext
+     * @return
+     */
+    @SuppressLint({"MissingPermission"})
+    public static String getMeid(Context paramContext) {
+        if (paramContext == null) {
+            return "";
+        }
+        String str = "";
+        TelephonyManager localTelephonyManager = (TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE);
+        if ((checkPermission(paramContext, Manifest.permission.READ_PHONE_STATE)) &&
+                (localTelephonyManager != null)) {
+            if (Build.VERSION.SDK_INT < 26) {
+                str = localTelephonyManager.getDeviceId();
+            } else {
+                try {
+                    str = getMeidReal(paramContext);
+                    if (TextUtils.isEmpty(str)) {
+                        str = localTelephonyManager.getDeviceId();
+                    }
+                } catch (Throwable localThrowable) {
+                }
+            }
+        }
+        return str;
+    }
+
+    private static String getMeidReal(Context paramContext) {
+        if (paramContext == null) {
+            return "";
+        }
+        String str = "";
+        try {
+            Class localClass = Class.forName("android.telephony.TelephonyManager");
+            Method localMethod = localClass.getMethod("getMeid", new Class[0]);
+            Object localObject = localMethod.invoke(null, new Object[0]);
+            if ((null != localObject) && ((localObject instanceof String))) {
+                str = (String) localObject;
+            }
+        } catch (Exception localException) {
+        }
+        return str;
+    }
+
+    private static String getNotNullStr(Object input) {
+        return input == null ? "" : input.toString();
+    }
+
+
+    /**
+     * android id,64位
+     *
+     * @param context
+     * @return
+     */
+    public static String getAndroidId(Context context) {
+        String androidId = Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
+        return getNotNullStr(androidId);
+    }
+
+    /**
+     * 自动生成一个IMEI号码
+     *
+     * @return
+     */
+    private static String getPhoneUUID() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("35"); //+ //we make this look like a valid IMEI
+        sb.append(getNotNullStr(Build.BOARD.length() % 10));
+        sb.append(getNotNullStr(Build.BRAND.length() % 10));
+        sb.append(getNotNullStr(Build.CPU_ABI.length() % 10));
+        sb.append(getNotNullStr(Build.DEVICE.length() % 10));
+        sb.append(getNotNullStr(Build.DISPLAY.length() % 10));
+        sb.append(getNotNullStr(Build.HOST.length() % 10));
+        sb.append(getNotNullStr(Build.ID.length() % 10));
+        sb.append(getNotNullStr(Build.MANUFACTURER.length() % 10));
+        sb.append(getNotNullStr(Build.MODEL.length() % 10));
+        sb.append(getNotNullStr(Build.PRODUCT.length() % 10));
+        sb.append(getNotNullStr(Build.TAGS.length() % 10));
+        sb.append(getNotNullStr(Build.TYPE.length() % 10));
+        sb.append(getNotNullStr(Build.USER.length() % 10)); //13 digits
+        String imei = sb.toString();
+        return imei;
+    }
+
+    /**
+     * 获取屏幕信息
+     *
+     * @param context
+     * @return
+     */
+    public static String getScreenInfo(Context context) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        return displayMetrics.heightPixels + "*" + displayMetrics.widthPixels;
+    }
+
+    /**
+     * 获取网络方式 4G/3G/2G/WIFI
+     *
+     * @param context
+     * @return
+     */
+    @SuppressLint("MissingPermission")
+    public static String getNetworkState(Context context) {
+        String strNetworkType = "unknown";
+        ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); // 获取网络服务
+        if (!checkPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) {
+            return strNetworkType;
+        }
+        NetworkInfo networkInfo = connManager.getActiveNetworkInfo();
+        if (networkInfo != null && networkInfo.isAvailable()) {
+            if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                strNetworkType = "WIFI";
+            } else if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
+                String _strSubTypeName = networkInfo.getSubtypeName();
+
+                // TD-SCDMA   networkType is 17
+                int networkType = networkInfo.getSubtype();
+                switch (networkType) {
+                    case TelephonyManager.NETWORK_TYPE_GPRS:
+                    case TelephonyManager.NETWORK_TYPE_EDGE:
+                    case TelephonyManager.NETWORK_TYPE_CDMA:
+                    case TelephonyManager.NETWORK_TYPE_1xRTT:
+                    case TelephonyManager.NETWORK_TYPE_IDEN: //api<8 : replace by 11
+                        strNetworkType = "2G";
+                        break;
+                    case TelephonyManager.NETWORK_TYPE_UMTS:
+                    case TelephonyManager.NETWORK_TYPE_EVDO_0:
+                    case TelephonyManager.NETWORK_TYPE_EVDO_A:
+                    case TelephonyManager.NETWORK_TYPE_HSDPA:
+                    case TelephonyManager.NETWORK_TYPE_HSUPA:
+                    case TelephonyManager.NETWORK_TYPE_HSPA:
+                    case TelephonyManager.NETWORK_TYPE_EVDO_B: //api<9 : replace by 14
+                    case TelephonyManager.NETWORK_TYPE_EHRPD:  //api<11 : replace by 12
+                    case TelephonyManager.NETWORK_TYPE_HSPAP:  //api<13 : replace by 15
+                        strNetworkType = "3G";
+                        break;
+                    case TelephonyManager.NETWORK_TYPE_LTE:    //api<11 : replace by 13
+                        strNetworkType = "4G";
+                        break;
+                    default:
+                        // http://baike.baidu.com/item/TD-SCDMA 中国移动 联通 电信 三种3G制式
+                        if (_strSubTypeName.equalsIgnoreCase("TD-SCDMA")
+                                || _strSubTypeName.equalsIgnoreCase("WCDMA")
+                                || _strSubTypeName.equalsIgnoreCase("CDMA2000")) {
+                            strNetworkType = "3G";
+                        } else {
+                            strNetworkType = _strSubTypeName;
+                        }
+
+                        break;
+                }
+            }
+        }
+        return strNetworkType;
+    }
+
+    /**
+     * @return 当前APK版本号
+     */
+    public static long getVersionCode(Context context) {
+        try {
+            PackageManager manager = context.getPackageManager();
+            PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
+            //如果SDK>=28,返回long型的
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                return info.getLongVersionCode();
+            }
+            return info.versionCode;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return 1;
+    }
+
+
+    /**
+     * @return 当前APK版本名
+     */
+    public static String getVersionName(Context context) {
+        try {
+            PackageManager manager = context.getPackageManager();
+            PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
+            return info.versionName;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "0";
+    }
+
+    /**
+     * 权限校验
+     *
+     * @param context
+     * @param permission
+     * @return
+     */
+    public static boolean checkPermission(Context context, String permission) {
+        boolean checked = false;
+        if (context == null) {
+            return false;
+        }
+        if (Build.VERSION.SDK_INT >= 23) {
+            try {
+                Class localClass = Class.forName("android.content.Context");
+                Method method = localClass.getMethod("checkSelfPermission", new Class[]{String.class});
+                int result = ((Integer) method.invoke(context, new Object[]{permission})).intValue();
+                if (result == PackageManager.PERMISSION_GRANTED) {
+                    checked = true;
+                } else {
+                    checked = false;
+                }
+            } catch (Exception localException) {
+                checked = false;
+            }
+        } else {
+            PackageManager packageManager = context.getPackageManager();
+            if (packageManager.checkPermission(permission, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+                checked = true;
+            }
+        }
+        return checked;
+    }
+}

+ 113 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/PushInfoUtil.java

@@ -0,0 +1,113 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.model.NotificationMessageModel;
+
+import org.json.JSONObject;
+
+import java.util.HashMap;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 点击通知栏后获取服务器传来的信息
+ * </p>
+ */
+public class PushInfoUtil {
+
+    //华为推送接收数据
+    public static NotificationMessageModel getHWIntentData(Intent intent) {
+        NotificationMessageModel hwNotificationMessage = new NotificationMessageModel();
+        if (null != intent) {
+            // 获取的值做打点统计
+//            String msgid = intent.getStringExtra("_push_msgid");
+//            String cmdType = intent.getStringExtra("_push_cmd_type");
+//            int notifyId = intent.getIntExtra("_push_notifyid", -1);
+
+            Bundle bundle = intent.getExtras();
+            if (bundle != null) {
+                HashMap<String, String> map = new HashMap<>();
+                for (String key : bundle.keySet()) {
+                    String content = String.valueOf(bundle.get(key));
+                    map.put(key, content);
+                    AppSpPushLog.i("hw receive data from push, key = " + key + ", content = " + content);
+                }
+                JSONObject json = new JSONObject(map);
+                hwNotificationMessage.setNotificationExtras(json.toString());
+            }
+        }
+        return hwNotificationMessage;
+    }
+
+    //小米
+    public static NotificationMessageModel getXMIntentData(Intent intent) {
+        NotificationMessageModel xmNotificationMessage = new NotificationMessageModel();
+        if (null != intent) {
+            Bundle bundle = intent.getExtras();
+            if (bundle != null) {
+                xmNotificationMessage = (NotificationMessageModel) bundle.getSerializable(AppSpPushConstant.HNOTIFICATIONMESSAGE);
+            }
+        }
+        return xmNotificationMessage;
+    }
+
+    //vivo
+    public static NotificationMessageModel getVivoIntentData(Intent intent) {
+        NotificationMessageModel vivoNotificationMessage = new NotificationMessageModel();
+        //获取自定义透传参数值
+        if (null != intent) {
+            HashMap<String, String> map = new HashMap<>();
+            Bundle bundle = intent.getExtras();
+            if (bundle != null) {
+                for (String key : bundle.keySet()) {
+                    String content = String.valueOf(bundle.get(key));
+                    map.put(key, content);
+                    AppSpPushLog.i("vivo receive data from push, key = " + key + ", content = " + content);
+                }
+                JSONObject json = new JSONObject(map);
+                vivoNotificationMessage.setNotificationExtras(json.toString());
+            }
+        }
+        return vivoNotificationMessage;
+    }
+
+    //oppo
+    public static NotificationMessageModel getOppoIntentData(Intent intent) {
+        NotificationMessageModel oppoNotificationMessage = new NotificationMessageModel();
+        //获取自定义透传参数值
+        if (null != intent) {
+            HashMap<String, String> map = new HashMap<>();
+            Bundle bundle = intent.getExtras();
+            if (bundle != null) {
+                for (String key : bundle.keySet()) {
+                    String content = String.valueOf(bundle.get(key));
+                    map.put(key, content);
+                    AppSpPushLog.i("oppo receive data from push, key = " + key + ", content = " + content);
+                }
+                JSONObject json = new JSONObject(map);
+                oppoNotificationMessage.setNotificationExtras(json.toString());
+            }
+        }
+        return oppoNotificationMessage;
+    }
+
+
+    //极光
+    public static NotificationMessageModel getJPushIntentData(Intent intent) {
+        NotificationMessageModel notificationMessageModel = new NotificationMessageModel();
+        if (null != intent) {
+            Bundle bundle = intent.getExtras();
+            if (bundle != null) {
+                notificationMessageModel = (NotificationMessageModel) bundle.getSerializable(AppSpPushConstant.HNOTIFICATIONMESSAGE);
+            }
+        }
+        return notificationMessageModel;
+    }
+}

+ 167 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/RSAUtil.java

@@ -0,0 +1,167 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import java.io.ByteArrayOutputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+
+public class RSAUtil {
+
+    /**
+     * RSA最大加密明文大小
+     */
+    private static final int MAX_ENCRYPT_BLOCK = 117;
+
+    /**
+     * RSA最大解密密文大小
+     */
+    private static final int MAX_DECRYPT_BLOCK = 128;
+
+    /**
+     * 获取密钥对
+     *
+     * @return 密钥对
+     */
+    public static KeyPair getKeyPair() throws Exception {
+        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+        generator.initialize(1024);
+        return generator.generateKeyPair();
+    }
+
+    /**
+     * 获取私钥
+     *
+     * @param privateKey 私钥字符串
+     * @return
+     */
+    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        byte[] decodedKey = Base64Util.decode(privateKey);
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
+        return keyFactory.generatePrivate(keySpec);
+    }
+
+    /**
+     * 获取公钥
+     *
+     * @param publicKey 公钥字符串
+     * @return
+     */
+    public static PublicKey getPublicKey(String publicKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        byte[] decodedKey = Base64Util.decode(publicKey);
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
+        return keyFactory.generatePublic(keySpec);
+    }
+
+    /**
+     * RSA加密
+     *
+     * @param data      待加密数据
+     * @param publicKey 公钥
+     * @return
+     */
+    public static String encrypt(String data, PublicKey publicKey) throws Exception {
+        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        int inputLen = data.getBytes().length;
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int offset = 0;
+        byte[] cache;
+        int i = 0;
+        // 对数据分段加密
+        while (inputLen - offset > 0) {
+            if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
+                cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
+            } else {
+                cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
+            }
+            out.write(cache, 0, cache.length);
+            i++;
+            offset = i * MAX_ENCRYPT_BLOCK;
+        }
+        byte[] encryptedData = out.toByteArray();
+        out.close();
+        // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
+        // 加密后的字符串
+        return Base64Util.encode(encryptedData);
+    }
+
+    /**
+     * RSA解密
+     *
+     * @param data       待解密数据
+     * @param privateKey 私钥
+     * @return
+     */
+    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
+        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        byte[] dataBytes = Base64Util.decode(data);
+        int inputLen = dataBytes.length;
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int offset = 0;
+        byte[] cache;
+        int i = 0;
+        // 对数据分段解密
+        while (inputLen - offset > 0) {
+            if (inputLen - offset > MAX_DECRYPT_BLOCK) {
+                cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
+            } else {
+                cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
+            }
+            out.write(cache, 0, cache.length);
+            i++;
+            offset = i * MAX_DECRYPT_BLOCK;
+        }
+        byte[] decryptedData = out.toByteArray();
+        out.close();
+        // 解密后的内容
+        return new String(decryptedData, "UTF-8");
+    }
+
+    /**
+     * 签名
+     *
+     * @param data       待签名数据
+     * @param privateKey 私钥
+     * @return 签名
+     */
+    public static String sign(String data, PrivateKey privateKey) throws Exception {
+        byte[] keyBytes = privateKey.getEncoded();
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey key = keyFactory.generatePrivate(keySpec);
+        Signature signature = Signature.getInstance("MD5withRSA");
+        signature.initSign(key);
+        signature.update(data.getBytes());
+        return Base64Util.encode(signature.sign());
+    }
+
+    /**
+     * 验签
+     *
+     * @param srcData   原始字符串
+     * @param publicKey 公钥
+     * @param sign      签名
+     * @return 是否验签通过
+     */
+    public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
+        byte[] keyBytes = publicKey.getEncoded();
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey key = keyFactory.generatePublic(keySpec);
+        Signature signature = Signature.getInstance("MD5withRSA");
+        signature.initVerify(key);
+        signature.update(srcData.getBytes());
+        return signature.verify(Base64Util.decode(sign));
+    }
+
+}

+ 134 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/util/SPUtil.java

@@ -0,0 +1,134 @@
+package com.anji.plus.ajpushlibrary.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 信息保存
+ * </p>
+ */
+public class SPUtil {
+    /**
+     * 保存在手机里面的文件名
+     */
+    public static final String FILE_NAME = "share_data";
+
+
+    /**
+     * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
+     */
+
+    public static void put(Context context, String key, Object object) {
+        SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sp.edit();
+
+        if (object instanceof String) {
+            editor.putString(key, (String) object);
+        } else if (object instanceof Integer) {
+            editor.putInt(key, (Integer) object);
+        } else if (object instanceof Boolean) {
+            editor.putBoolean(key, (Boolean) object);
+        } else if (object instanceof Float) {
+            editor.putFloat(key, (Float) object);
+        } else if (object instanceof Long) {
+            editor.putLong(key, (Long) object);
+        } else {
+            editor.putString(key, object.toString());
+        }
+
+        SharedPreferencesCompat.apply(editor);
+    }
+
+    /**
+     * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
+     */
+
+    public static Object get(Context context, String key, Object defaultObject) {
+        SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+                Context.MODE_PRIVATE);
+
+        if (defaultObject instanceof String) {
+            return sp.getString(key, (String) defaultObject);
+        } else if (defaultObject instanceof Integer) {
+            return sp.getInt(key, (Integer) defaultObject);
+        } else if (defaultObject instanceof Boolean) {
+            return sp.getBoolean(key, (Boolean) defaultObject);
+        } else if (defaultObject instanceof Float) {
+            return sp.getFloat(key, (Float) defaultObject);
+        } else if (defaultObject instanceof Long) {
+            return sp.getLong(key, (Long) defaultObject);
+        }
+
+        return null;
+    }
+
+    /**
+     * 移除某个key值已经对应的值
+     */
+
+    public static void remove(Context context, String key) {
+        SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sp.edit();
+        editor.remove(key);
+        SharedPreferencesCompat.apply(editor);
+    }
+
+    /**
+     * 清除所有数据
+     */
+
+    public static void clear(Context context) {
+        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sp.edit();
+        editor.clear();
+        SharedPreferencesCompat.apply(editor);
+    }
+
+    /**
+     * 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类
+     */
+    private static class SharedPreferencesCompat {
+        private static final Method sApplyMethod = findApplyMethod();
+
+        /**
+         * 反射查找apply的方法
+         */
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        private static Method findApplyMethod() {
+            try {
+                Class clz = SharedPreferences.Editor.class;
+                return clz.getMethod("apply");
+            } catch (NoSuchMethodException e) {
+            }
+
+            return null;
+        }
+
+        /**
+         * 如果找到则使用apply执行,否则使用commit
+         */
+        public static void apply(SharedPreferences.Editor editor) {
+            try {
+                if (sApplyMethod != null) {
+                    sApplyMethod.invoke(editor);
+                    return;
+                }
+            } catch (IllegalArgumentException e) {
+            } catch (IllegalAccessException e) {
+            } catch (InvocationTargetException e) {
+            }
+            editor.commit();
+        }
+    }
+
+}

+ 27 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/vivo/VivoPushReceiver.java

@@ -0,0 +1,27 @@
+package com.anji.plus.ajpushlibrary.vivo;
+
+import android.content.Context;
+
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.vivo.push.model.UPSNotificationMessage;
+import com.vivo.push.sdk.OpenClientPushMessageReceiver;
+
+
+public class VivoPushReceiver extends OpenClientPushMessageReceiver {
+    /**
+     * TAG to Log
+     */
+
+    @Override
+    public void onNotificationMessageClicked(Context context, UPSNotificationMessage msg) {
+        String customContentString = msg.getSkipContent();
+        String notifyString = "通知点击 msgId " + msg.getMsgId() + " ;customContent=" + customContentString;
+        AppSpPushLog.d(notifyString);
+    }
+
+    @Override
+    public void onReceiveRegId(Context context, String regId) {
+        String responseString = "onReceiveRegId regId = " + regId;
+        AppSpPushLog.d(responseString);
+    }
+}

+ 133 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/vivo/VivoPushUtil.java

@@ -0,0 +1,133 @@
+package com.anji.plus.ajpushlibrary.vivo;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+import com.anji.plus.ajpushlibrary.service.IAppSpCallback;
+import com.vivo.push.IPushActionListener;
+import com.vivo.push.PushClient;
+
+public class VivoPushUtil {
+    private Context context;
+
+    public VivoPushUtil(Context context) {
+        this.context = context;
+    }
+
+    //用于启动打开push开关
+    public void bind() {
+        PushClient.getInstance(context).turnOnPush(new IPushActionListener() {
+
+            @Override
+            public void onStateChanged(int state) {
+                if (state != 0) {
+                    AppSpPushLog.e("打开push异常" + state);
+                } else {
+                    AppSpPushLog.e("打开push成功" + state);
+
+                    AppSpPushConstant.pushToken = PushClient.getInstance(context).getRegId();
+
+                    Log.i("vivo的id",PushClient.getInstance(context).getRegId());
+
+
+
+                    AppSpPushConfig.getInstance().sendRegTokenToServer(new IAppSpCallback() {
+                        @Override
+                        public void pushInfo(AppSpModel<String> appSpModel) {
+
+                        }
+
+                        @Override
+                        public void error(String code, String msg) {
+
+                        }
+                    });
+                }
+            }
+        });
+
+    }
+
+    public void unbind(View v) {
+        PushClient.getInstance(context).turnOffPush(new IPushActionListener() {
+
+            @Override
+            public void onStateChanged(int state) {
+                if (state != 0) {
+                    AppSpPushLog.e("关闭push异常" + state);
+                } else {
+                    AppSpPushLog.e("关闭push成功" + state);
+                }
+            }
+        });
+    }
+
+    // 删除别名
+    public void delAlias(String alias) {
+        PushClient.getInstance(context).unBindAlias(alias, new IPushActionListener() {
+
+            @Override
+            public void onStateChanged(int state) {
+                if (state != 0) {
+                    AppSpPushLog.e("取消别名异常" + state);
+                } else {
+                    AppSpPushLog.e("取消别名成功" + state);
+                }
+            }
+        });
+    }
+
+    // 设置别名
+    public void setAlias(String alias) {
+        PushClient.getInstance(context).bindAlias(alias, new IPushActionListener() {
+
+            @Override
+            public void onStateChanged(int state) {
+                if (state != 0) {
+                    AppSpPushLog.e("设置别名异常" + state);
+                } else {
+                    AppSpPushLog.e("设置别名成功" + state);
+                }
+            }
+        });
+    }
+
+    // 设置标签
+    public void setTopic(String topic) {
+        PushClient.getInstance(context).setTopic(topic, new IPushActionListener() {
+
+            @Override
+            public void onStateChanged(int state) {
+                if (state != 0) {
+                    AppSpPushLog.e("设置标签异常" + state);
+                } else {
+                    AppSpPushLog.e("设置标签成功" + state);
+                }
+            }
+        });
+    }
+
+    // 删除标签
+    public void delTopic(String topic) {
+        // Push: 删除标签调用方式
+        PushClient.getInstance(context).delTopic(topic, new IPushActionListener() {
+
+            @Override
+            public void onStateChanged(int state) {
+                if (state != 0) {
+                    AppSpPushLog.e("删除标签异常" + state);
+                } else {
+                    AppSpPushLog.e("删除标签成功" + state);
+                }
+            }
+        });
+    }
+
+}
+
+

+ 184 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/xm/XMPushReceiver.java

@@ -0,0 +1,184 @@
+package com.anji.plus.ajpushlibrary.xm;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.model.AppSpModel;
+import com.anji.plus.ajpushlibrary.service.IAppSpCallback;
+import com.anji.plus.ajpushlibrary.util.MessageUtil;
+import com.anji.plus.ajpushlibrary.util.SPUtil;
+import com.xiaomi.mipush.sdk.ErrorCode;
+import com.xiaomi.mipush.sdk.MiPushClient;
+import com.xiaomi.mipush.sdk.MiPushCommandMessage;
+import com.xiaomi.mipush.sdk.MiPushMessage;
+import com.xiaomi.mipush.sdk.PushMessageReceiver;
+
+import org.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 1、PushMessageReceiver 是个抽象类,该类继承了 BroadcastReceiver。<br/>
+ * 2、需要将自定义的 DemoMessageReceiver 注册在 AndroidManifest.xml 文件中:
+ * <pre>
+ * {@code
+ *  <receiver
+ *      android:name="com.xiaomi.mipushdemo.DemoMessageReceiver"
+ *      android:exported="true">
+ *      <intent-filter>
+ *          <action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
+ *      </intent-filter>
+ *      <intent-filter>
+ *          <action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
+ *      </intent-filter>
+ *      <intent-filter>
+ *          <action android:name="com.xiaomi.mipush.ERROR" />
+ *      </intent-filter>
+ *  </receiver>
+ *  }</pre>
+ * 3、DemoMessageReceiver 的 onReceivePassThroughMessage 方法用来接收服务器向客户端发送的透传消息。<br/>
+ * 4、DemoMessageReceiver 的 onNotificationMessageClicked 方法用来接收服务器向客户端发送的通知消息,
+ * 这个回调方法会在用户手动点击通知后触发。<br/>
+ * 5、DemoMessageReceiver 的 onNotificationMessageArrived 方法用来接收服务器向客户端发送的通知消息,
+ * 这个回调方法是在通知消息到达客户端时触发。另外应用在前台时不弹出通知的通知消息到达客户端也会触发这个回调函数。<br/>
+ * 6、DemoMessageReceiver 的 onCommandResult 方法用来接收客户端向服务器发送命令后的响应结果。<br/>
+ * 7、DemoMessageReceiver 的 onReceiveRegisterResult 方法用来接收客户端向服务器发送注册命令后的响应结果。<br/>
+ * 8、以上这些方法运行在非 UI 线程中。
+ *
+ */
+public class XMPushReceiver extends PushMessageReceiver {
+    private String mRegId;
+    private String mTopic;
+    private String mAlias;
+    private String mAccount;
+    private String mStartTime;
+    private String mEndTime;
+
+    @Override
+    public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
+        //接收服务器推送的透传消息,消息封装在 MiPushMessage类中
+        AppSpPushLog.i("onReceivePassThroughMessage is called. " + message.toString());
+        if (!TextUtils.isEmpty(message.getTopic())) {
+            mTopic = message.getTopic();
+        } else if (!TextUtils.isEmpty(message.getAlias())) {
+            mAlias = message.getAlias();
+        } else if (!TextUtils.isEmpty(message.getUserAccount())) {
+            mAccount = message.getUserAccount();
+        }
+        JSONObject json = new JSONObject(message.getExtra());
+        MessageUtil.onMessageArrived(context, message.getMessageId(), message.getTitle(), message.getContent(), json.toString());
+    }
+
+    @Override
+    public void onNotificationMessageClicked(Context context, MiPushMessage message) {
+        //接收服务器推送的通知消息,用户点击后触发,消息封装在 MiPushMessage类中
+        AppSpPushLog.i("onNotificationMessageClicked is called. " + message.toString());
+
+        if (!TextUtils.isEmpty(message.getTopic())) {
+            mTopic = message.getTopic();
+        } else if (!TextUtils.isEmpty(message.getAlias())) {
+            mAlias = message.getAlias();
+        } else if (!TextUtils.isEmpty(message.getUserAccount())) {
+            mAccount = message.getUserAccount();
+        }
+
+        //todo 点击通知栏消息,发送广播给打开自定义页面
+//        notificationMessageModel.setMessageType(3);
+        SPUtil.put(context, "messsageId", message.getMessageId());
+        JSONObject json = new JSONObject(message.getExtra());
+        MessageUtil.onMessageClicked(context, message.getTitle(), message.getContent(), json.toString());
+    }
+
+    @Override
+    public void onNotificationMessageArrived(Context context, MiPushMessage message) {
+        //接收服务器推送的通知消息,消息到达客户端时触发,还可以接受应用在前台时不弹出通知的通知消息,消息封装在 MiPushMessage类中
+        AppSpPushLog.i("onNotificationMessageArrived is called. " + message.toString());
+
+        if (!TextUtils.isEmpty(message.getTopic())) {
+            mTopic = message.getTopic();
+        } else if (!TextUtils.isEmpty(message.getAlias())) {
+            mAlias = message.getAlias();
+        } else if (!TextUtils.isEmpty(message.getUserAccount())) {
+            mAccount = message.getUserAccount();
+        }
+//        notificationMessageModel.setMessageType(3);
+        JSONObject json = new JSONObject(message.getExtra());
+        MessageUtil.onMessageArrived(context, message.getMessageId(), message.getTitle(), message.getContent(), json.toString());
+    }
+
+    @Override
+    public void onCommandResult(Context context, MiPushCommandMessage message) {
+        //获取给服务器发送命令的结果,结果封装在MiPushCommandMessage类中
+        AppSpPushLog.i("onCommandResult is called. " + message.toString());
+        String command = message.getCommand();
+        List<String> arguments = message.getCommandArguments();
+        String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
+        String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);
+        if (MiPushClient.COMMAND_REGISTER.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mRegId = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_SET_ALIAS.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mAlias = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_UNSET_ALIAS.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mAlias = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_SET_ACCOUNT.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mAccount = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_UNSET_ACCOUNT.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mAccount = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_SUBSCRIBE_TOPIC.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mTopic = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mTopic = cmdArg1;
+            }
+        } else if (MiPushClient.COMMAND_SET_ACCEPT_TIME.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mStartTime = cmdArg1;
+                mEndTime = cmdArg2;
+            }
+        }
+    }
+
+    @Override
+    public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
+        //获取给服务器发送注册命令的结果,结果封装在MiPushCommandMessage类中
+        AppSpPushLog.i("onReceiveRegisterResult is called. " + message.toString());
+        String command = message.getCommand();
+        List<String> arguments = message.getCommandArguments();
+        String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
+        String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);
+        if (MiPushClient.COMMAND_REGISTER.equals(command)) {
+            if (message.getResultCode() == ErrorCode.SUCCESS) {
+                mRegId = cmdArg1;
+                //收到regId之后,可以设置alias、userAccount、topic,将数据传给服务器
+                AppSpPushConstant.pushToken = mRegId;
+                AppSpPushConfig.getInstance().sendRegTokenToServer(new IAppSpCallback() {
+                    @Override
+                    public void pushInfo(AppSpModel<String> appSpModel) {
+
+                    }
+
+                    @Override
+                    public void error(String code, String msg) {
+
+                    }
+                });
+            }
+        }
+
+    }
+}

+ 64 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/java/com/anji/plus/ajpushlibrary/xm/XMPushUtil.java

@@ -0,0 +1,64 @@
+package com.anji.plus.ajpushlibrary.xm;
+
+import android.content.Context;
+
+import com.xiaomi.mipush.sdk.MiPushClient;
+
+/**
+ * Created by wuyan on 2020/12/7.
+ */
+public class XMPushUtil {
+    private static final String TAG = "XMPushUtil";
+    private Context context;
+
+    public XMPushUtil(Context context) {
+        this.context = context;
+    }
+
+    //设置别名
+    public void setAlias(String alias) {
+        MiPushClient.setAlias(context, alias, null);
+    }
+
+    //撤销别名
+    public void unsetAlias(String alias) {
+        MiPushClient.unsetAlias(context, alias, null);
+    }
+
+    //设置帐号
+    public void setAccount(String account) {
+        MiPushClient.setUserAccount(context, account, null);
+    }
+
+    //撤销帐号
+    public void unsetAccount(String account) {
+        MiPushClient.unsetUserAccount(context, account, null);
+    }
+
+    //设置标签,不同手机上的同一个app可以订阅同一个主题
+    public void subscribeTopic(String topic) {
+        MiPushClient.subscribe(context, topic, null);
+    }
+
+    //撤销标签
+    public void unsubscribeTopic(String topic) {
+        MiPushClient.unsubscribe(context, topic, null);
+    }
+
+    //暂停推送
+    public void pausePush() {
+        MiPushClient.pausePush(context, null);
+    }
+
+    //恢复推送
+    public void resumePush() {
+        MiPushClient.resumePush(context, null);
+    }
+
+    public String getRegId(Context context) {
+        return MiPushClient.getRegId(context);
+    }
+
+}
+
+

+ 3 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/main/res/values/strings.xml

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

+ 17 - 0
demo/android/push/SimplePushDemo/ajpushlibrary/src/test/java/com/anji/plus/ajpushlibrary/ExampleUnitTest.java

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

+ 1 - 0
demo/android/push/SimplePushDemo/app/.gitignore

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

+ 14 - 0
demo/android/push/SimplePushDemo/app/agconnect-services.json

@@ -0,0 +1,14 @@
+{
+	"client":{
+		"appType":"1",
+		"cp_id":"890086000102023564",
+		"product_id":"736430079244730478",
+		"client_id":"512221520127329344",
+		"client_secret":"7B9AAD35C97F7C82EE2FDEA4606F2D93C1FE7DDCFD6700CBB4FE896D31C91CE5",
+		"project_id":"736430079244730478",
+		"app_id":"103436069",
+		"api_key":"CgB6e3x9ct59JPA6AqN3ueD4kjLtd4GB0xOxhx1ZEc0Safm4CJQwJ/l5M2EjTk4zWeYQwlAP8XdjM9Rt/iq7zd6I",
+		"package_name":"com.anji.plus.pushdemo"
+	},
+	"configuration_version":"1.0"
+}

+ 69 - 0
demo/android/push/SimplePushDemo/app/build.gradle

@@ -0,0 +1,69 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.huawei.agconnect'
+
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.3"
+    defaultConfig {
+        applicationId "com.anji.plus.pushdemo"
+        minSdkVersion 21
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+        flavorDimensions "versionCode"
+
+
+        //统一配置
+        manifestPlaceholders = [
+                "JPUSH_PKGNAME" : applicationId,
+                "JPUSH_APPKEY"  : "66c9266fc53b8025cb0dd919", //极光开发平台上注册的包名对应的appkey.
+                "JPUSH_CHANNEL" : "developer-default", //暂时填写默认值即可.
+                "appspHost"     : "https://openappsp.anji-plus.com",//push请求的host
+                "appspAppKey"   : "3fffc7ab4e704965b3d7ebc034addef7",//为移动服务平台申请的appkey
+                "appspSecretKey": "12d606b4ab5b496992c48048e2a035f1",//为移动服务平台申请的appspSecretKey
+                "vivoAppId"     : "105303997",//vivo 厂商通道 appID
+                "vivoAppKey"    : "ecaa359c8bbe601d7bbc86d29f5cc58e",//vivo 厂商通道 appKey
+                "xmAppId"       : "\\ 2882303761518880022",//小米 厂商通道 appID,记得如果是长数,前面加“\\ ”,有空格
+                "xmAppKey"      : "\\ 5161888025022",//小米 厂商通道 appKey,记得如果是长数,前面加“\\ ”,有空格
+                "oppoAppKey"    : "c160669462604212962064ffa2df36af",//oppo 厂商通道 appKey
+                "oppoAppSecret" : "b77b1509f99043e3b695795366e929ef"//oppo 厂商通道 appSecret
+        ]
+
+    }
+
+    signingConfigs {
+        config {
+            keyAlias 'pushdemo'
+            keyPassword '123456'
+            storeFile file('pushDemo.jks')
+            storePassword '123456'
+        }
+    }
+
+    buildTypes {
+        debug {
+            signingConfig signingConfigs.config
+        }
+        release {
+            signingConfig signingConfigs.config
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.2.1'
+    implementation project(path: ':ajpushlibrary')
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+
+
+}

+ 36 - 0
demo/android/push/SimplePushDemo/app/proguard-rules.pro

@@ -0,0 +1,36 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep class com.anji.plus.ajpushlibrary.** {*;}
+-dontwarn com.anji.plus.ajpushlibrary.**
+
+-keep class com.huawei.** {*;}
+-dontwarn com.huawei.**
+
+-keep class cn.jiguang.** {*;}
+-dontwarn cn.jiguang.**
+
+-keep class cn.jpush.** {*;}
+-dontwarn cn.jpush.**
+
+-keep class com.heytap.** {*;}
+-dontwarn com.heytap.**

BIN
demo/android/push/SimplePushDemo/app/pushDemo.jks


+ 26 - 0
demo/android/push/SimplePushDemo/app/src/androidTest/java/com/anji/plus/pushdemo/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.anji.plus.pushdemo;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.anji.plus.pushdemo", appContext.getPackageName());
+    }
+}

+ 78 - 0
demo/android/push/SimplePushDemo/app/src/main/AndroidManifest.xml

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.anji.plus.pushdemo">
+
+    <application
+        android:name=".appplication.BaseApplication"
+        android:allowBackup="false"
+        android:icon="@mipmap/app_logo"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.PushDemo"
+        tools:replace="android:allowBackup"
+        >
+        <activity android:name=".activity.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <!--华为的过滤条件-->
+            <intent-filter>
+                <!-- ‘name’值由您自定义 -->
+                <action android:name="com.push.demo.internal" />
+            </intent-filter>
+
+            <!--vivo的过滤条件-->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <!-- 路径由您自定义 -->
+                <data
+                    android:host="com.vivo.push.notifysdk"
+                    android:path="/detail"
+                    android:scheme="vpushscheme" />
+            </intent-filter>
+
+            <!--oppo的过滤条件-->
+            <intent-filter>
+                <!-- ‘name’值由您自定义 -->
+                <action android:name="com.push.demo.internal" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <!-- 配置项 -->
+        <meta-data
+            android:name="appspHost"
+            android:value="${appspHost}" />
+        <meta-data
+            android:name="appspAppKey"
+            android:value="${appspAppKey}" />
+        <meta-data
+            android:name="appspSecretKey"
+            android:value="${appspSecretKey}" />
+        <meta-data
+            android:name="com.vivo.push.api_key"
+            android:value="${vivoAppKey}" />
+        <meta-data
+            android:name="com.vivo.push.app_id"
+            android:value="${vivoAppId}" />
+        <meta-data
+            android:name="xmAppId"
+            android:value="${xmAppId}" />
+        <meta-data
+            android:name="xmAppKey"
+            android:value="${xmAppKey}" />
+        <meta-data
+            android:name="oppoAppKey"
+            android:value="${oppoAppKey}" />
+        <meta-data
+            android:name="oppoAppSecret"
+            android:value="${oppoAppSecret}" />
+
+    </application>
+
+</manifest>

+ 41 - 0
demo/android/push/SimplePushDemo/app/src/main/java/com/anji/plus/pushdemo/SoundUtils.java

@@ -0,0 +1,41 @@
+package com.anji.plus.pushdemo;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.SoundPool;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * Appsp-Push 播放声音
+ * </p>
+ */
+public class SoundUtils {
+    public static SoundPool mSoundPlayer = new SoundPool(10,
+            AudioManager.STREAM_SYSTEM, 5);
+    public static SoundUtils soundPlayUtils;
+    static Context mContext;
+
+    /**
+     * 初始化
+     * @param context
+     */
+    public static SoundUtils init(Context context) {
+        if (soundPlayUtils == null) {
+            soundPlayUtils = new SoundUtils();
+        }
+        // 初始化声音
+        mContext = context;
+        mSoundPlayer.load(mContext, R.raw.hua_notice, 1);// 1
+        mSoundPlayer.load(mContext, R.raw.error, 1);// 2
+        return soundPlayUtils;
+    }
+
+    //播放声音
+    public static void play(int soundID) {
+        mSoundPlayer.play(soundID, 1, 1, 0, 0, 1);
+    }
+}

+ 134 - 0
demo/android/push/SimplePushDemo/app/src/main/java/com/anji/plus/pushdemo/activity/MainActivity.java

@@ -0,0 +1,134 @@
+package com.anji.plus.pushdemo.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.model.NotificationMessageModel;
+import com.anji.plus.ajpushlibrary.util.CommonUtil;
+import com.anji.plus.pushdemo.R;
+import com.anji.plus.pushdemo.SoundUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * 推送入口页面
+ * </p>
+ */
+public class MainActivity extends AppCompatActivity {
+    private MessageReceiver mMessageReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        //注册接收透传消息的广播
+        register();
+    }
+
+    private void register() {
+        mMessageReceiver = new MessageReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        filter.addAction(AppSpPushConstant.MESSAGE_RECEIVED_ACTION);
+        registerReceiver(mMessageReceiver, filter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mMessageReceiver);
+    }
+
+    //接收透传消息的广播
+    private class MessageReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(final Context context, Intent intent) {
+            try {
+                if (AppSpPushConstant.MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) {
+                    Bundle bundle = intent.getExtras();
+                    NotificationMessageModel notificationMessageModel = (NotificationMessageModel) bundle.getSerializable(AppSpPushConstant.HNOTIFICATIONMESSAGE);
+                    final String title = notificationMessageModel.getTitle();
+                    String messge = notificationMessageModel.getContent();
+                    final String extras = notificationMessageModel.getNotificationExtras();
+                    final StringBuilder showMsg = new StringBuilder();
+                    showMsg.append(messge + "\n");
+                    if (!CommonUtil.isEmpty(extras)) {
+                        showMsg.append("extras: " + extras + "\n");
+                    }
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            buildDialog(title, showMsg.toString());
+                            soundPlay(extras);
+                        }
+                    });
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        private void soundPlay(String extras) {
+            //获取推送消息中的sound的值,注意sound时与后端协商好的传送声音类型的key,如果后端改变key,此处也随之改变
+            JSONObject json = null;
+            String soundType = "";//从推送消息中获取的播放的声音类型
+            try {
+                json = new JSONObject(extras);
+                Iterator it = json.keys();
+                while (it.hasNext()) {
+                    String key = (String) it.next();
+                    if (key.equals("sound")) {
+                        soundType = json.get(key).toString();
+                    }
+                }
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+            //根据后端传来的数据值决定播放哪个声音
+            switch (soundType) {
+                case "hua_notice":
+                    SoundUtils.play(1);
+                    break;
+                default:
+                    SoundUtils.play(2);
+            }
+        }
+    }
+
+    private void buildDialog(String title, String msg) {
+        AlertDialog.Builder builder;
+        builder = new AlertDialog.Builder(this).setIcon(R.mipmap.ic_launcher).setTitle(title)
+                .setMessage(msg).setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        Toast.makeText(MainActivity.this, "确定按钮", Toast.LENGTH_LONG).show();
+                    }
+                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        Toast.makeText(MainActivity.this, "关闭按钮", Toast.LENGTH_LONG).show();
+                        dialogInterface.dismiss();
+                    }
+                });
+        builder.create().show();
+    }
+
+
+}

+ 73 - 0
demo/android/push/SimplePushDemo/app/src/main/java/com/anji/plus/pushdemo/appplication/BaseApplication.java

@@ -0,0 +1,73 @@
+package com.anji.plus.pushdemo.appplication;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.anji.plus.ajpushlibrary.AppSpPushConfig;
+import com.anji.plus.ajpushlibrary.AppSpPushLog;
+import com.anji.plus.ajpushlibrary.AppSpPushConstant;
+import com.anji.plus.ajpushlibrary.http.AppSpPushRequestUrl;
+import com.anji.plus.pushdemo.SoundUtils;
+
+/**
+ * Copyright © 2018 anji-plus
+ * 安吉加加信息技术有限公司
+ * http://www.anji-plus.com
+ * All rights reserved.
+ * <p>
+ * Appsp 推送
+ * </p>
+ */
+public class BaseApplication extends Application {
+    private Context mContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        SoundUtils.init(this);
+        mContext = getApplicationContext();
+        initConfiguration();
+        debugConfiguration();
+    }
+
+    ///从build.gradle读取配置
+    private void initConfiguration() {
+        try {
+            ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(),
+                    PackageManager.GET_META_DATA);
+            AppSpPushConstant.packageName = mContext.getPackageName();
+            AppSpPushRequestUrl.Host = AppSpPushConstant.appspHost = getMetaInfoByKey(appInfo, "appspHost");
+            AppSpPushConstant.appspAppKey = getMetaInfoByKey(appInfo, "appspAppKey");
+            AppSpPushConstant.appspSecretKey = getMetaInfoByKey(appInfo, "appspSecretKey");
+            AppSpPushConstant.oppoAppKey = getMetaInfoByKey(appInfo, "oppoAppKey");
+            AppSpPushConstant.oppoAppSecret = getMetaInfoByKey(appInfo, "oppoAppSecret");
+            AppSpPushConstant.xmAppId = getMetaInfoByKey(appInfo, "xmAppId");
+            AppSpPushConstant.xmAppKey = getMetaInfoByKey(appInfo, "xmAppKey");
+        } catch (Exception e) {
+
+        }
+        AppSpPushConfig.getInstance().init(mContext, AppSpPushConstant.appspAppKey, AppSpPushConstant.appspSecretKey, AppSpPushRequestUrl.Host + AppSpPushRequestUrl.putPushInfo);
+    }
+
+    private String getMetaInfoByKey(ApplicationInfo appInfo, String key) {
+        Object value = appInfo.metaData.get(key);
+        String valueStr = "";
+        if (value != null) {
+            valueStr = value.toString().trim();
+        }
+        return valueStr;
+    }
+
+    private void debugConfiguration() {
+        AppSpPushLog.d(" AppSpPushRequestUrl.Host " + AppSpPushRequestUrl.Host);
+        AppSpPushLog.d(" AppSpPushConstant.packageName " + AppSpPushConstant.packageName);
+        AppSpPushLog.d(" AppSpPushConstant.appspAppKey " + AppSpPushConstant.appspAppKey);
+        AppSpPushLog.d(" AppSpPushConstant.appspSecretKey " + AppSpPushConstant.appspSecretKey);
+        AppSpPushLog.d(" AppSpPushConstant.oppoAppKey " + AppSpPushConstant.oppoAppKey);
+        AppSpPushLog.d(" AppSpPushConstant.oppoAppSecret " + AppSpPushConstant.oppoAppSecret);
+        AppSpPushLog.d(" AppSpPushConstant.xmAppId " + AppSpPushConstant.xmAppId);
+        AppSpPushLog.d(" AppSpPushConstant.xmAppKey " + AppSpPushConstant.xmAppKey);
+    }
+}

Plik diff jest za duży
+ 30 - 0
demo/android/push/SimplePushDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 170 - 0
demo/android/push/SimplePushDemo/app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 12 - 0
demo/android/push/SimplePushDemo/app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="这是一个推送页面,静候通知到来吧~" />
+
+</FrameLayout>

+ 5 - 0
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xhdpi/app_logo.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


BIN
demo/android/push/SimplePushDemo/app/src/main/res/raw/error.mp3


BIN
demo/android/push/SimplePushDemo/app/src/main/res/raw/hua_notice.mp3


+ 16 - 0
demo/android/push/SimplePushDemo/app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.PushDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 10 - 0
demo/android/push/SimplePushDemo/app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 3 - 0
demo/android/push/SimplePushDemo/app/src/main/res/values/strings.xml

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

+ 16 - 0
demo/android/push/SimplePushDemo/app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.PushDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 17 - 0
demo/android/push/SimplePushDemo/app/src/test/java/com/anji/plus/pushdemo/ExampleUnitTest.java

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

+ 28 - 0
demo/android/push/SimplePushDemo/build.gradle

@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+        // huawei maven
+        maven { url 'https://developer.huawei.com/repo/' }
+    }
+    dependencies {
+        classpath "com.android.tools.build:gradle:4.1.0"
+        classpath 'com.huawei.agconnect:agcp:1.4.1.300'
+
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+        jcenter() // Warning: this repository is going to shut down soon
+        // huawei maven
+        maven { url 'https://developer.huawei.com/repo/' }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 17 - 0
demo/android/push/SimplePushDemo/gradle.properties

@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true

+ 6 - 0
demo/android/push/SimplePushDemo/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Fri Aug 27 10:41:23 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 172 - 0
demo/android/push/SimplePushDemo/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# 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
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# 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
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+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" -a "$nonstop" = "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
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
demo/android/push/SimplePushDemo/gradlew.bat

@@ -0,0 +1,84 @@
+@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
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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=
+
+@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 Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_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=%*
+
+: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

+ 3 - 0
demo/android/push/SimplePushDemo/settings.gradle

@@ -0,0 +1,3 @@
+rootProject.name = "PushDemo"
+include ':app'
+include  ':ajpushlibrary'

+ 211 - 0
demo/android/version/aj_android_appsp/README.md

@@ -0,0 +1,211 @@
+
+# 快速接入使用
+## 初始化服务
+AppKey的获取见 **操作手册**
+
+```java
+public class AppContext extends Application {
+    private static AppContext mInstance;
+    //appkey是在Appsp创建应用生成的
+    public static final String appKey = "aadcfae6215a4e0f9bf5bc5edccb1045";
+
+    public static AppContext getInstance() {
+        return mInstance;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mInstance = this;
+		//关键代码
+        AppSpConfig.getInstance()
+                .init(this, appKey)
+                //可修改基础请求地址
+                .setHost("https://openappsp.anji-plus.com/sp/")
+                //正式环境可以禁止日志输出,通过Tag APP-SP过滤看日志
+                .setDebuggable(BuildConfig.DEBUG ? true : false)
+                //务必要初始化,否则后面请求会报错
+                .deviceInit();
+    }
+
+}
+```
+
+## 版本更新服务
+* 权限
+
+```java
+    <!-- apk存储 -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <!-- 网络请求 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <!-- apk升级 -->
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <!-- 7.0+文件读取 com.anji.appsp.sdktest是Demo的包名-->
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.anji.appsp.sdktest.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+```
+
+* file_paths.xml代码
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<paths>
+    <external-path
+        name="files_root"
+        path="Android/data/com.anji.appsp.sdktest/" />
+    <external-path
+        name="external_storage_root"
+        path="." />
+    <root-path
+        name="root_path"
+        path="" />
+</paths>
+```
+
+* 调用
+
+ ```java
+ private void checkVersion() {
+        AppSpConfig.getInstance().setVersionUpdateCallback(new IAppSpVersionUpdateCallback() {
+            @Override
+            public void update(AppSpModel<AppSpVersion> spModel) {
+                //注意,当前是子线程
+                //成功处理 TODO
+            }
+
+            @Override
+            public void error(String code, String msg) {
+               //注意,当前是子线程
+			   //失败处理 TODO
+            }
+        });
+    }
+ ```
+
+**请求返回数据结构如下**
+
+**spModel** 数据详情
+
+ ```java
+  {
+  	"repCode": "0000", //业务返回码,0000表示成功
+  	"repMsg": "成功",  //业务日志
+  	"repData": {
+  		"downloadUrl": "app下载地址",
+  		"mustUpdate": false, //是否强制更新,true为强制更新
+  		"showUpdate": true, //是否允许弹出更新
+  		"updateLog": "更新日志"
+  	}
+  }
+ ```
+
+***
+
+| 字段| 类型| 说明 |
+ | :-----| :---- | :---- |
+| repCode |String | 业务返回码,0000表示成功  |
+| repMsg |String | 业务日志、异常信息 |
+| repData | Object | 请求业务数据包、详情见下 |
+
+**repData** 数据详情
+
+| 字段| 类型| 说明 |
+ | :-----| :---- | :---- |
+| downloadUrl |String | app下载地址 |
+| mustUpdate | boolean | 是否强制更新,true为强制更新; false为非强制更新 |
+| showUpdate | boolean | 是否提示更新:允许弹出更新 |
+| updateLog |String | 更新日志 |
+
+***
+
+**errorInfo** 数据详情
+
+ ```java
+ repCode: 抛出异常Code
+ repMsg: 异常信息
+ 
+ ```
+
+| 字段| 类型| 说明 |
+ | :-----| :---- | :---- |
+| repCode |String | 抛出异常Code ; 1001: 请求异常;1202: appKey为空;1203: appKey校验失败;1207: 系统版本号不能为空;1208: 应用版本号不能为空 |
+| repMsg |String | 异常信息 |
+
+### 公告服务
+   ```java
+    private void checkNotice() {
+        AppSpConfig.getInstance().getNotice(new IAppSpNoticeCallback() {
+            @Override
+            public void notice(AppSpModel<List<AppSpNoticeModelItem>> spModel) {
+                //注意,当前是子线程
+                //成功处理 TODO
+            }
+
+            @Override
+            public void error(String code, String msg) {
+                //注意,当前是子线程
+                //失败处理 TODO
+            }
+        });
+
+    }
+   ```
+**请求返回数据结构如下**
+
+***
+**spModel** 数据详情
+
+```java
+ {
+ 	"repCode": "0000", //业务返回码,0000表示成功
+ 	"repMsg": "成功", //业务日志
+ 	"repData": [ // 返回数据为 List
+ 		{
+	 		"title": "公告标题",
+	 		"details": "公告内容",
+	 		"templateType": "dialog", //公告类型( 弹窗:dialog; 水平滚动:horizontal_scroll)
+	 		"templateTypeName": "公告"//公告模板名称
+ 		}
+ 	]
+ }
+```
+
+| 字段| 类型| 说明 |
+| :-----| :---- | :---- |
+| repCode |String | 业务返回码,0000表示成功  |
+| repMsg |String | 业务日志、异常信息 |
+| repData |Object | 请求业务数据包、详情见下 |
+
+**repData** 数据详情
+
+| 字段| 类型| 说明 |
+| :-----| :---- | :---- |
+| title |String | 公告标题 |
+| details |String | 公告内容 |
+| templateType |String | 公告类型( 弹窗:dialog; 水平滚动:horizontal_scroll)|
+| templateTypeName |String | 公告模板名称 |
+
+***
+
+**errorInfo** 数据详情
+
+  ```java
+  repCode: 抛出异常Code
+  repMsg: 异常信息
+  
+  ```
+
+| 字段| 类型| 说明 |
+  | :-----| :---- | :---- |
+| repCode |string | 抛出异常Code ; 1001: 请求异常;1202: appKey为空;1203: appKey校验失败;1207: 系统版本号不能为空;1208: 应用版本号不能为空 |
+| repMsg |string | 异常信息 |

+ 1 - 0
demo/android/version/aj_android_appsp/app/.gitignore

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

BIN
demo/android/version/aj_android_appsp/app/appsp.jks


+ 0 - 0
demo/android/version/aj_android_appsp/app/build.gradle


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików