├── .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 |
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 | 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 |
4 |
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 |
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 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/webmenu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------