├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── dbnavigator.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── README_EN.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── RichardLuo │ │ └── notificationpush │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── RichardLuo │ │ │ └── notificationpush │ │ │ ├── Application.java │ │ │ ├── Const.java │ │ │ ├── FCMReceiver.java │ │ │ ├── ForegroundMonitor.java │ │ │ ├── GetNotification.java │ │ │ ├── MainActivity.java │ │ │ ├── Preferences.java │ │ │ ├── QQInstallReceiver.java │ │ │ ├── QQLogin.java │ │ │ ├── ThemeProvider.java │ │ │ └── Utils.java │ └── res │ │ ├── anim │ │ ├── down_from_top.xml │ │ └── up_from_bottom.xml │ │ ├── drawable-anydpi-v24 │ │ └── ic_notification.xml │ │ ├── drawable-anydpi │ │ ├── ic_filter.xml │ │ ├── ic_reverse.xml │ │ └── ic_search.xml │ │ ├── drawable-hdpi │ │ ├── ic_filter.png │ │ ├── ic_notification.png │ │ ├── ic_reverse.png │ │ └── ic_search.png │ │ ├── drawable-mdpi │ │ ├── ic_filter.png │ │ ├── ic_notification.png │ │ ├── ic_reverse.png │ │ └── ic_search.png │ │ ├── drawable-xhdpi │ │ ├── ic_filter.png │ │ ├── ic_notification.png │ │ ├── ic_reverse.png │ │ └── ic_search.png │ │ ├── drawable-xxhdpi │ │ ├── ic_filter.png │ │ ├── ic_notification.png │ │ ├── ic_reverse.png │ │ └── ic_search.png │ │ ├── drawable │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ ├── activity_application.xml │ │ ├── activity_main.xml │ │ ├── app_layout.xml │ │ ├── dropdown.xml │ │ └── qq_login.xml │ │ ├── menu │ │ ├── appmenu.xml │ │ ├── menu.xml │ │ └── webmenu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night-v31 │ │ └── colors.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values-sw360dp │ │ └── values-preference.xml │ │ ├── values-v31 │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values-zh │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── accessibility.xml │ │ ├── preference.xml │ │ └── searchable.xml │ └── test │ └── java │ └── com │ └── RichardLuo │ └── notificationpush │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | /app/google-services.json 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 29 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | xmlns:android 38 | 39 | ^$ 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | xmlns:.* 49 | 50 | ^$ 51 | 52 | 53 | BY_NAME 54 | 55 |
56 |
57 | 58 | 59 | 60 | .*:id 61 | 62 | http://schemas.android.com/apk/res/android 63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | 71 | .*:name 72 | 73 | http://schemas.android.com/apk/res/android 74 | 75 | 76 | 77 |
78 |
79 | 80 | 81 | 82 | name 83 | 84 | ^$ 85 | 86 | 87 | 88 |
89 |
90 | 91 | 92 | 93 | style 94 | 95 | ^$ 96 | 97 | 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | ^$ 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 | 114 | 115 | 116 | .* 117 | 118 | http://schemas.android.com/apk/res/android 119 | 120 | 121 | ANDROID_ATTRIBUTE_ORDER 122 | 123 |
124 |
125 | 126 | 127 | 128 | .* 129 | 130 | .* 131 | 132 | 133 | BY_NAME 134 | 135 |
136 |
137 |
138 |
139 |
140 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RichardLuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NotificationPush 2 | 此应用可以通过fcm从一台手机向另一台手机转发通知 3 | 4 | 目前仅在酷安发布[传送门](https://www.coolapk.com/apk/223104) 5 | 6 | ## 需要 7 | * 一台旧手机作为服务器 8 | * 一台可以接收fcm推送的主力机 9 | 10 | ## 使用 11 | * 下载最新release 12 | * 为两台手机安装 13 | * 将主力机上的token(就是那堆乱码)复制到server的输入框中 14 | * 取消电池优化 15 | * 在服务端切换start开关并授予相关权限 16 | * 现在,你应该能收到推送了 17 | 18 | ### 对于中国大陆用户 19 | * 服务端可能必须24小时挂梯! 20 | 21 | ## 自己编译 22 | * 下载源码 23 | * 在firebase中建立新项目 24 | * 根据Google的firebase文档加入google-services.json文件 25 | * 在根目录底下创建一个local.properties文件,然后写入 26 | ``` 27 | FCM_AUTHORIZATION="你对应的内容" 28 | FCM_SENDER="你对应的内容" 29 | ``` 30 | * 编译 -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # NotificationPush 2 | This app is for forwarding your notification from one phone to another using google's fcm 3 | 4 | Currently it is only publishing in [CoolApk](https://www.coolapk.com/apk/223104) 5 | 6 | ## What do you need 7 | * An old phone used as server 8 | * Your daily phone which could receive fcm push 9 | 10 | ## What do you need to do 11 | * Download the latest release 12 | * Install for both phones 13 | * Copy the token from daily phone to the input box on the server 14 | * It may be necessary to turn off the battery optimization 15 | * Switch the 'start' button and allow the permission on server 16 | * Now,you should have been able to receive the notification 17 | 18 | ### Only for users from China 19 | * 服务端可能必须24小时挂梯! 20 | 21 | # If you want to compile by yourself 22 | * Download the source code 23 | * Create a new project in firebase 24 | * Add google-services.json file according to the official document of Google 25 | * Create a file local.properties under project root folder. Write 26 | ``` 27 | FCM_AUTHORIZATION="Your authorization key" 28 | FCM_SENDER="Your sender number" 29 | ``` 30 | * Compile -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def localPropertiesFile = rootProject.file("local.properties") 4 | def localProperties = new Properties() 5 | localProperties.load(new FileInputStream(localPropertiesFile)) 6 | 7 | android { 8 | compileSdkVersion 31 9 | defaultConfig { 10 | applicationId "com.RichardLuo.notificationpush" 11 | minSdkVersion 21 12 | targetSdkVersion 31 13 | versionCode 30 14 | versionName "1.1.6" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | buildConfigField "String", "FCM_AUTHORIZATION", localProperties['FCM_AUTHORIZATION'] 17 | buildConfigField "String", "FCM_SENDER", localProperties['FCM_SENDER'] 18 | } 19 | buildTypes { 20 | release { 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | minifyEnabled true 23 | shrinkResources true 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(include: ['*.jar'], dir: 'libs') 30 | implementation 'androidx.appcompat:appcompat:1.1.0' 31 | implementation 'com.google.android.material:material:1.2.0-alpha02' 32 | implementation 'androidx.preference:preference:1.1.0' 33 | implementation 'androidx.media:media:1.1.0' 34 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 35 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 36 | implementation 'com.google.firebase:firebase-core:20.1.0' 37 | implementation 'com.google.firebase:firebase-messaging:23.0.0' 38 | testImplementation 'junit:junit:4.13.2' 39 | androidTestImplementation 'androidx.test:runner:1.4.0' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 41 | } 42 | 43 | apply plugin: 'com.google.gms.google-services' 44 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.RichardLuo.notificationpush", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 30, 15 | "versionName": "1.1.6", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/RichardLuo/notificationpush/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.RichardLuo.notificationpush", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 53 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/Application.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.app.SearchManager; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.content.pm.ApplicationInfo; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.drawable.Drawable; 10 | import android.os.Bundle; 11 | import android.view.LayoutInflater; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.animation.AnimationUtils; 17 | import android.widget.AdapterView; 18 | import android.widget.ArrayAdapter; 19 | import android.widget.BaseAdapter; 20 | import android.widget.ImageView; 21 | import android.widget.ListView; 22 | import android.widget.ProgressBar; 23 | import android.widget.TextView; 24 | 25 | import androidx.appcompat.app.AppCompatActivity; 26 | import androidx.appcompat.widget.AppCompatAutoCompleteTextView; 27 | import androidx.appcompat.widget.SearchView; 28 | 29 | import java.util.ArrayList; 30 | import java.util.Collections; 31 | import java.util.List; 32 | import java.util.Objects; 33 | 34 | public class Application extends AppCompatActivity { 35 | ListView listView; 36 | SharedPreferences preferences; 37 | ProgressBar progressBar; 38 | boolean filterSystemApp = true; 39 | String searchText = null; 40 | BaseAdapter ba; 41 | Runnable notifyDataSet = new Runnable() { 42 | @Override 43 | public void run() { 44 | ba.notifyDataSetChanged(); 45 | } 46 | }; 47 | 48 | PackageManager packageManager; 49 | List packageInfo; 50 | List displayItem = new ArrayList<>(); 51 | List tempItem; 52 | Info[] displayInfo; 53 | 54 | static class ViewHolder { 55 | TextView text; 56 | ImageView icon; 57 | AppCompatAutoCompleteTextView act; 58 | } 59 | 60 | class Info { 61 | String text; 62 | Drawable icon; 63 | AdapterView.OnItemClickListener onItemClickListener; 64 | int selection; 65 | 66 | Info(final ApplicationInfo applicationInfo) { 67 | text = getPackageManager().getApplicationLabel(applicationInfo).toString(); 68 | icon = getPackageManager().getApplicationIcon(applicationInfo); 69 | selection = preferences.getInt(applicationInfo.packageName, preferences.contains("allOff") ? 2 : 0); 70 | 71 | onItemClickListener = (parent, view, position, id) -> { 72 | if (position == 0 && !preferences.contains("allOff")) { 73 | preferences.edit().remove(applicationInfo.packageName).apply(); 74 | return; 75 | } else if (position == 2 && preferences.contains("allOff")) 76 | return; 77 | preferences.edit().putInt(applicationInfo.packageName, position).apply(); 78 | }; 79 | } 80 | } 81 | 82 | @Override 83 | protected void onCreate(Bundle savedInstanceState) { 84 | super.onCreate(savedInstanceState); 85 | setTheme(ThemeProvider.getCurrentStyle(this)); 86 | setContentView(R.layout.activity_application); 87 | Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); 88 | listView = findViewById(R.id.listview); 89 | progressBar = findViewById(R.id.progressBar); 90 | preferences = getPreferences(MODE_PRIVATE); 91 | 92 | listView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 93 | listView.setFitsSystemWindows(true); 94 | // final ActionBar actionBar = getSupportActionBar(); 95 | //// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 96 | //// listView.setOnScrollChangeListener(new View.OnScrollChangeListener() { 97 | //// boolean lastScrollinTop = true; 98 | //// ValueAnimator animation = ValueAnimator.ofInt(0, 8); 99 | //// 100 | //// { 101 | //// animation.setDuration(400); 102 | //// animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 103 | //// @Override 104 | //// public void onAnimationUpdate(ValueAnimator animation) { 105 | //// actionBar.setElevation((int) animation.getAnimatedValue()); 106 | //// } 107 | //// }); 108 | //// } 109 | //// 110 | //// @Override 111 | //// public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { 112 | //// if (!v.canScrollVertically(-1) && !lastScrollinTop) { 113 | //// animation.reverse(); 114 | //// lastScrollinTop = true; 115 | //// } else if (lastScrollinTop) { 116 | //// animation.start(); 117 | //// lastScrollinTop = false; 118 | //// } 119 | //// } 120 | //// }); 121 | //// } else 122 | //// actionBar.setElevation(8); 123 | 124 | new Thread() { 125 | @Override 126 | public void run() { 127 | final String[] priorityContent = getApplicationContext().getResources().getStringArray(R.array.priorityContent); 128 | final ArrayAdapter actAdapter = new ArrayAdapter<>(getApplicationContext(), R.layout.dropdown, priorityContent); 129 | packageManager = getPackageManager(); 130 | packageInfo = packageManager.getInstalledApplications(0); 131 | Collections.sort(packageInfo, new ApplicationInfo.DisplayNameComparator(packageManager)); 132 | displayInfo = new Info[packageInfo.size()]; 133 | 134 | ba = new BaseAdapter() { 135 | private int lastPosition = -1; 136 | 137 | @Override 138 | public int getCount() { 139 | return displayItem.size(); 140 | } 141 | 142 | @Override 143 | public Object getItem(int position) { 144 | return displayItem.get(position); 145 | } 146 | 147 | @Override 148 | public long getItemId(int position) { 149 | return position; 150 | } 151 | 152 | @Override 153 | public void notifyDataSetChanged() { 154 | displayInfo = new Info[packageInfo.size()]; 155 | lastPosition = -1; 156 | super.notifyDataSetChanged(); 157 | } 158 | 159 | @Override 160 | public View getView(int position, View convertView, ViewGroup parent) { 161 | ViewHolder holder; 162 | Info info; 163 | if (displayInfo[position] == null) 164 | displayInfo[position] = (info = new Info(displayItem.get(position))); 165 | else 166 | info = displayInfo[position]; 167 | if (convertView == null) { 168 | convertView = LayoutInflater.from(getBaseContext()).inflate(R.layout.app_layout, listView, false); 169 | holder = new ViewHolder(); 170 | holder.text = convertView.findViewById(R.id.appName); 171 | holder.icon = convertView.findViewById(R.id.imageView); 172 | holder.act = convertView.findViewById(R.id.priority); 173 | holder.act.setAdapter(actAdapter); 174 | holder.act.setKeyListener(null); 175 | convertView.setTag(holder); 176 | } else { 177 | holder = (ViewHolder) convertView.getTag(); 178 | } 179 | holder.text.setText(info.text); 180 | holder.icon.setImageDrawable(info.icon); 181 | holder.act.setOnItemClickListener(info.onItemClickListener); 182 | holder.act.setText(priorityContent[info.selection], false); 183 | convertView.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), (position > lastPosition) ? R.anim.up_from_bottom : R.anim.down_from_top)); 184 | lastPosition = position; 185 | return convertView; 186 | } 187 | }; 188 | 189 | runOnUiThread(() -> refreshListData(() -> listView.setAdapter(ba))); 190 | } 191 | }.start(); 192 | } 193 | 194 | public void refreshListData(final Runnable update) { 195 | progressBar.setVisibility(View.VISIBLE); 196 | 197 | new Thread() { 198 | @Override 199 | public void run() { 200 | if (filterSystemApp || (searchText != null && !searchText.equals(""))) { 201 | if (displayItem == packageInfo) 202 | displayItem = tempItem; 203 | displayItem.clear(); 204 | for (ApplicationInfo applicationInfo : packageInfo) { 205 | if (filterSystemApp && !((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0)) 206 | continue; 207 | 208 | // 判断search Text 209 | if ((searchText != null && !searchText.equals("")) && !packageManager.getApplicationLabel(applicationInfo).toString().contains(searchText)) 210 | continue; 211 | 212 | displayItem.add(applicationInfo); 213 | } 214 | } else if (displayItem != packageInfo) { 215 | tempItem = displayItem; 216 | displayItem = packageInfo; 217 | } 218 | 219 | runOnUiThread(() -> { 220 | update.run(); 221 | progressBar.setVisibility(View.GONE); 222 | }); 223 | super.run(); 224 | } 225 | }.start(); 226 | } 227 | 228 | @Override 229 | public boolean onCreateOptionsMenu(Menu menu) { 230 | getMenuInflater().inflate(R.menu.appmenu, menu); 231 | 232 | SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); 233 | MenuItem searchMenu = menu.findItem(R.id.search); 234 | searchMenu.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { 235 | @Override 236 | public boolean onMenuItemActionExpand(MenuItem menuItem) { 237 | return true; 238 | } 239 | 240 | @Override 241 | public boolean onMenuItemActionCollapse(MenuItem menuItem) { 242 | if (searchText != null) { 243 | searchText = null; 244 | refreshListData(notifyDataSet); 245 | } 246 | return true; 247 | } 248 | }); 249 | SearchView searchView = (SearchView) searchMenu.getActionView(); 250 | searchView.setSearchableInfo(Objects.requireNonNull(searchManager).getSearchableInfo(getComponentName())); 251 | searchView.setIconifiedByDefault(false); 252 | 253 | if (preferences.contains("allOff")) 254 | menu.findItem(R.id.reverse).setTitle(getString(R.string.positive)); 255 | return true; 256 | } 257 | 258 | @Override 259 | public boolean onOptionsItemSelected(MenuItem item) { 260 | switch (item.getItemId()) { 261 | case android.R.id.home: 262 | finish(); 263 | break; 264 | case R.id.filter: 265 | filterSystemApp = !filterSystemApp; 266 | refreshListData(notifyDataSet); 267 | break; 268 | case R.id.reverse: 269 | if (!preferences.contains("allOff")) { 270 | preferences.edit().clear().putInt("allOff", 0).apply(); 271 | item.setTitle(getString(R.string.positive)); 272 | } else { 273 | preferences.edit().remove("allOff").apply(); 274 | item.setTitle(getString(R.string.reverse)); 275 | } 276 | refreshListData(notifyDataSet); 277 | break; 278 | } 279 | return true; 280 | } 281 | 282 | @Override 283 | protected void onNewIntent(Intent intent) { 284 | if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 285 | searchText = intent.getStringExtra(SearchManager.QUERY); 286 | refreshListData(notifyDataSet); 287 | } 288 | super.onNewIntent(intent); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/Const.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | public class Const { 4 | 5 | static final String TAG = "NotificationPush"; 6 | 7 | static final String[] QQ_NAMES = new String[]{"com.tencent.mobileqq", "com.tencent.tim", "com.tencent.mobileqqi", "com.tencent.qqlite", "com.tencent.minihd.qq"}; 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/FCMReceiver.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 4 | import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 5 | import static androidx.preference.PreferenceManager.getDefaultSharedPreferences; 6 | 7 | import android.app.Notification; 8 | import android.app.NotificationChannel; 9 | import android.app.NotificationManager; 10 | import android.app.PendingIntent; 11 | import android.content.BroadcastReceiver; 12 | import android.content.Intent; 13 | import android.content.IntentFilter; 14 | import android.database.Cursor; 15 | import android.database.sqlite.SQLiteDatabase; 16 | import android.graphics.Bitmap; 17 | import android.graphics.BitmapFactory; 18 | import android.graphics.Canvas; 19 | import android.graphics.Paint; 20 | import android.graphics.PorterDuff; 21 | import android.graphics.PorterDuffXfermode; 22 | import android.graphics.Rect; 23 | import android.os.Build; 24 | import android.os.PatternMatcher; 25 | import android.service.notification.StatusBarNotification; 26 | import android.util.Log; 27 | 28 | import androidx.annotation.NonNull; 29 | import androidx.core.app.NotificationCompat; 30 | import androidx.core.app.NotificationManagerCompat; 31 | import androidx.core.app.Person; 32 | import androidx.core.graphics.drawable.IconCompat; 33 | 34 | import com.google.firebase.messaging.FirebaseMessagingService; 35 | import com.google.firebase.messaging.RemoteMessage; 36 | 37 | import java.io.File; 38 | import java.io.FileOutputStream; 39 | import java.io.IOException; 40 | import java.io.InputStream; 41 | import java.net.HttpURLConnection; 42 | import java.net.URL; 43 | import java.util.Date; 44 | import java.util.HashMap; 45 | import java.util.Map; 46 | import java.util.Objects; 47 | 48 | public class FCMReceiver extends FirebaseMessagingService { 49 | static Map package_Intent = new HashMap<>(); 50 | 51 | int color = 0; 52 | Boolean ringForEach; 53 | NotificationManagerCompat notificationManagerCompat; 54 | 55 | private final BroadcastReceiver br = new QQInstallReceiver(); 56 | 57 | @Override 58 | public void onCreate() { 59 | notificationManagerCompat = NotificationManagerCompat.from(this); 60 | super.onCreate(); 61 | 62 | QQInstallReceiver.findQQPackage(this); 63 | IntentFilter filter = new IntentFilter(); 64 | filter.addAction(Intent.ACTION_PACKAGE_ADDED); 65 | filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 66 | filter.addDataScheme("package"); 67 | filter.addDataSchemeSpecificPart("com.tencent", PatternMatcher.PATTERN_LITERAL); 68 | registerReceiver(br, filter); 69 | } 70 | 71 | @Override 72 | public void onDestroy() { 73 | super.onDestroy(); 74 | 75 | unregisterReceiver(br); 76 | } 77 | 78 | @Override 79 | public void onNewToken(@NonNull String s) { 80 | super.onNewToken(s); 81 | String channelName = "Token变更"; 82 | setChannel(channelName); 83 | Notification notification = new NotificationCompat.Builder(this, channelName) 84 | .setSmallIcon(R.drawable.ic_notification) 85 | .setColor(color) 86 | .setStyle(new NotificationCompat.BigTextStyle() 87 | .setSummaryText(channelName)) 88 | .setContentTitle(channelName) 89 | .setContentText("Token发生变更,请更换服务端token") 90 | .setAutoCancel(true) 91 | .setOnlyAlertOnce(!ringForEach) 92 | .build(); 93 | notificationManagerCompat.notify(channelName, 0, notification); 94 | } 95 | 96 | @Override 97 | public void onMessageReceived(RemoteMessage remoteMessage) { 98 | ringForEach = getDefaultSharedPreferences(this).getBoolean("ringForEach", false); 99 | color = ThemeProvider.getCurrentColor(this); 100 | Map data = remoteMessage.getData(); 101 | String title = data.get("title"); 102 | String body = data.get("body"); 103 | String packageName = data.get("package"); 104 | String AppName = data.get("name"); 105 | int id = Integer.parseInt(Objects.requireNonNull(data.get("id"))); 106 | String senderName = null; 107 | if (data.containsKey("senderName")) 108 | senderName = data.get("senderName"); 109 | PendingIntent intent; 110 | boolean hide = getDefaultSharedPreferences(this).getBoolean("hide", false); 111 | if (hide && ForegroundMonitor.packageName.equals(packageName)) 112 | return; 113 | 114 | setChannel(AppName); 115 | 116 | boolean isQQ = false; 117 | for (String qqName : Const.QQ_NAMES) { 118 | if (qqName.equals(packageName)) { 119 | isQQ = true; 120 | break; 121 | } 122 | } 123 | 124 | if (isQQ && senderName != null) { 125 | String className = ForegroundMonitor.packageName; 126 | String qqPackageName = getSharedPreferences("MainActivity", MODE_PRIVATE).getString("installedQQ", null); 127 | intent = getIntent(qqPackageName); 128 | if (hide && qqPackageName != null && className.contains(qqPackageName)) 129 | return; 130 | if (senderName.equals("")) 131 | senderName = " "; 132 | Bitmap icon = null; 133 | Bitmap largeIcon = null; 134 | if (getDefaultSharedPreferences(this).getBoolean("sendQQ", false) && this.getDatabasePath("friends.db").exists()) { 135 | boolean isfriend = senderName.equals(title); 136 | String encodeSendername = "member_" + Utils.md5(senderName); 137 | File file = new File(this.getCacheDir().getPath() + "/" + encodeSendername); 138 | out: 139 | try { 140 | if (!file.exists()) { 141 | SQLiteDatabase db; 142 | Cursor cursor; 143 | if (isfriend) { 144 | db = SQLiteDatabase.openOrCreateDatabase(this.getDatabasePath("friends.db"), null); 145 | if (getSharedPreferences("groups", MODE_PRIVATE).contains("sync_friends")) 146 | cursor = db.query("friends", new String[]{"uin"}, "name ='" + senderName + "'", null, null, null, null); 147 | else 148 | break out; 149 | } else if (getSharedPreferences("groups", MODE_PRIVATE).contains(title)) { 150 | db = SQLiteDatabase.openOrCreateDatabase(this.getDatabasePath("friends.db"), null); 151 | Cursor cursorTemp = db.query("'" + title + "'", new String[]{"uin"}, "name ='" + senderName + "'", null, null, null, null); 152 | if (cursorTemp.getCount() == 0) { 153 | cursorTemp.close(); 154 | cursor = db.query("friends", new String[]{"uin"}, "name ='" + senderName + "'", null, null, null, null); 155 | } else cursor = cursorTemp; 156 | } else 157 | break out; 158 | if (cursor.getCount() != 0) { 159 | if (cursor.moveToFirst()) { 160 | String QQnumber = cursor.getString(0); 161 | cursor.close(); 162 | db.close(); 163 | downloadIcon("https://q4.qlogo.cn/g?b=qq&s=140&nk=" + QQnumber, encodeSendername); 164 | } 165 | } else { 166 | cursor.close(); 167 | db.close(); 168 | break out; 169 | } 170 | } 171 | icon = BitmapFactory.decodeFile(this.getCacheDir().getPath() + "/" + encodeSendername); 172 | } catch (IOException e) { 173 | Log.e("", e.getMessage(), e); 174 | } 175 | // Large icon 176 | if (isfriend) { 177 | if (icon != null) 178 | largeIcon = icon; 179 | } else if (title != null) { 180 | String encodeTitle = "group_" + Utils.md5(title); 181 | if (new File(this.getCacheDir().getPath() + "/" + encodeTitle).exists()) 182 | largeIcon = BitmapFactory.decodeFile(this.getCacheDir().getPath() + "/" + encodeTitle); 183 | else { 184 | String groupNumber = getSharedPreferences("groupsNumber", MODE_PRIVATE).getString(title, null); 185 | if (groupNumber != null) { 186 | try { 187 | largeIcon = downloadIcon("https://p.qlogo.cn/gh/" + groupNumber + "/" + groupNumber + "/100", encodeTitle); 188 | } catch (IOException e) { 189 | Log.e(Const.TAG, e.getMessage(), e); 190 | } 191 | } 192 | } 193 | } 194 | } 195 | setSummary(packageName, AppName, intent); 196 | MessagingStyle(packageName, AppName, title, senderName, body, intent, id, icon == null ? null : IconCompat.createWithBitmap(icon), largeIcon); 197 | return; 198 | } else { 199 | intent = getIntent(packageName); 200 | setSummary(packageName, AppName, intent); 201 | } 202 | 203 | Notification notification = new NotificationCompat.Builder(this, AppName == null ? "" : AppName) 204 | .setSmallIcon(R.drawable.ic_notification) 205 | .setColor(color) 206 | .setStyle(new NotificationCompat.BigTextStyle() 207 | .setSummaryText(AppName)) 208 | .setContentTitle(title) 209 | .setContentText(body) 210 | .setGroup(packageName) 211 | .setContentIntent(intent) 212 | .setAutoCancel(true) 213 | .setOnlyAlertOnce(!ringForEach) 214 | .build(); 215 | notificationManagerCompat.notify(packageName, id, notification); 216 | } 217 | 218 | @SuppressWarnings("SuspiciousNameCombination") 219 | private Bitmap downloadIcon(String url, String name) throws IOException { 220 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); 221 | connection.setRequestMethod("GET"); 222 | connection.setDoInput(true); 223 | connection.setConnectTimeout(2000); 224 | connection.setReadTimeout(2000); 225 | connection.connect(); 226 | if (connection.getResponseCode() == 200 || connection.getResponseCode() == 304) { 227 | InputStream inputStream = connection.getInputStream(); 228 | FileOutputStream out = new FileOutputStream(new File(getCacheDir(), name)); 229 | Bitmap avatar = BitmapFactory.decodeStream(inputStream); 230 | int width = avatar.getWidth(); 231 | Bitmap output = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); 232 | Canvas canvas = new Canvas(output); 233 | Rect rect = new Rect(0, 0, width, width); 234 | Paint paint = new Paint(); 235 | paint.setAntiAlias(true); 236 | canvas.drawARGB(0, 0, 0, 0); 237 | paint.setColor(0xff424242); 238 | canvas.drawCircle(width / 2f, width / 2f, width / 2f, paint); 239 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 240 | canvas.drawBitmap(avatar, rect, rect, paint); 241 | output.compress(Bitmap.CompressFormat.PNG, 100, out); 242 | return output; 243 | } 244 | connection.disconnect(); 245 | return null; 246 | } 247 | 248 | public void setChannel(String AppName) { 249 | NotificationChannel mChannel; 250 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !getSharedPreferences("Channels", MODE_PRIVATE).contains(AppName)) { 251 | mChannel = new NotificationChannel(AppName, AppName, IMPORTANCE_DEFAULT); 252 | Objects.requireNonNull(getSystemService(NotificationManager.class)).createNotificationChannel(mChannel); 253 | getSharedPreferences("Channels", MODE_PRIVATE).edit().putBoolean(AppName, true).apply(); 254 | } 255 | } 256 | 257 | private PendingIntent getIntent(String packageName) { 258 | PendingIntent intent = null; 259 | if (package_Intent.containsKey(packageName)) { 260 | intent = package_Intent.get(packageName); 261 | return intent; 262 | } 263 | if (packageName != null) 264 | try { 265 | Intent launchIntent = getPackageManager().getLaunchIntentForPackage(packageName); 266 | if (launchIntent == null) 267 | return null; 268 | int flags; 269 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 270 | flags = PendingIntent.FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT; 271 | else 272 | flags = FLAG_UPDATE_CURRENT; 273 | intent = PendingIntent.getActivity(this, 200, launchIntent, flags); 274 | } catch (Exception e) { 275 | package_Intent.put(packageName, null); 276 | return null; 277 | } 278 | package_Intent.put(packageName, intent); 279 | return intent; 280 | } 281 | 282 | private Notification getCurrentNotification(String packageName, int id) { 283 | StatusBarNotification[] sbns; 284 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) 285 | sbns = Objects.requireNonNull(getSystemService(NotificationManager.class)).getActiveNotifications(); 286 | else 287 | return null; 288 | for (StatusBarNotification sbn : sbns) { 289 | if (sbn.getTag().equals(packageName) && sbn.getId() == id) { 290 | return sbn.getNotification(); 291 | } 292 | } 293 | return null; 294 | } 295 | 296 | public void setSummary(String packageName, String AppName, PendingIntent intent) { 297 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { 298 | Notification summary = new NotificationCompat.Builder(this, AppName) 299 | .setSmallIcon(R.drawable.ic_notification) 300 | .setColor(color) 301 | .setStyle(new NotificationCompat.BigTextStyle() 302 | .setSummaryText(AppName)) 303 | .setGroup(packageName) 304 | .setContentIntent(intent) 305 | .setGroupSummary(true) 306 | .setAutoCancel(true) 307 | .setOnlyAlertOnce(true) 308 | .build(); 309 | notificationManagerCompat.notify(packageName, 0, summary); 310 | } 311 | } 312 | 313 | private void MessagingStyle(String packageName, String AppName, String title, String senderName, String message, PendingIntent intent, int ID, IconCompat icon, Bitmap largeIcon) { 314 | Person.Builder personBuilder = new Person.Builder() 315 | .setName(senderName); 316 | if (icon != null) 317 | personBuilder.setIcon(icon); 318 | Person sender = personBuilder.build(); 319 | 320 | Notification current; 321 | NotificationCompat.MessagingStyle style = null; 322 | if (!((current = getCurrentNotification(packageName, ID)) == null)) 323 | style = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(current); 324 | if (style == null) { 325 | style = new NotificationCompat.MessagingStyle(sender); 326 | if (title.equals(senderName)) 327 | style.setGroupConversation(false); 328 | else 329 | style.setConversationTitle(title).setGroupConversation(true); 330 | } 331 | style.addMessage(message, new Date().getTime(), sender); 332 | 333 | NotificationCompat.Builder notification = new NotificationCompat.Builder(this, AppName) 334 | .setSmallIcon(R.drawable.ic_notification) 335 | .setColor(color) 336 | .setContentTitle(packageName) 337 | .setStyle(style); 338 | if (largeIcon != null) 339 | notification.setLargeIcon(largeIcon); 340 | notification.setGroup(packageName) 341 | .setContentIntent(intent) 342 | .setAutoCancel(true) 343 | .setOnlyAlertOnce(!ringForEach); 344 | notificationManagerCompat.notify(packageName, ID, notification.build()); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/ForegroundMonitor.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.view.accessibility.AccessibilityEvent; 5 | 6 | public class ForegroundMonitor extends AccessibilityService { 7 | static String packageName = ""; 8 | 9 | @Override 10 | public void onAccessibilityEvent(AccessibilityEvent event) { 11 | if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getContentChangeTypes() == 0) { 12 | if (event.getPackageName() == null || event.getPackageName().toString().contains("inputmethod") || event.getPackageName().toString().contains("system")) 13 | return; 14 | packageName = event.getPackageName().toString(); 15 | } 16 | } 17 | 18 | @Override 19 | public void onInterrupt() { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/GetNotification.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 4 | import static androidx.preference.PreferenceManager.getDefaultSharedPreferences; 5 | 6 | import android.app.Notification; 7 | import android.app.NotificationChannel; 8 | import android.app.NotificationManager; 9 | import android.content.SharedPreferences; 10 | import android.content.pm.PackageManager; 11 | import android.os.StrictMode; 12 | import android.service.notification.NotificationListenerService; 13 | import android.service.notification.StatusBarNotification; 14 | import android.util.Log; 15 | 16 | import androidx.core.app.NotificationCompat; 17 | 18 | import org.json.JSONObject; 19 | 20 | import java.io.DataOutputStream; 21 | import java.net.HttpURLConnection; 22 | import java.net.URL; 23 | import java.util.Objects; 24 | import java.util.regex.Matcher; 25 | import java.util.regex.Pattern; 26 | 27 | public class GetNotification extends NotificationListenerService { 28 | protected final String Authorization = BuildConfig.FCM_AUTHORIZATION; 29 | protected final String Sender = BuildConfig.FCM_SENDER; 30 | public String inputID; 31 | PackageManager pm; 32 | 33 | @Override 34 | public void onCreate() { 35 | inputID = getDefaultSharedPreferences(this).getString("input", ""); 36 | pm = getPackageManager(); 37 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build()); 38 | if (getDefaultSharedPreferences(getApplicationContext()).getBoolean("startForeground", false)) { 39 | NotificationChannel mChannel; 40 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 41 | mChannel = new NotificationChannel("Foreground", "前台服务", IMPORTANCE_DEFAULT); 42 | Objects.requireNonNull(getSystemService(NotificationManager.class)).createNotificationChannel(mChannel); 43 | } 44 | Notification foregroundNotice = new NotificationCompat.Builder(this, "Foreground") 45 | .setSmallIcon(R.drawable.ic_notification) 46 | .setColor(ThemeProvider.getCurrentColor(this)) 47 | .setContentTitle("后台转发通知中") 48 | .setContentText("转发中") 49 | .build(); 50 | startForeground(1, foregroundNotice); 51 | } 52 | } 53 | 54 | @Override 55 | public void onDestroy() { 56 | if (getDefaultSharedPreferences(getApplicationContext()).getBoolean("startForeground", false)) { 57 | stopForeground(true); 58 | } 59 | super.onDestroy(); 60 | } 61 | 62 | @Override 63 | public void onNotificationPosted(final StatusBarNotification sbn) { 64 | new Thread() { 65 | @Override 66 | public void run() { 67 | String packageName = sbn.getPackageName(); 68 | Notification oneNotification = sbn.getNotification(); 69 | String title = oneNotification.extras.getString(Notification.EXTRA_TITLE, "无标题"); 70 | String body = oneNotification.extras.getCharSequence(Notification.EXTRA_TEXT, "无内容").toString(); 71 | String AppName; 72 | SharedPreferences sharedPreferences = getSharedPreferences("AppName", MODE_PRIVATE); 73 | if ((AppName = sharedPreferences.getString(packageName, null)) == null) { 74 | try { 75 | AppName = pm.getApplicationLabel(pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA)).toString(); 76 | } catch (PackageManager.NameNotFoundException e) { 77 | AppName = " "; 78 | } 79 | sharedPreferences.edit().putString(packageName, AppName).apply(); 80 | } 81 | int ID = sbn.getId(); 82 | String senderName = null; 83 | String priority = "normal"; 84 | 85 | if (title.contains("正在运行") || title.contains("running") || inputID == null) return; 86 | if (getDefaultSharedPreferences(getApplicationContext()).getBoolean("hide_no_content", false) && title.equals("无标题") && body.equals("无内容")) 87 | return; 88 | 89 | SharedPreferences appPreference = getSharedPreferences("Application", MODE_PRIVATE); 90 | switch (appPreference.getInt(packageName, appPreference.contains("allOff") ? 2 : 0)) { 91 | case 0: 92 | priority = "normal"; 93 | break; 94 | case 1: 95 | priority = "high"; 96 | break; 97 | case 2: 98 | return; 99 | } 100 | 101 | //此处对单个应用进行单独定义 102 | switch (packageName) { 103 | case "com.RichardLuo.notificationpush": 104 | return; 105 | case "com.tencent.minihd.qq": 106 | case "com.tencent.mobileqqi": 107 | case "com.tencent.qqlite": 108 | case "com.tencent.tim": 109 | case "com.tencent.mobileqq": 110 | if (!(title.contains("QQ空间"))) { 111 | if (oneNotification.tickerText != null) { 112 | String tickerText = oneNotification.tickerText.toString().replace("\n", ""); 113 | Matcher matcher = Pattern.compile("^(.*?)\\((((?![()]).)*?)\\):(.*?)$").matcher(tickerText); 114 | if (matcher.find()) { 115 | senderName = matcher.group(1); 116 | title = matcher.group(2); 117 | body = Objects.requireNonNull(matcher.group(4)).trim(); 118 | } else { 119 | String[] single = tickerText.split(":", 2); 120 | senderName = single[0]; 121 | title = single[0]; 122 | if (single.length > 1) 123 | body = single[1].trim(); 124 | } 125 | if (title != null) 126 | ID = StringToA(title); 127 | } else 128 | return; 129 | } 130 | } 131 | 132 | 133 | HttpURLConnection connection; 134 | try { 135 | URL url = new URL("https://fcm.googleapis.com/fcm/send"); 136 | connection = (HttpURLConnection) url.openConnection(); 137 | connection.setDoOutput(true); 138 | connection.setConnectTimeout(3000); 139 | connection.setReadTimeout(3000); 140 | connection.setRequestMethod("POST"); 141 | connection.setRequestProperty("Content-Type", "application/json"); 142 | connection.setRequestProperty("Authorization", "key=" + Authorization); 143 | connection.setRequestProperty("Sender", "id=" + Sender); 144 | connection.connect(); 145 | DataOutputStream out = new DataOutputStream(connection.getOutputStream()); 146 | JSONObject obj = new JSONObject(); 147 | JSONObject content = new JSONObject(); 148 | content.put("title", title); 149 | content.put("body", body); 150 | content.put("package", packageName); 151 | content.put("name", AppName); 152 | content.put("id", ID); 153 | if (senderName != null) 154 | content.put("senderName", senderName); 155 | obj.put("to", inputID); 156 | obj.put("priority", priority); 157 | obj.put("data", content); 158 | String json = obj.toString(); 159 | out.write(json.getBytes()); 160 | out.flush(); 161 | out.close(); 162 | connection.getResponseCode(); 163 | connection.disconnect(); 164 | } catch (Exception e) { 165 | Log.e("error:", "Can't send " + packageName + " " + title); 166 | } 167 | super.run(); 168 | } 169 | }.start(); 170 | } 171 | 172 | private static int StringToA(String content) { 173 | int result = 0; 174 | int max = content.length(); 175 | for (int i = 0; i < max; i++) { 176 | char c = content.charAt(i); 177 | result = result + (int) c; 178 | } 179 | return result; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.widget.Toast; 10 | 11 | import androidx.appcompat.app.AlertDialog; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | 14 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setTheme(ThemeProvider.getCurrentStyle(this)); 22 | setContentView(R.layout.activity_main); 23 | getSupportFragmentManager().beginTransaction() 24 | .replace(R.id.preference, new Preferences()) 25 | .commit(); 26 | } 27 | 28 | @Override 29 | public boolean onCreateOptionsMenu(Menu menu) { 30 | getMenuInflater().inflate(R.menu.menu, menu); 31 | return true; 32 | } 33 | 34 | @SuppressLint("NonConstantResourceId") 35 | @Override 36 | public boolean onOptionsItemSelected(MenuItem item) { 37 | switch (item.getItemId()) { 38 | case R.id.color: 39 | AlertDialog.Builder listDialog = new MaterialAlertDialogBuilder(this); 40 | listDialog.setTitle("选择颜色"); 41 | listDialog.setSingleChoiceItems(ThemeProvider.getThemeNameList(), ThemeProvider.getCurrentTheme(this), (dialog, click) -> { 42 | ThemeProvider.setTheme(this, click); 43 | Intent intent = new Intent(this, MainActivity.class); 44 | startActivity(intent); 45 | finish(); 46 | }); 47 | listDialog.show(); 48 | break; 49 | case R.id.about: 50 | final AlertDialog.Builder normalDialog = new MaterialAlertDialogBuilder(this); 51 | normalDialog.setTitle("关于"); 52 | normalDialog.setMessage(getResources().getString(R.string.HowToUse)); 53 | normalDialog.setPositiveButton("捐赠", (dialog, which) -> { 54 | try { 55 | startActivity(new Intent().setData(Uri.parse("alipays://platformapi/startapp?saId=10000007&clientVersion=3.7.0.0718&qrcode=https://qr.alipay.com/fkx0746746ugqrzxkrle7c0?_s=web-other")).setAction("android.intent.action.VIEW")); 56 | } catch (Exception e) { 57 | Toast.makeText(getApplicationContext(), "Alipay not found!", Toast.LENGTH_SHORT).show(); 58 | } 59 | }); 60 | normalDialog.setNeutralButton("GITHUB", (dialog, which) -> startActivity(new Intent().setData(Uri.parse("https://github.com/CJieLuo/NotificationPush")).setAction("android.intent.action.VIEW"))); 61 | normalDialog.show(); 62 | break; 63 | } 64 | return super.onOptionsItemSelected(item); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/Preferences.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import static android.content.Context.MODE_PRIVATE; 4 | import static androidx.preference.PreferenceManager.getDefaultSharedPreferences; 5 | 6 | import android.app.NotificationChannel; 7 | import android.app.NotificationManager; 8 | import android.app.ProgressDialog; 9 | import android.content.ClipData; 10 | import android.content.ClipboardManager; 11 | import android.content.ComponentName; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.SharedPreferences; 15 | import android.database.sqlite.SQLiteDatabase; 16 | import android.os.Build; 17 | import android.os.Bundle; 18 | import android.provider.Settings; 19 | import android.text.Html; 20 | import android.text.TextUtils; 21 | import android.util.Log; 22 | import android.view.View; 23 | import android.widget.Toast; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.annotation.Nullable; 27 | import androidx.appcompat.app.AlertDialog; 28 | import androidx.preference.CheckBoxPreference; 29 | import androidx.preference.EditTextPreference; 30 | import androidx.preference.Preference; 31 | import androidx.preference.PreferenceFragmentCompat; 32 | import androidx.preference.SwitchPreference; 33 | 34 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 35 | import com.google.firebase.messaging.FirebaseMessaging; 36 | 37 | import org.json.JSONArray; 38 | import org.json.JSONObject; 39 | 40 | import java.io.ByteArrayOutputStream; 41 | import java.io.DataOutputStream; 42 | import java.io.IOException; 43 | import java.io.InputStream; 44 | import java.net.HttpURLConnection; 45 | import java.net.SocketTimeoutException; 46 | import java.net.URL; 47 | import java.net.URLDecoder; 48 | import java.util.ArrayList; 49 | import java.util.Iterator; 50 | import java.util.Objects; 51 | 52 | public class Preferences extends PreferenceFragmentCompat { 53 | private SharedPreferences preferences; 54 | private SwitchPreference start; 55 | private EditTextPreference input; 56 | private SwitchPreference hide; 57 | private String token; 58 | 59 | @Override 60 | public void onCreatePreferences(Bundle bundle, String s) { 61 | setPreferencesFromResource(R.xml.preference, null); 62 | preferences = getDefaultSharedPreferences(requireActivity()); 63 | 64 | start = findPreference("start"); 65 | Objects.requireNonNull(start).setOnPreferenceClickListener(preference -> { 66 | Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); 67 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 68 | if (!Objects.requireNonNull(preferences.getString("input", "")).isEmpty()) { 69 | startActivity(intent); 70 | } else { 71 | Toast.makeText(getActivity(), "请填写设备ID", Toast.LENGTH_SHORT).show(); 72 | start.setChecked(false); 73 | } 74 | return false; 75 | }); 76 | 77 | CheckBoxPreference startForeground = findPreference("startForeground"); 78 | Objects.requireNonNull(startForeground).setOnPreferenceClickListener(preference -> { 79 | if (start.isChecked()) 80 | Toast.makeText(getActivity(), "请重启通知监听", Toast.LENGTH_SHORT).show(); 81 | return false; 82 | }); 83 | 84 | input = findPreference("input"); 85 | 86 | Preference logcat = findPreference("logcat"); 87 | Objects.requireNonNull(logcat).setOnPreferenceClickListener(preference -> { 88 | AlertDialog.Builder log = new MaterialAlertDialogBuilder(requireContext()); 89 | log.setTitle("Log"); 90 | final ByteArrayOutputStream result = new ByteArrayOutputStream(); 91 | try { 92 | InputStream is = Runtime.getRuntime().exec(new String[]{"logcat", "-d", "*:E"}).getInputStream(); 93 | byte[] buffer = new byte[1024]; 94 | int length; 95 | while ((length = is.read(buffer)) != -1) { 96 | result.write(buffer, 0, length); 97 | } 98 | } catch (IOException e) { 99 | Log.e("error:", "read fail"); 100 | Toast.makeText(getActivity(), "读取失败", Toast.LENGTH_SHORT).show(); 101 | } 102 | log.setPositiveButton("复制", (dialog, which) -> { 103 | ((ClipboardManager) Objects.requireNonNull(requireActivity().getSystemService(Context.CLIPBOARD_SERVICE))).setPrimaryClip(ClipData.newPlainText("token", result.toString())); 104 | Toast.makeText(getActivity(), "复制成功", Toast.LENGTH_SHORT).show(); 105 | }); 106 | log.setMessage(result.toString()); 107 | log.show(); 108 | return true; 109 | }); 110 | 111 | hide = findPreference("hide"); 112 | Objects.requireNonNull(hide).setOnPreferenceClickListener(preference -> { 113 | Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); 114 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 115 | startActivity(intent); 116 | return false; 117 | }); 118 | 119 | Preference tokenPreference = findPreference("token"); 120 | Objects.requireNonNull(tokenPreference).setOnPreferenceClickListener(preference -> { 121 | final AlertDialog.Builder normalDialog = new MaterialAlertDialogBuilder(requireContext()); 122 | normalDialog.setTitle("Token"); 123 | normalDialog.setMessage(token); 124 | normalDialog.setPositiveButton("复制", (dialog, which) -> { 125 | ((ClipboardManager) Objects.requireNonNull(requireActivity().getSystemService(Context.CLIPBOARD_SERVICE))).setPrimaryClip(ClipData.newPlainText("token", token)); 126 | Toast.makeText(getActivity(), "复制成功", Toast.LENGTH_SHORT).show(); 127 | }); 128 | normalDialog.setNeutralButton("取消", (dialog, which) -> { 129 | }); 130 | normalDialog.show(); 131 | return false; 132 | }); 133 | 134 | Preference LoginPreference = findPreference("Login"); 135 | Objects.requireNonNull(LoginPreference).setOnPreferenceClickListener(preference -> { 136 | startActivityForResult(new Intent(getContext(), QQLogin.class), 200); 137 | Toast.makeText(getActivity(), "登录成功后返回即可", Toast.LENGTH_SHORT).show(); 138 | return false; 139 | }); 140 | 141 | Preference clear = findPreference("clear"); 142 | Objects.requireNonNull(clear).setOnPreferenceClickListener(preference -> { 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 144 | final NotificationManager notificationManager = requireContext().getSystemService(NotificationManager.class); 145 | for (NotificationChannel channel : Objects.requireNonNull(notificationManager).getNotificationChannels()) { 146 | notificationManager.deleteNotificationChannel(channel.getId()); 147 | } 148 | } 149 | requireContext().getSharedPreferences("Channels", MODE_PRIVATE).edit().clear().apply(); 150 | Toast.makeText(getActivity(), "已删除通知渠道", Toast.LENGTH_SHORT).show(); 151 | return true; 152 | }); 153 | } 154 | 155 | @Override 156 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 157 | super.onViewCreated(view, savedInstanceState); 158 | 159 | FirebaseMessaging.getInstance().getToken() 160 | .addOnCompleteListener(task -> { 161 | if (!task.isSuccessful()) { 162 | Log.w(Const.TAG, "Fetching FCM registration token failed", task.getException()); 163 | Toast.makeText(requireActivity(), "获取token失败", Toast.LENGTH_SHORT).show(); 164 | return; 165 | } 166 | token = task.getResult(); 167 | }); 168 | 169 | getListView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 170 | getListView().setFitsSystemWindows(true); 171 | // 172 | // final ActionBar actionBar = Objects.requireNonNull(((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()); 173 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 174 | // getListView().setOnScrollChangeListener(new View.OnScrollChangeListener() { 175 | // boolean lastScrollinTop = true; 176 | // ValueAnimator animation = ValueAnimator.ofInt(0, 8); 177 | // 178 | // { 179 | // animation.setDuration(400); 180 | // animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 181 | // @Override 182 | // public void onAnimationUpdate(ValueAnimator animation) { 183 | // actionBar.setElevation((int) animation.getAnimatedValue()); 184 | // } 185 | // }); 186 | // } 187 | // 188 | // @Override 189 | // public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { 190 | // if (!v.canScrollVertically(-1) && !lastScrollinTop) { 191 | // animation.reverse(); 192 | // lastScrollinTop = true; 193 | // } else if (lastScrollinTop) { 194 | // animation.start(); 195 | // lastScrollinTop = false; 196 | // } 197 | // } 198 | // }); 199 | // } else 200 | // actionBar.setElevation(8); 201 | } 202 | 203 | @Override 204 | public void onStart() { 205 | super.onStart(); 206 | if (isNotificationListenersEnabled()) { 207 | input.setEnabled(false); 208 | start.setChecked(true); 209 | } else { 210 | input.setEnabled(true); 211 | start.setChecked(false); 212 | } 213 | hide.setChecked(isAccessibilitySettingsOn(requireActivity())); 214 | } 215 | 216 | @Override 217 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 218 | final String pskey; 219 | final String skey; 220 | final String uin; 221 | final String token; 222 | if (requestCode == 200) { 223 | if (resultCode == 0) { 224 | Toast.makeText(getActivity(), "未拿到Cookies", Toast.LENGTH_SHORT).show(); 225 | return; 226 | } 227 | pskey = Objects.requireNonNull(data.getExtras()).getString("pskey"); 228 | skey = Objects.requireNonNull(data.getExtras()).getString("skey"); 229 | uin = Objects.requireNonNull(data.getExtras()).getString("uin"); 230 | token = Objects.requireNonNull(data.getExtras()).getString("token"); 231 | assert skey != null; 232 | final long bkn = GetBkn(skey); 233 | final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(requireContext().getDatabasePath("friends.db"), null); 234 | 235 | AlertDialog.Builder listDialog = new MaterialAlertDialogBuilder(requireActivity()); 236 | listDialog.setTitle("你需要同步"); 237 | listDialog.setItems(new String[]{"好友", "群组"}, (dialog, click) -> { 238 | switch (click) { 239 | case 0: 240 | Toast.makeText(getActivity(), "开始同步好友", Toast.LENGTH_SHORT).show(); 241 | new Thread() { 242 | @Override 243 | public void run() { 244 | try { 245 | HttpURLConnection connection = connect(new URL("https://qun.qq.com/cgi-bin/qun_mgr/get_friend_list"), pskey, skey, uin, token); 246 | DataOutputStream out = new DataOutputStream(connection.getOutputStream()); 247 | out.writeBytes("bkn=" + bkn); 248 | out.flush(); 249 | out.close(); 250 | connection.connect(); 251 | String json = null; 252 | if (connection.getResponseCode() == 200) { 253 | json = parseResult(connection.getInputStream()); 254 | } 255 | connection.disconnect(); 256 | if (json == null) throw new Exception("Result parse error"); 257 | 258 | JSONObject friendsList = new JSONObject(json.substring(37, json.length() - 1)); 259 | db.execSQL("drop table if exists friends"); 260 | db.execSQL("create table friends(uin text primary key,name text)"); 261 | for (Iterator it = friendsList.keys(); it.hasNext(); ) { 262 | String key = it.next(); 263 | JSONArray friends = friendsList.getJSONObject(key).optJSONArray("mems"); 264 | if (friends == null) continue; 265 | for (int j = 0; j < friends.length(); j++) { 266 | JSONObject friend = friends.getJSONObject(j); 267 | db.execSQL("INSERT INTO friends VALUES (?, ?)", new String[]{friend.getString("uin"), URLDecoder.decode(Html.fromHtml(friend.getString("name")).toString(), "UTF-8")}); 268 | } 269 | } 270 | 271 | requireActivity().runOnUiThread(new Runnable() { 272 | public void run() { 273 | requireActivity().getSharedPreferences("groups", MODE_PRIVATE).edit().putBoolean("sync_friends", true).apply(); 274 | Toast.makeText(getContext(), "同步成功", Toast.LENGTH_SHORT).show(); 275 | } 276 | }); 277 | } catch (Exception e) { 278 | Log.e(Const.TAG, e.getMessage(), e); 279 | requireActivity().runOnUiThread(new Runnable() { 280 | public void run() { 281 | Toast.makeText(getContext(), "解析错误", Toast.LENGTH_SHORT).show(); 282 | } 283 | }); 284 | } finally { 285 | db.close(); 286 | } 287 | } 288 | }.start(); 289 | break; 290 | case 1: 291 | Toast.makeText(getActivity(), "开始同步群组", Toast.LENGTH_SHORT).show(); 292 | new Thread() { 293 | @Override 294 | public void run() { 295 | try { 296 | HttpURLConnection getGroup = connect(new URL("https://qun.qq.com/cgi-bin/qun_mgr/get_group_list"), pskey, skey, uin, token); 297 | DataOutputStream getGroupOut = new DataOutputStream(getGroup.getOutputStream()); 298 | getGroupOut.writeBytes("bkn=" + bkn); 299 | getGroupOut.flush(); 300 | getGroupOut.close(); 301 | getGroup.connect(); 302 | if (getGroup.getResponseCode() == 200) { 303 | String json = parseResult(getGroup.getInputStream()); 304 | getGroup.disconnect(); 305 | if (json.length() < 100) throw new Exception(); 306 | JSONObject allList = new JSONObject(json); 307 | String joinList = allList.getJSONArray("join").toString().replace("]", ","); 308 | String manageList = allList.getJSONArray("manage").toString().substring(1); 309 | final JSONArray groupsList = new JSONArray(joinList + manageList); 310 | final String[] groupNames = new String[groupsList.length()]; 311 | SharedPreferences.Editor editor = requireActivity().getSharedPreferences("groupsNumber", MODE_PRIVATE).edit(); 312 | for (int i = 0; i < groupsList.length(); i++) { 313 | JSONObject group = groupsList.getJSONObject(i); 314 | String groupName = Html.fromHtml(group.getString("gn").replace("&", "&").replace(" ", " ")).toString(); 315 | groupNames[i] = groupName; 316 | editor.putString(groupName, group.getString("gc")); 317 | } 318 | editor.apply(); 319 | if (getActivity() != null) 320 | getActivity().runOnUiThread(() -> { 321 | final ArrayList choices = new ArrayList<>(); 322 | AlertDialog.Builder ChoiceDialog = new MaterialAlertDialogBuilder(getActivity()); 323 | ChoiceDialog.setTitle("选择要同步的群组"); 324 | ChoiceDialog.setMultiChoiceItems(groupNames, null, (dialog1, which, isChecked) -> { 325 | if (isChecked) 326 | choices.add(which); 327 | else 328 | choices.remove(Integer.valueOf(which)); 329 | }); 330 | ChoiceDialog.setPositiveButton("确定", (dialog12, which) -> { 331 | if (choices.isEmpty()) 332 | return; 333 | final ProgressDialog waitingDialog = new ProgressDialog(getActivity()); 334 | waitingDialog.setTitle("同步中"); 335 | waitingDialog.setMessage(groupNames[choices.get(0)]); 336 | waitingDialog.setIndeterminate(true); 337 | waitingDialog.setCancelable(false); 338 | waitingDialog.show(); 339 | new Thread() { 340 | @Override 341 | public void run() { 342 | SharedPreferences.Editor editor = requireActivity().getSharedPreferences("groups", MODE_PRIVATE).edit(); 343 | for (final Integer choice : choices) { 344 | requireActivity().runOnUiThread(() -> waitingDialog.setMessage(groupNames[choice])); 345 | final String name = groupNames[choice]; 346 | try { 347 | int temp = 41; 348 | int last = 0; 349 | db.execSQL("drop table if exists '" + name + "'"); 350 | db.execSQL("create table '" + name + "'(uin text primary key,name text)"); 351 | editor.putBoolean(name, true).apply(); 352 | while (temp >= 41) { 353 | HttpURLConnection getMember = connect(new URL("https://qun.qq.com/cgi-bin/qun_mgr/search_group_members"), pskey, skey, uin, token); 354 | DataOutputStream getMemberout = new DataOutputStream(getMember.getOutputStream()); 355 | getMemberout.writeBytes("st=" + last + "&end=" + (last + 40) + "&sort=0&bkn=" + bkn + "&gc=" + groupsList.getJSONObject(choice).getInt("gc")); 356 | last += 41; 357 | getMemberout.flush(); 358 | getMemberout.close(); 359 | getMember.connect(); 360 | if (getMember.getResponseCode() == 200) { 361 | String groupjson = parseResult(getMember.getInputStream()); 362 | getMember.disconnect(); 363 | JSONObject all = new JSONObject(groupjson); 364 | if (groupjson.length() < 40 || all.getString("em").contains("malicious")) 365 | throw new IllegalArgumentException(); 366 | JSONArray members; 367 | if (all.has("mems")) 368 | members = all.getJSONArray("mems"); 369 | else 370 | break; 371 | temp = members.length(); 372 | for (int j = 0; j < temp; j++) { 373 | JSONObject member = members.getJSONObject(j); 374 | String card = member.getString("card"); 375 | db.execSQL("INSERT INTO '" + name + "' VALUES (?, ?)", new Object[]{member.getString("uin"), URLDecoder.decode(Html.fromHtml(card.isEmpty() ? member.getString("nick") : card).toString(), "UTF-8")}); 376 | } 377 | } 378 | getMember.disconnect(); 379 | sleep(400); 380 | } 381 | if (getActivity() != null) 382 | getActivity().runOnUiThread(new Runnable() { 383 | public void run() { 384 | Toast.makeText(getContext(), name + "同步成功", Toast.LENGTH_SHORT).show(); 385 | } 386 | }); 387 | } catch (IllegalArgumentException e) { 388 | Log.e(Const.TAG, e.getMessage(), e); 389 | requireActivity().runOnUiThread(new Runnable() { 390 | public void run() { 391 | Toast.makeText(getContext(), name + "被反恶意机制拦截", Toast.LENGTH_SHORT).show(); 392 | } 393 | }); 394 | } catch (SocketTimeoutException e) { 395 | Log.e(Const.TAG, e.getMessage(), e); 396 | requireActivity().runOnUiThread(new Runnable() { 397 | public void run() { 398 | Toast.makeText(getContext(), name + "网络超时", Toast.LENGTH_SHORT).show(); 399 | } 400 | }); 401 | } catch (Exception e) { 402 | Log.e(Const.TAG, e.getMessage(), e); 403 | requireActivity().runOnUiThread(new Runnable() { 404 | public void run() { 405 | Toast.makeText(getContext(), name + "解析错误", Toast.LENGTH_SHORT).show(); 406 | } 407 | }); 408 | } 409 | try { 410 | sleep(400); 411 | } catch (InterruptedException e1) { 412 | e1.printStackTrace(); 413 | } 414 | } 415 | editor.commit(); 416 | waitingDialog.dismiss(); 417 | db.close(); 418 | } 419 | }.start(); 420 | }); 421 | ChoiceDialog.show(); 422 | }); 423 | } 424 | getGroup.disconnect(); 425 | if (getActivity() != null) 426 | getActivity().runOnUiThread(new Runnable() { 427 | public void run() { 428 | Toast.makeText(getContext(), "列表加载成功", Toast.LENGTH_SHORT).show(); 429 | } 430 | }); 431 | } catch (Exception e) { 432 | Log.e(Const.TAG, e.getMessage(), e); 433 | requireActivity().runOnUiThread(new Runnable() { 434 | public void run() { 435 | Toast.makeText(getContext(), "解析错误", Toast.LENGTH_SHORT).show(); 436 | } 437 | }); 438 | } 439 | } 440 | }.start(); 441 | break; 442 | } 443 | }); 444 | listDialog.show(); 445 | } 446 | } 447 | 448 | private HttpURLConnection connect(URL url, String pskey, String skey, String uin, String token) throws IOException { 449 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 450 | connection.setDoOutput(true); 451 | connection.setDoInput(true); 452 | connection.setConnectTimeout(2000); 453 | connection.setReadTimeout(2000); 454 | connection.setRequestMethod("POST"); 455 | connection.setRequestProperty("cookie", "p_skey=" + pskey + ";p_uin=" + uin + ";pt4_token=" + token + ";uin=" + uin + ";skey=" + skey); 456 | return connection; 457 | } 458 | 459 | private String parseResult(InputStream inputStream) throws IOException { 460 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 461 | byte[] buffer = new byte[1024]; 462 | int length; 463 | while ((length = inputStream.read(buffer)) != -1) { 464 | result.write(buffer, 0, length); 465 | } 466 | inputStream.close(); 467 | return result.toString("UTF-8"); 468 | } 469 | 470 | private long GetBkn(String skey) { 471 | int t = 5381, n = 0; 472 | int o = skey.length(); 473 | for (; n < o; ++n) 474 | t += (t << 5) + skey.charAt(n); 475 | return 2147483647 & t; 476 | } 477 | 478 | private boolean isNotificationListenersEnabled() { 479 | String pkgName = requireActivity().getPackageName(); 480 | final String flat = Settings.Secure.getString(requireActivity().getContentResolver(), "enabled_notification_listeners"); 481 | if (!TextUtils.isEmpty(flat)) { 482 | final String[] names = flat.split(":"); 483 | for (String name : names) { 484 | final ComponentName cn = ComponentName.unflattenFromString(name); 485 | if (cn != null) { 486 | if (TextUtils.equals(pkgName, cn.getPackageName())) { 487 | return true; 488 | } 489 | } 490 | } 491 | } 492 | return false; 493 | } 494 | 495 | private boolean isAccessibilitySettingsOn(Context mContext) { 496 | int accessibilityEnabled; 497 | final String service = requireActivity().getPackageName() + "/" + ForegroundMonitor.class.getCanonicalName(); 498 | try { 499 | accessibilityEnabled = Settings.Secure.getInt( 500 | mContext.getApplicationContext().getContentResolver(), 501 | android.provider.Settings.Secure.ACCESSIBILITY_ENABLED); 502 | } catch (Settings.SettingNotFoundException e) { 503 | return false; 504 | } 505 | TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':'); 506 | 507 | if (accessibilityEnabled == 1) { 508 | String settingValue = Settings.Secure.getString( 509 | mContext.getApplicationContext().getContentResolver(), 510 | Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 511 | if (settingValue != null) { 512 | mStringColonSplitter.setString(settingValue); 513 | while (mStringColonSplitter.hasNext()) { 514 | String accessibilityService = mStringColonSplitter.next(); 515 | if (accessibilityService.equalsIgnoreCase(service)) { 516 | return true; 517 | } 518 | } 519 | } 520 | } 521 | return false; 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/QQInstallReceiver.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import static android.content.Context.MODE_PRIVATE; 4 | 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.content.pm.ApplicationInfo; 10 | 11 | import java.util.List; 12 | 13 | public class QQInstallReceiver extends BroadcastReceiver { 14 | @Override 15 | public void onReceive(Context context, Intent intent) { 16 | String packageName = intent.getData().getSchemeSpecificPart(); 17 | for (String qqName : Const.QQ_NAMES) { 18 | if (qqName.equals(packageName)) { 19 | if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) 20 | context.getSharedPreferences("MainActivity", MODE_PRIVATE).edit().putString("installedQQ", packageName).apply(); 21 | else 22 | context.getSharedPreferences("MainActivity", MODE_PRIVATE).edit().putString("installedQQ", null).apply(); 23 | break; 24 | } 25 | } 26 | } 27 | 28 | static void findQQPackage(Context context) { 29 | SharedPreferences preferences = context.getSharedPreferences("MainActivity", MODE_PRIVATE); 30 | List packageInfo = context.getPackageManager().getInstalledApplications(0); 31 | String installedQQ = preferences.getString("installedQQ", ""); 32 | String current = null; 33 | for (ApplicationInfo info : packageInfo) { 34 | if (info.packageName.equals(installedQQ)) break; 35 | for (String QQName : Const.QQ_NAMES) { 36 | if (QQName.equals(info.packageName) && info.enabled) { 37 | current = info.packageName; 38 | preferences.edit().putString("installedQQ", current).apply(); 39 | break; 40 | } 41 | } 42 | if (current != null) break; 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/QQLogin.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.webkit.CookieManager; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | import android.widget.Toast; 12 | 13 | import androidx.appcompat.app.AppCompatActivity; 14 | 15 | import java.util.Objects; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | public class QQLogin extends AppCompatActivity { 20 | WebView web; 21 | 22 | @SuppressLint("SetJavaScriptEnabled") 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setTheme(ThemeProvider.getCurrentStyle(this)); 27 | setContentView(R.layout.qq_login); 28 | Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); 29 | 30 | web = findViewById(R.id.web); 31 | web.setWebViewClient(new WebViewClient() { 32 | @Override 33 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 34 | return false; 35 | } 36 | }); 37 | web.getSettings().setJavaScriptEnabled(true); 38 | if (CookieManager.getInstance().getCookie("https://qun.qq.com") != null) 39 | web.loadUrl("https://qun.qq.com/member.html"); 40 | else 41 | web.loadUrl("https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_disable_pwd=0&appid=715030901&daid=73&hide_close_icon=1&pt_no_auth=1&s_url=https%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D8292362"); 42 | } 43 | 44 | @Override 45 | public boolean onCreateOptionsMenu(Menu menu) { 46 | getMenuInflater().inflate(R.menu.webmenu, menu); 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean onOptionsItemSelected(MenuItem item) { 52 | switch (item.getItemId()) { 53 | case R.id.clear_cookies: 54 | CookieManager.getInstance().removeAllCookies(null); 55 | Toast.makeText(this, "清除完成", Toast.LENGTH_SHORT).show(); 56 | break; 57 | case R.id.refresh: 58 | web.reload(); 59 | break; 60 | case android.R.id.home: 61 | finish(); 62 | return true; 63 | } 64 | return super.onOptionsItemSelected(item); 65 | } 66 | 67 | @Override 68 | public void finish() { 69 | Intent intent = new Intent(); 70 | String cookies = CookieManager.getInstance().getCookie("https://qun.qq.com") + ";"; 71 | Matcher matcher_pskey = Pattern.compile("p_skey=(.*?);").matcher(cookies); 72 | Matcher matcher_skey = Pattern.compile("skey=(.*?);").matcher(cookies); 73 | Matcher matcher_uin = Pattern.compile("p_uin=(.*?);").matcher(cookies); 74 | Matcher matcher_token = Pattern.compile("pt4_token=(.*?);").matcher(cookies); 75 | if (matcher_pskey.find() && matcher_skey.find() && matcher_uin.find() && matcher_token.find()) { 76 | intent.putExtra("pskey", matcher_pskey.group(1)); 77 | intent.putExtra("skey", matcher_skey.group(1)); 78 | intent.putExtra("uin", matcher_uin.group(1)); 79 | intent.putExtra("token", matcher_token.group(1)); 80 | setResult(RESULT_OK, intent); 81 | } else 82 | setResult(0); 83 | super.finish(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/ThemeProvider.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | 6 | import androidx.core.content.ContextCompat; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class ThemeProvider { 11 | private static class Theme { 12 | String name; 13 | int style; 14 | int color; 15 | 16 | public Theme(String name, int style, int color) { 17 | this.name = name; 18 | this.style = style; 19 | this.color = color; 20 | } 21 | } 22 | 23 | final static private ArrayList themeList = new ArrayList<>(); 24 | 25 | static { 26 | themeList.add(new Theme("水鸭青", R.style.base_DayNight_AppTheme_teal, R.color.teal)); 27 | themeList.add(new Theme("姨妈红", R.style.base_DayNight_AppTheme_red, R.color.red)); 28 | themeList.add(new Theme("哔哩粉", R.style.base_DayNight_AppTheme_pink, R.color.pink)); 29 | themeList.add(new Theme("基佬紫", R.style.base_DayNight_AppTheme_purple, R.color.purple)); 30 | themeList.add(new Theme("很深蓝", R.style.base_DayNight_AppTheme_blue, R.color.blue)); 31 | themeList.add(new Theme("非常黄", R.style.base_DayNight_AppTheme_yellow, R.color.yellow)); 32 | themeList.add(new Theme("真的灰", R.style.base_DayNight_AppTheme_grey, R.color.grey)); 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) 34 | themeList.add(new Theme("Android12+", R.style.base_DayNight_AppTheme_android12plus, R.color.android12plus)); 35 | } 36 | 37 | static Theme selected = null; 38 | 39 | static String[] getThemeNameList() { 40 | String[] themeNameList = new String[themeList.size()]; 41 | for (int i = 0; i < themeList.size(); i++) { 42 | themeNameList[i] = themeList.get(i).name; 43 | } 44 | return themeNameList; 45 | } 46 | 47 | static void setTheme(Context context, int i) { 48 | context.getSharedPreferences("theme", Context.MODE_PRIVATE).edit().putString("selected", themeList.get(i).name).apply(); 49 | selected = themeList.get(i); 50 | } 51 | 52 | static Theme getCurrentTheme(Context context) { 53 | if (selected == null) { 54 | String themeName = context.getSharedPreferences("theme", Context.MODE_PRIVATE).getString("selected", themeList.get(0).name); 55 | for (int i = 0; i < themeList.size(); i++) { 56 | Theme theme = themeList.get(i); 57 | if (theme.name.equals(themeName)) { 58 | selected = theme; 59 | break; 60 | } 61 | } 62 | } 63 | return selected; 64 | } 65 | 66 | static int getCurrentStyle(Context context) { 67 | return getCurrentTheme(context).style; 68 | } 69 | 70 | static int getCurrentColor(Context context) { 71 | return ContextCompat.getColor(context, getCurrentTheme(context).color); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/RichardLuo/notificationpush/Utils.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import android.util.Log; 4 | 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | public class Utils { 9 | public static String md5(final String s) { 10 | final String MD5 = "MD5"; 11 | try { 12 | MessageDigest digest = java.security.MessageDigest 13 | .getInstance(MD5); 14 | digest.update(s.getBytes()); 15 | byte[] messageDigest = digest.digest(); 16 | 17 | StringBuilder hexString = new StringBuilder(); 18 | for (byte aMessageDigest : messageDigest) { 19 | StringBuilder h = new StringBuilder(Integer.toHexString(0xFF & aMessageDigest)); 20 | while (h.length() < 2) 21 | h.insert(0, "0"); 22 | hexString.append(h); 23 | } 24 | return hexString.toString(); 25 | } catch (NoSuchAlgorithmException e) { 26 | Log.e(Const.TAG, e.getMessage(), e); 27 | } 28 | return "unknown"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/anim/down_from_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/anim/up_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 11 | 12 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_filter.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_reverse.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_search.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-hdpi/ic_filter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-hdpi/ic_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-hdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-mdpi/ic_filter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-mdpi/ic_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-mdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xhdpi/ic_filter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xhdpi/ic_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xxhdpi/ic_filter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xxhdpi/ic_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/drawable-xxhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_application.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 13 | 14 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 28 | 29 | 35 | 36 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dropdown.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/layout/qq_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/menu/appmenu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 16 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/webmenu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_200 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4db6ac 4 | #ec407a 5 | #f06292 6 | #ba68c8 7 | #7986cb 8 | #ffd54f 9 | #90a4ae 10 | 11 | #000000 12 | #ffffff 13 | 14 | #cc000000 15 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw360dp/values-preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 0dp 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_600 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | fcm通知转发 3 | GetNotification 4 | ForegroundMonitor 5 | 应用前台时隐藏通知 6 | 服务端 7 | 开始转发 8 | 隐藏无内容通知 9 | 隐藏无标题无内容通知 10 | 设备ID 11 | 希望发送的地址 12 | 更改通知优先级 13 | 更改发送优先级 14 | 15 | 16 | 客户端 17 | 隐藏前台应用通知 18 | token 19 | 本机token 20 | 删除所有通知渠道 21 | 删除通知渠道 22 | 开启QQ头像显示 23 | 下载并显示对应头像 24 | 同步QQ头像数据库 25 | 请不要重复同步过多次数 26 | 27 | 28 | 普通优先级 29 | 高优先级 30 | 关闭通知 31 | 32 | 33 | 更换颜色 34 | 关于 35 | 此应用可以通过fcm通道从一台手机向另一台手机转发通知\n如果token显示fail,请检查Google框架并尝试翻墙重启应用\n你只需要将token(就是那堆乱码)复制到服务端的输入框,点击开始转发即可开始推送\n注意设置优先级指发送的优先级\n要开启头像显示需至少先同步一次数据库\n服务端必须24小时翻墙\n—by Adama 36 | 应用 37 | QQ 登录 38 | 清除cookies 39 | 刷新 40 | Logcat 41 | 过滤系统app 42 | 以前台服务形式启动 43 | 增大服务存活率 44 | 关闭全部应用通知 45 | 开启全部提示音 46 | 每一条通知都将会有提示音 47 | 搜索 48 | 重置为正常优先级 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #009688 4 | #f44336 5 | #e91e63 6 | #9c27b0 7 | #3f51b5 8 | #ffc107 9 | #607d8b 10 | 11 | #EAEAEA 12 | #000000 13 | 14 | #ccffffff 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NotificationPush 3 | GetNotification 4 | ForegroundMonitor 5 | Hide notification when app is foreground 6 | Server 7 | Start 8 | Hide No Content 9 | Hide notification which has no content 10 | DeviceID 11 | Where do you want to send 12 | Change Priority 13 | Change send priority 14 | 15 | Client 16 | Hide when Foreground 17 | Token 18 | Local token 19 | Remove All Channels 20 | Remove notification channels 21 | Show Avatar for QQ 22 | Download and show QQ avatar 23 | Sync QQ Contacts Datebase 24 | Please do not sync repeatly 25 | 26 | 27 | Normal 28 | High 29 | off 30 | 31 | 32 | Change Color 33 | About 34 | 此应用可以通过fcm通道从一台手机向另一台手机转发通知\n如果token显示fail,请检查Google框架并尝试翻墙重启应用\n你只需要将token(就是那堆乱码)复制到服务端的输入框,点击开始转发即可开始推送\n注意设置优先级指发送的优先级\n要开启头像显示需至少先同步一次数据库\n服务端必须24小时翻墙\n—by Adama 35 | Application 36 | QQ Login 37 | Clear cookies 38 | Refresh 39 | Logcat 40 | exclude system app 41 | Start Foreground service 42 | To keep alive 43 | close all Notification 44 | Alert every time 45 | Alert for every notice 46 | search 47 | reset to normal 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 25 | 26 | 29 | 30 | 31 | 35 | 36 | 40 | 41 | 45 | 46 | 50 | 51 | 55 | 56 | 60 | 61 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/xml/accessibility.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 13 | 18 | 24 | 27 | 31 | 32 | 36 | 37 | 38 | 39 | 43 | 47 | 51 | 56 | 61 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/xml/searchable.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/java/com/RichardLuo/notificationpush/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.RichardLuo.notificationpush; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.1.3' 11 | classpath 'com.google.gms:google-services:4.3.10' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | android.useAndroidX=true 11 | android.enableJetifier=true 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | 17 | 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardLuo0/NotificationPush/972d27a3f6fec13859e0de10554d3dea2066a92a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 18 15:19:16 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------