├── .gitignore
├── .idea
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── STATUS.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── simonk
│ │ └── projects
│ │ └── taskmanager
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── simonk
│ │ │ └── projects
│ │ │ └── taskmanager
│ │ │ ├── database
│ │ │ ├── DatabaseManager.java
│ │ │ ├── LocalDatabase.java
│ │ │ ├── entity
│ │ │ │ ├── BlacklistEntity.java
│ │ │ │ └── TerminalSnapshotEntity.java
│ │ │ └── providers
│ │ │ │ ├── BlacklistProvider.java
│ │ │ │ └── TerminalSnapshotProvider.java
│ │ │ ├── entity
│ │ │ ├── AppInfo.java
│ │ │ ├── ProcessInfo.java
│ │ │ └── TerminalCall.java
│ │ │ ├── repository
│ │ │ ├── BlacklistRepository.java
│ │ │ └── ProcessRepository.java
│ │ │ ├── services
│ │ │ └── blacklist
│ │ │ │ └── BlacklistJobService.java
│ │ │ ├── terminal
│ │ │ ├── StringTerminalListener.java
│ │ │ ├── Terminal.java
│ │ │ ├── TerminalListener.java
│ │ │ ├── TerminalService.java
│ │ │ ├── handlers
│ │ │ │ ├── BlankCommandHandler.java
│ │ │ │ ├── CdCommandHandler.java
│ │ │ │ ├── EchoCommandHandler.java
│ │ │ │ ├── ExportCommandHandler.java
│ │ │ │ ├── PwdCommandHandler.java
│ │ │ │ └── RequestHandler.java
│ │ │ └── interceptors
│ │ │ │ ├── TerminalRequestInterceptor.java
│ │ │ │ └── TopCommandInterceptor.java
│ │ │ ├── ui
│ │ │ ├── BindingActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── blacklist
│ │ │ │ ├── BlackListAdapter.java
│ │ │ │ ├── BlacklistFragment.java
│ │ │ │ └── viewmodels
│ │ │ │ │ └── BlacklistViewModel.java
│ │ │ ├── process
│ │ │ │ ├── ChangeDetailsDialog.java
│ │ │ │ ├── CleanedDialog.java
│ │ │ │ ├── ProcessAdapter.java
│ │ │ │ ├── ProcessFragment.java
│ │ │ │ └── viewmodels
│ │ │ │ │ └── ProcessViewModel.java
│ │ │ ├── terminal
│ │ │ │ ├── TerminalFragment.java
│ │ │ │ └── viewmodels
│ │ │ │ │ └── TerminalViewModel.java
│ │ │ └── util
│ │ │ │ └── ObjectListAdapter.java
│ │ │ └── util
│ │ │ ├── MemoryUtils.java
│ │ │ └── ProcessCompat.java
│ └── res
│ │ ├── drawable-anydpi
│ │ └── ic_close.xml
│ │ ├── drawable-hdpi
│ │ └── ic_close.png
│ │ ├── drawable-mdpi
│ │ └── ic_close.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_close.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_close.png
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── blacklist_item.xml
│ │ ├── blacklist_item_label.xml
│ │ ├── change_details_dialog.xml
│ │ ├── cleaned_dialog.xml
│ │ ├── fragment_blacklist.xml
│ │ ├── fragment_process.xml
│ │ ├── fragment_terminal.xml
│ │ ├── process_list_item.xml
│ │ └── process_list_item_details.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-ru
│ │ └── strings.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── simonk
│ └── projects
│ └── taskmanager
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── apps.png
├── blacklist.png
└── terminal.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | /debug_keystore.properties
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android-Process-Manager - beautiful manager of processes, apps and terminal for Android
2 |
3 | Apps provide you information about memory usage and help you to clean unnecessary running apps.
4 | Let you block running apps in backgroud and also show scary message when you (or not you) launch other app that was added in blacklist of this app - parental control you need!
5 | But if you wanna work with processes and apps like a pro, you can use terminal and send requests to Android shell
6 |
7 | ### You've never seen anything like this before!!!
8 |
9 | ### Features:
10 | * Show running processes
11 | * Kill a process you want
12 | * Show you memory usage
13 | * Clean memory as much as possible
14 | * Show additional information about process
15 | * Show all installed (and not system) apps
16 | * Add apps in blacklist
17 | * Stop background processes of applications that were added to blacklist
18 | * Show notification with scary message when blocked app is running
19 | * Show last time blocked app was opened (for parental control, yeah)
20 | * Let you work with Android shell
21 |
22 | ### Languages supported:
23 | * English
24 | * Russian
25 |
26 | ### Screenshots:
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | #### Check STATUS.md to see tasks and progress :)
38 |
--------------------------------------------------------------------------------
/STATUS.md:
--------------------------------------------------------------------------------
1 | | Title | Description | Deadline | Finished |
2 | |----------------------|:---------------------:|-------:|------:|
3 | | Init and configure project | Initialize project, configure build.gradle and other settings for modern work process. Modern dependencies, debug keystore, versioning of apk | 15.04 | 29.03 |
4 | | Show working processes | Create fragment with recycler view that shows list of running proccesses. Icon, process name, process package. Tap on item should show additional information | 15.04 | 07.04 |
5 | | Let process be killed | Let user to kill a process. Button in list item that appears when user makes one tap | 15.04 | 27.04 |
6 | | Clean memory | Add feature to kill all apps. Show memory usage and percent of free memory. Button like Clean up to remove process from memory. Show result dialog: memory before cleaning, memory after cleaning, effectiveness | 15.04 | 11.04 |
7 | | Change process details | Long tap should show dialog where you can change process details. Figure out what we can change | 15.04 | 11.04 |
8 | | Clean architecture, Tabs | Add tabs in main acitivity, all other ui logic in corresponding fragments. Introduce Clean architecture with Room, View Model, Live Data, Repository | 20.04 | 21.04 (Lack of time) |
9 | | Blacklist fragment add. All apps | Show all installed apps in recyclerview in special list. Create separated adapter with twe different lists in one | 01.05 | 28.04 |
10 | | Blacklist. Persistance storage | Create database to store blacklist data. Show blacklist apps in special part of recyelr view (may be in top) | 01.05 | 28.04 |
11 | | Background killing | Make blacklist apps to be killed by app. Create JobScheduler service for this puprose | 01.05 | 29.04 |
12 | | Notification | Send notification to user when blacklist app is running. Show scary message. Make notification sound. Save timestamp to show user when blacklist app was opened last time | 01.05 | 29.04 |
13 | | Terminal fragment add. Terminal simple output | Create simple fragment where corresponding edit text and text view will be created dynamically. Create terminal class where new processes for commands will be created | 01.05 | 23.04 |
14 | | Terminal. Add request handlers | Create request handlers for commands: pwd, cd, echo, export to handle it manually. Use brain to create it maintainable | 01.05 | 23.04 |
15 | | Terminal. Add multithreading | Create HandlerThread to perform temrinal process in other thread. Add listeners to get data in ui. Add handlers to communicate with thread | 01.05 | 23.04 |
16 | | Terminal. Top command | Let terminal to handle input stream while command is running and perform some commands in other way. Support top command. Show output continuously while user will not stop. Show top command output correctly: first memory usage, than table headers and then all processes. When new refresh occure, refresh textview and not add new output to bottom of fragment, just change current textview content | 01.05 | 26.04 |
17 | | Terminal. Database | Add history to temrinal. Save history in database to let user see previous commands. Also let user tap on previous command to reuse it | 01.05 | 27.04 |
18 | | Terminal. Database | Add history to temrinal. Save history in database to let user see previous commands. Also let user tap on previous command to reuse it | 01.05 | 27.04 |
19 | | Localization. Russion. English | Add localization to app: russian, english | 03.05 | 01.05 |
20 | | Process and blacklist multithreading | Add advanced multithreading to repositories, make database manager work with thread pool executor | 15.05 | in progress |
21 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | def getVersion(){
4 | def cmd = 'git describe --tags'
5 | Process process = cmd.execute()
6 | String[] descriptionInfo = process.in.text.split('-')
7 | if (descriptionInfo.length == 1) {
8 | descriptionInfo[0] = descriptionInfo[0].replace('\n', '')
9 | return descriptionInfo[0]+"."+0
10 | }
11 | return descriptionInfo[0] + "." + descriptionInfo[1]
12 | }
13 |
14 | def debugKeystoreProperties = new Properties()
15 | debugKeystoreProperties.load(new FileInputStream(rootProject.file("debug_keystore.properties")))
16 |
17 | android {
18 | signingConfigs {
19 | debug_config {
20 | keyAlias debugKeystoreProperties['keyAlias']
21 | keyPassword debugKeystoreProperties['keyPassword']
22 | storeFile file(debugKeystoreProperties['storeFile'])
23 | storePassword debugKeystoreProperties['storePassword']
24 | }
25 | }
26 |
27 | compileSdkVersion 28
28 | defaultConfig {
29 | applicationId "com.simonk.projects.taskmanager"
30 | minSdkVersion 21
31 | targetSdkVersion 28
32 | versionCode 1
33 | versionName getVersion()
34 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
35 | }
36 |
37 | android.applicationVariants.all { variant ->
38 | variant.outputs.all {
39 | outputFileName = "${rootProject.name}-${variant.versionName}.apk"
40 | }
41 | }
42 |
43 | buildTypes {
44 | debug {
45 | signingConfig signingConfigs.debug_config
46 | }
47 | release {
48 | minifyEnabled false
49 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
50 | }
51 | }
52 |
53 | compileOptions {
54 | sourceCompatibility JavaVersion.VERSION_1_8
55 | targetCompatibility JavaVersion.VERSION_1_8
56 | }
57 |
58 | dataBinding.enabled = true
59 | }
60 |
61 | dependencies {
62 | implementation fileTree(dir: 'libs', include: ['*.jar'])
63 |
64 | implementation 'androidx.appcompat:appcompat:1.0.2'
65 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
66 | implementation "androidx.recyclerview:recyclerview:1.0.0"
67 | implementation "androidx.cardview:cardview:1.0.0"
68 | implementation 'com.google.android.material:material:1.1.0-alpha05'
69 |
70 | implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
71 |
72 | implementation "androidx.room:room-runtime:2.1.0-alpha07"
73 | annotationProcessor "androidx.room:room-compiler:2.1.0-alpha07"
74 |
75 | implementation 'com.google.guava:guava:27.1-jre'
76 |
77 | testImplementation 'junit:junit:4.12'
78 | androidTestImplementation 'androidx.test:runner:1.1.1'
79 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
80 | }
81 |
--------------------------------------------------------------------------------
/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/com/simonk/projects/taskmanager/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.InstrumentationRegistry;
6 | import androidx.test.runner.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getTargetContext();
24 |
25 | assertEquals("com.simonk.projects.taskmanager", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/database/DatabaseManager.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.database;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.lifecycle.LiveData;
6 |
7 | import com.simonk.projects.taskmanager.database.entity.BlacklistEntity;
8 | import com.simonk.projects.taskmanager.database.entity.TerminalSnapshotEntity;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * Makes using LocalDatabase easier. You can still work with LocalDatabase by yourself,
14 | * this class only help you avoid long method calls
15 | *
16 | * Provide multithreading for insert, update, delete operations
17 | *
18 | * TODO: work with threads using thread pool executor
19 | *
20 | */
21 | public class DatabaseManager {
22 |
23 | public static LiveData> loadSnapshots(Context context) {
24 | return LocalDatabase.getInstance(context)
25 | .terminalSnapshotProvider()
26 | .loadSnapshots();
27 | }
28 |
29 | public static void insertSnapshot(Context context, TerminalSnapshotEntity snapshot) {
30 | Thread thread = new Thread(() -> {
31 | LocalDatabase.getInstance(context)
32 | .terminalSnapshotProvider()
33 | .insertSnapshot(snapshot);
34 | });
35 | thread.setName("TaskManager-Database");
36 | thread.start();
37 | }
38 |
39 | public static void deleteAllSnapshots(Context context) {
40 | Thread thread = new Thread(() -> {
41 | LocalDatabase.getInstance(context)
42 | .terminalSnapshotProvider()
43 | .clearSnapshots();
44 | });
45 | thread.setName("TaskManager-Database");
46 | thread.start();
47 | }
48 |
49 | public static LiveData> loadBlacklistEntities(Context context) {
50 | return LocalDatabase.getInstance(context)
51 | .blacklistProvider()
52 | .loadBlacklistEntities();
53 | }
54 |
55 | public static void insertBlacklistEntity(Context context, BlacklistEntity entity) {
56 | Thread thread = new Thread(() -> {
57 | LocalDatabase.getInstance(context)
58 | .blacklistProvider()
59 | .insertBlacklistEntity(entity);
60 | });
61 | thread.setName("TaskManager-Database");
62 | thread.start();
63 | }
64 |
65 | public static void deleteBlacklistEntityByPackage(Context context, String ppackage) {
66 | Thread thread = new Thread(() -> {
67 | LocalDatabase.getInstance(context)
68 | .blacklistProvider()
69 | .deleteBlacklistEntityByPackage(ppackage);
70 | });
71 | thread.setName("TaskManager-Database");
72 | thread.start();
73 | }
74 |
75 | public static void updateBlacklistEntityOpenDate(Context context, String ppackage, long openDate) {
76 | Thread thread = new Thread(() -> {
77 | LocalDatabase.getInstance(context)
78 | .blacklistProvider()
79 | .updateBlacklistEntityOpenDate(ppackage, openDate);
80 | });
81 | thread.setName("TaskManager-Database");
82 | thread.start();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/database/LocalDatabase.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.database;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.room.Room;
6 | import androidx.room.RoomDatabase;
7 |
8 | import com.simonk.projects.taskmanager.database.entity.BlacklistEntity;
9 | import com.simonk.projects.taskmanager.database.entity.TerminalSnapshotEntity;
10 | import com.simonk.projects.taskmanager.database.providers.BlacklistProvider;
11 | import com.simonk.projects.taskmanager.database.providers.TerminalSnapshotProvider;
12 |
13 | @androidx.room.Database(entities = {TerminalSnapshotEntity.class, BlacklistEntity.class}, version = 1, exportSchema = false)
14 | public abstract class LocalDatabase extends RoomDatabase {
15 |
16 | private static LocalDatabase sInstance;
17 |
18 | private static final String DATABASE_NAME = "TaskManagerDatabase";
19 |
20 | /**
21 | * Provider for working with snapshots of terminal.
22 | * See {@link TerminalSnapshotEntity} for more details
23 | * @return
24 | */
25 | public abstract TerminalSnapshotProvider terminalSnapshotProvider();
26 | /**
27 | * Provider for working with blacklist entries.
28 | * See {@link BlacklistEntity} for more details
29 | * @return
30 | */
31 | public abstract BlacklistProvider blacklistProvider();
32 |
33 | public static LocalDatabase getInstance(final Context context) {
34 | if (sInstance == null) {
35 | synchronized (LocalDatabase.class) {
36 | if (sInstance == null) {
37 | sInstance = Room.databaseBuilder(context.getApplicationContext(), LocalDatabase.class, DATABASE_NAME)
38 | .build();
39 | }
40 | }
41 | }
42 | return sInstance;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/database/entity/BlacklistEntity.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.database.entity;
2 |
3 | import androidx.room.Entity;
4 | import androidx.room.PrimaryKey;
5 |
6 | /**
7 | * Entity class for database. Represents information about blocked app, which will be saved in database
8 | */
9 | @Entity(tableName = "blacklist")
10 | public class BlacklistEntity {
11 |
12 | @PrimaryKey(autoGenerate = true)
13 | public int id;
14 |
15 | /**
16 | * Package full name of blocked app
17 | */
18 | public String apppackage;
19 |
20 | /**
21 | * Last time when app was opened in milliseconds
22 | */
23 | public long lastOpenDate;
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/database/entity/TerminalSnapshotEntity.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.database.entity;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.room.Entity;
5 | import androidx.room.PrimaryKey;
6 |
7 | /**
8 | * Entity class for database. Represents information about terminal snapshot, which will be saved in database
9 | * Terminal snapshot is piece of data which contains request and response as strings only.
10 | * Helpful for creating history of terminal
11 | */
12 | @Entity(tableName = "terminal_snapshot")
13 | public class TerminalSnapshotEntity {
14 |
15 | @PrimaryKey(autoGenerate = true)
16 | public int id;
17 |
18 | /**
19 | * Request to terminal
20 | */
21 | public String request;
22 |
23 | /**
24 | * Response of terminal
25 | */
26 | public String response;
27 |
28 | public TerminalSnapshotEntity() {
29 |
30 | }
31 |
32 | public TerminalSnapshotEntity(String request, String response) {
33 | this.request = request;
34 | this.response = response;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/database/providers/BlacklistProvider.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.database.providers;
2 |
3 | import androidx.lifecycle.LiveData;
4 | import androidx.room.Dao;
5 | import androidx.room.Delete;
6 | import androidx.room.Insert;
7 | import androidx.room.Query;
8 | import androidx.room.Update;
9 |
10 | import com.simonk.projects.taskmanager.database.entity.BlacklistEntity;
11 | import com.simonk.projects.taskmanager.database.entity.TerminalSnapshotEntity;
12 |
13 | import java.util.List;
14 |
15 | @Dao
16 | public interface BlacklistProvider {
17 |
18 | @Query("SELECT * FROM blacklist WHERE id=:id")
19 | LiveData loadBlacklistEntity(String id);
20 |
21 | @Query("SELECT * FROM blacklist WHERE id=:id")
22 | BlacklistEntity getBlacklistEntity(String id);
23 |
24 | @Query("SELECT * FROM blacklist")
25 | LiveData> loadBlacklistEntities();
26 |
27 | @Query("SELECT * FROM blacklist")
28 | List getBlacklistEntities();
29 |
30 | @Insert
31 | void insertBlacklistEntity(BlacklistEntity entity);
32 |
33 | @Update
34 | void updateBlacklistEntity(BlacklistEntity entity);
35 |
36 | @Delete
37 | void deleteBlacklistEntity(BlacklistEntity entity);
38 |
39 | @Query("DELETE FROM blacklist WHERE apppackage=:ppackage")
40 | void deleteBlacklistEntityByPackage(String ppackage);
41 |
42 | @Query("DELETE FROM blacklist")
43 | void clearBlacklistEntities();
44 |
45 | @Query("UPDATE blacklist SET lastOpenDate = :openDate WHERE apppackage=:ppackage")
46 | void updateBlacklistEntityOpenDate(String ppackage, long openDate);
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/database/providers/TerminalSnapshotProvider.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.database.providers;
2 |
3 | import androidx.lifecycle.LiveData;
4 | import androidx.room.Dao;
5 | import androidx.room.Delete;
6 | import androidx.room.Insert;
7 | import androidx.room.Query;
8 | import androidx.room.Update;
9 |
10 | import com.simonk.projects.taskmanager.database.entity.TerminalSnapshotEntity;
11 |
12 | import java.util.List;
13 |
14 | @Dao
15 | public interface TerminalSnapshotProvider {
16 |
17 | @Query("SELECT * FROM terminal_snapshot WHERE id=:id")
18 | LiveData loadSnapshot(String id);
19 |
20 | @Query("SELECT * FROM terminal_snapshot WHERE id=:id")
21 | TerminalSnapshotEntity getSnapshot(String id);
22 |
23 | @Query("SELECT * FROM terminal_snapshot")
24 | LiveData> loadSnapshots();
25 |
26 | @Query("SELECT * FROM terminal_snapshot")
27 | List getSnapshots();
28 |
29 | @Insert
30 | void insertSnapshot(TerminalSnapshotEntity entity);
31 |
32 | @Update
33 | void updateSnapshot(TerminalSnapshotEntity entity);
34 |
35 | @Delete
36 | void deleteSnapshot(TerminalSnapshotEntity entity);
37 |
38 | @Query("DELETE FROM terminal_snapshot")
39 | void clearSnapshots();
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/entity/AppInfo.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.entity;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | import java.util.Objects;
6 |
7 | /**
8 | * POJO represents usefull for this app information about other apps.
9 | */
10 | public class AppInfo {
11 |
12 | private String text;
13 | private Drawable image;
14 | private String ppackage;
15 | private boolean isEnabled;
16 | private boolean isSystem;
17 | private boolean isInBlacklist;
18 | private long lastOpenDate;
19 |
20 | public String getText() {
21 | return text;
22 | }
23 |
24 | public void setText(String text) {
25 | this.text = text;
26 | }
27 |
28 | public Drawable getImage() {
29 | return image;
30 | }
31 |
32 | public void setImage(Drawable image) {
33 | this.image = image;
34 | }
35 |
36 | public String getPpackage() {
37 | return ppackage;
38 | }
39 |
40 | public void setPpackage(String ppackage) {
41 | this.ppackage = ppackage;
42 | }
43 |
44 | public boolean isEnabled() {
45 | return isEnabled;
46 | }
47 |
48 | public void setEnabled(boolean enabled) {
49 | isEnabled = enabled;
50 | }
51 |
52 | public boolean getIsSystem() {
53 | return isSystem;
54 | }
55 |
56 | public void setIsSystem(boolean isSystem) {
57 | this.isSystem = isSystem;
58 | }
59 |
60 | public boolean isInBlacklist() {
61 | return isInBlacklist;
62 | }
63 |
64 | public void setInBlacklist(boolean inBlacklist) {
65 | isInBlacklist = inBlacklist;
66 | }
67 |
68 | public long getLastOpenDate() {
69 | return lastOpenDate;
70 | }
71 |
72 | public void setLastOpenDate(long lastOpenDate) {
73 | this.lastOpenDate = lastOpenDate;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/entity/ProcessInfo.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.entity;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | import java.io.Serializable;
6 | import java.util.Objects;
7 |
8 | /**
9 | * POJO represents usefull for this app information about a running process
10 | */
11 | public class ProcessInfo implements Serializable {
12 | private String text;
13 | private Drawable image;
14 | private String ppackage;
15 | private int priority;
16 | private boolean status;
17 | private int minSdk;
18 | private int uid;
19 | private String description;
20 | private int pid;
21 |
22 | public String getText() {
23 | return text;
24 | }
25 |
26 | public void setText(String text) {
27 | this.text = text;
28 | }
29 |
30 | public Drawable getImage() {
31 | return image;
32 | }
33 |
34 | public void setImage(Drawable image) {
35 | this.image = image;
36 | }
37 |
38 | public String getPpackage() {
39 | return ppackage;
40 | }
41 |
42 | public void setPpackage(String ppackage) {
43 | this.ppackage = ppackage;
44 | }
45 |
46 | public int getPriority() {
47 | return priority;
48 | }
49 |
50 | public void setPriority(int priority) {
51 | this.priority = priority;
52 | }
53 |
54 | public boolean isStatus() {
55 | return status;
56 | }
57 |
58 | public void setStatus(boolean status) {
59 | this.status = status;
60 | }
61 |
62 | public int getMinSdk() {
63 | return minSdk;
64 | }
65 |
66 | public void setMinSdk(int minSdk) {
67 | this.minSdk = minSdk;
68 | }
69 |
70 | public int getUid() {
71 | return uid;
72 | }
73 |
74 | public void setUid(int uid) {
75 | this.uid = uid;
76 | }
77 |
78 | public String getDescription() {
79 | return description;
80 | }
81 |
82 | public void setDescription(String description) {
83 | this.description = description;
84 | }
85 |
86 | public int getPid() {
87 | return pid;
88 | }
89 |
90 | public void setPid(int pid) {
91 | this.pid = pid;
92 | }
93 |
94 | @Override
95 | public boolean equals(Object o) {
96 | if (this == o) return true;
97 | if (o == null || getClass() != o.getClass()) return false;
98 | ProcessInfo that = (ProcessInfo) o;
99 | return getPriority() == that.getPriority() &&
100 | isStatus() == that.isStatus() &&
101 | getMinSdk() == that.getMinSdk() &&
102 | getUid() == that.getUid() &&
103 | getPid() == that.getPid() &&
104 | Objects.equals(getText(), that.getText()) &&
105 | Objects.equals(getImage(), that.getImage()) &&
106 | Objects.equals(getPpackage(), that.getPpackage()) &&
107 | Objects.equals(getDescription(), that.getDescription());
108 | }
109 |
110 | @Override
111 | public int hashCode() {
112 | return Objects.hash(getText(), getImage(), getPpackage(), getPriority(), isStatus(),
113 | getMinSdk(), getUid(), getDescription(), getPid());
114 | }
115 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/entity/TerminalCall.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.entity;
2 |
3 | import java.io.InputStream;
4 | import java.io.OutputStream;
5 | import java.util.Objects;
6 |
7 | /**
8 | * POJO represents terminal call. Contains request to terminal and different streams of corresponding response
9 | */
10 | public class TerminalCall {
11 |
12 | private String request;
13 | private Exception exception;
14 |
15 | private Process process;
16 |
17 | private InputStream inputStream;
18 |
19 | /**
20 | * @return request to terminal. Additionally trim output
21 | */
22 | public String getRequest() {
23 | return request.trim();
24 | }
25 |
26 | public void setRequest(String request) {
27 | this.request = request;
28 | }
29 |
30 | public void setProcess(Process process) {
31 | this.process = process;
32 | }
33 |
34 | public Process getProcess() {
35 | return process;
36 | }
37 |
38 | /**
39 | * By default input stream will be retrieved from Process object supplied by setProcess method
40 | * But you can explicitly set input stream which will have bigger priority than process's one
41 | * @param inputStream
42 | */
43 | public void setResponseInputStream(InputStream inputStream) {
44 | this.inputStream = inputStream;
45 | }
46 |
47 | /**
48 | * @return input stream provided by setResponseInputStream or input stream retrieved from Process
49 | */
50 | public InputStream getResponseInputStream() {
51 | return inputStream == null
52 | ? (process == null ? null : process.getInputStream())
53 | : inputStream;
54 | }
55 |
56 | public InputStream getResponseErrorStream() {
57 | return process.getErrorStream();
58 | }
59 |
60 | public OutputStream getResponseOutputStream() {
61 | return process.getOutputStream();
62 | }
63 |
64 | public Exception getException() {
65 | return exception;
66 | }
67 |
68 | public void setException(Exception exception) {
69 | this.exception = exception;
70 | }
71 |
72 | @Override
73 | public boolean equals(Object o) {
74 | if (this == o) return true;
75 | if (o == null || getClass() != o.getClass()) return false;
76 | TerminalCall that = (TerminalCall) o;
77 | return Objects.equals(getRequest(), that.getRequest()) &&
78 | Objects.equals(getException(), that.getException()) &&
79 | Objects.equals(getProcess(), that.getProcess());
80 | }
81 |
82 | @Override
83 | public int hashCode() {
84 | return Objects.hash(getRequest(), getException(), getProcess());
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/repository/BlacklistRepository.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.repository;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.content.pm.ApplicationInfo;
6 | import android.content.pm.PackageManager;
7 | import android.os.Build;
8 |
9 | import androidx.lifecycle.LiveData;
10 | import androidx.lifecycle.MutableLiveData;
11 | import androidx.lifecycle.Observer;
12 | import androidx.lifecycle.Transformations;
13 | import androidx.room.Database;
14 |
15 | import com.simonk.projects.taskmanager.database.DatabaseManager;
16 | import com.simonk.projects.taskmanager.database.entity.BlacklistEntity;
17 | import com.simonk.projects.taskmanager.entity.AppInfo;
18 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | /**
24 | * Repository for blacklist. Provides base functions for work with blacklist
25 | * Retrieves information from different sources: system's PackageManager and local database
26 | */
27 | public class BlacklistRepository {
28 |
29 | /**
30 | * Returns all installed on device apps. Uses system's PackageManager
31 | *
32 | * All returned apps supposed to be not in black list
33 | *
34 | * Note: not returns current application in list
35 | *
36 | * TODO: perform logic in separated thread with LiveData
37 | *
38 | * @param context
39 | * @param notSystem if true all system applications will be ignored
40 | * @return list of applications
41 | */
42 | public List getAllInstalledApplicationsInfo(Context context, boolean notSystem) {
43 | PackageManager packageManager = context.getPackageManager();
44 | List installedApplications = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
45 |
46 | List appInfoList = new ArrayList<>();
47 | for (ApplicationInfo info : installedApplications) {
48 | AppInfo appInfo = new AppInfo();
49 | appInfo.setText(packageManager.getApplicationLabel(info).toString());
50 | appInfo.setImage(packageManager.getApplicationIcon(info));
51 | appInfo.setPpackage(info.processName);
52 | appInfo.setEnabled(info.enabled);
53 | appInfo.setInBlacklist(false);
54 | boolean isSystem = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
55 | if (isSystem && notSystem) {
56 | continue;
57 | }
58 | appInfo.setIsSystem(isSystem);
59 |
60 | if (appInfo.getPpackage().contains(context.getPackageName())) {
61 | continue;
62 | }
63 |
64 | appInfoList.add(appInfo);
65 | }
66 |
67 | return appInfoList;
68 | }
69 |
70 | /**
71 | * Returns LiveData of blacklist retrieved from database
72 | * @param context
73 | * @return
74 | */
75 | public LiveData> getAllBlacklistApplicationInfo(Context context) {
76 | LiveData> blacklistEntitiesData = DatabaseManager.loadBlacklistEntities(context);
77 | return Transformations.map(blacklistEntitiesData, blacklistEntities -> {
78 | List appInfoList = new ArrayList<>();
79 | PackageManager packageManager = context.getPackageManager();
80 | for (BlacklistEntity entity : blacklistEntities) {
81 | ApplicationInfo info = null;
82 | try {
83 | info = packageManager.getApplicationInfo(entity.apppackage, PackageManager.GET_META_DATA);
84 | } catch (PackageManager.NameNotFoundException exception) {
85 | continue;
86 | }
87 | AppInfo appInfo = new AppInfo();
88 | appInfo.setText(packageManager.getApplicationLabel(info).toString());
89 | appInfo.setImage(packageManager.getApplicationIcon(info));
90 | appInfo.setPpackage(info.processName);
91 | appInfo.setEnabled(info.enabled);
92 | appInfo.setIsSystem((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
93 | appInfo.setInBlacklist(true);
94 | appInfo.setLastOpenDate(entity.lastOpenDate);
95 |
96 | appInfoList.add(appInfo);
97 | }
98 |
99 | return appInfoList;
100 | });
101 | }
102 |
103 | /**
104 | * Insert an application to blacklist
105 | * @param context
106 | * @param appInfo
107 | */
108 | public void insertAppInBlacklist(Context context, AppInfo appInfo) {
109 | BlacklistEntity blacklistEntity = new BlacklistEntity();
110 | blacklistEntity.apppackage = appInfo.getPpackage();
111 | blacklistEntity.lastOpenDate = appInfo.getLastOpenDate();
112 | DatabaseManager.insertBlacklistEntity(context, blacklistEntity);
113 | }
114 |
115 | /**
116 | * Delete an application from blacklsit
117 | * @param context
118 | * @param appInfo
119 | */
120 | public void deleteAppFromBlackList(Context context, AppInfo appInfo) {
121 | BlacklistEntity blacklistEntity = new BlacklistEntity();
122 | blacklistEntity.apppackage = appInfo.getPpackage();
123 | blacklistEntity.lastOpenDate = appInfo.getLastOpenDate();
124 | DatabaseManager.deleteBlacklistEntityByPackage(context, blacklistEntity.apppackage);
125 | }
126 |
127 | /**
128 | * Change last open date of an application in blacklsit
129 | * @param context
130 | * @param appInfo
131 | * @param openDate
132 | */
133 | public void setBlacklistAppOpenDate(Context context, AppInfo appInfo, long openDate) {
134 | DatabaseManager.updateBlacklistEntityOpenDate(context, appInfo.getPpackage(), openDate);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/repository/ProcessRepository.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.repository;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.content.pm.ApplicationInfo;
6 | import android.content.pm.PackageManager;
7 |
8 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Repository for process management. Works mainly with system's API
15 | *
16 | */
17 | public class ProcessRepository {
18 |
19 | /**
20 | * @param context
21 | * @return all currently running processes
22 | */
23 | public List getAllProcessInfo(Context context) {
24 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
25 | List runningAppProcessInfos
26 | = activityManager.getRunningAppProcesses();
27 |
28 | PackageManager packageManager = context.getPackageManager();
29 |
30 | List processInfoList = new ArrayList<>();
31 | for (ActivityManager.RunningAppProcessInfo info : runningAppProcessInfos) {
32 | ProcessInfo processInfo = new ProcessInfo();
33 | try {
34 | ApplicationInfo applicationInfo =
35 | packageManager.getApplicationInfo(info.processName, 0);
36 | processInfo.setText(packageManager.getApplicationLabel(applicationInfo).toString());
37 | processInfo.setImage(packageManager.getApplicationIcon(applicationInfo));
38 | processInfo.setPpackage(info.processName);
39 | processInfo.setPriority(info.importance);
40 | processInfo.setStatus(applicationInfo.enabled);
41 | processInfo.setMinSdk(applicationInfo.targetSdkVersion);
42 | processInfo.setUid(applicationInfo.uid);
43 | processInfo.setDescription(applicationInfo.descriptionRes != 0
44 | ? context.getResources().getString(applicationInfo.descriptionRes)
45 | : "");
46 | processInfo.setPid(info.pid);
47 | } catch (PackageManager.NameNotFoundException e) {
48 | continue;
49 | }
50 | processInfoList.add(processInfo);
51 | }
52 |
53 | return processInfoList;
54 | }
55 |
56 | /**
57 | * @param context
58 | * @param packageName
59 | * @return return process info for corresponding package
60 | * or null if there is not application or application is not running
61 | */
62 | public ProcessInfo getRunningProcessInfoForPackage(Context context, String packageName) {
63 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
64 | List runningAppProcessInfos
65 | = activityManager.getRunningAppProcesses();
66 |
67 | PackageManager packageManager = context.getPackageManager();
68 |
69 | for (ActivityManager.RunningAppProcessInfo info : runningAppProcessInfos) {
70 | if (info.processName.equals(packageName)) {
71 | ProcessInfo processInfo = new ProcessInfo();
72 | try {
73 | ApplicationInfo applicationInfo =
74 | packageManager.getApplicationInfo(info.processName, 0);
75 | processInfo.setText(packageManager.getApplicationLabel(applicationInfo).toString());
76 | processInfo.setImage(packageManager.getApplicationIcon(applicationInfo));
77 | processInfo.setPpackage(info.processName);
78 | processInfo.setPriority(info.importance);
79 | processInfo.setStatus(applicationInfo.enabled);
80 | processInfo.setMinSdk(applicationInfo.targetSdkVersion);
81 | processInfo.setUid(applicationInfo.uid);
82 | processInfo.setDescription(applicationInfo.descriptionRes != 0
83 | ? context.getResources().getString(applicationInfo.descriptionRes)
84 | : "");
85 | processInfo.setPid(info.pid);
86 | } catch (PackageManager.NameNotFoundException e) {
87 | return null;
88 | }
89 | return processInfo;
90 | }
91 | }
92 |
93 | return null;
94 | }
95 |
96 | /**
97 | * Kill process.
98 | *
99 | * Important: there is not guarantee that process will be destroyed actually.
100 | * It only sends kill signals
101 | *
102 | * Since there is not root permissions on device foreground process will never be killed
103 | *
104 | * @param context
105 | * @param processInfo
106 | */
107 | public void killProcess(Context context, ProcessInfo processInfo) {
108 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
109 |
110 | android.os.Process.killProcess(processInfo.getPid());
111 | android.os.Process.sendSignal(processInfo.getPid(), android.os.Process.SIGNAL_KILL);
112 | activityManager.killBackgroundProcesses(processInfo.getPpackage());
113 | }
114 |
115 | /**
116 | * Tries to change process priority
117 | *
118 | * Important: there is not guarantee that process will accept new priority.
119 | *
120 | * @param context
121 | * @param processInfo
122 | * @param priority
123 | */
124 | public void changeProcessPriority(Context context, ProcessInfo processInfo, int priority) {
125 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
126 | List runningAppProcessInfos
127 | = activityManager.getRunningAppProcesses();
128 | for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : runningAppProcessInfos) {
129 | if (runningAppProcessInfo.processName.equals(processInfo.getPpackage())) {
130 | runningAppProcessInfo.importance = priority;
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * Kill processes as much as possible
137 | *
138 | * Important: there is not guarantee that process will be destroyed actually.
139 | * It only sends kill signals
140 | *
141 | * Since there is not root permissions on device foreground process will never be killed
142 | *
143 | * @param context
144 | */
145 | public void killAllProcesses(Context context) {
146 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
147 | List runningAppProcessInfos
148 | = activityManager.getRunningAppProcesses();
149 |
150 | for (ActivityManager.RunningAppProcessInfo info : runningAppProcessInfos) {
151 | if (!info.processName.contains("simonk")) {
152 | android.os.Process.killProcess(info.pid);
153 | android.os.Process.sendSignal(info.pid, android.os.Process.SIGNAL_KILL);
154 | activityManager.killBackgroundProcesses(info.processName);
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/services/blacklist/BlacklistJobService.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.services.blacklist;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationManager;
5 | import android.app.PendingIntent;
6 | import android.app.job.JobParameters;
7 | import android.app.job.JobService;
8 | import android.content.Intent;
9 | import android.util.Log;
10 |
11 | import androidx.core.app.NotificationCompat;
12 | import androidx.core.app.NotificationManagerCompat;
13 | import androidx.lifecycle.LiveData;
14 | import androidx.lifecycle.Observer;
15 |
16 | import com.simonk.projects.taskmanager.R;
17 | import com.simonk.projects.taskmanager.entity.AppInfo;
18 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
19 | import com.simonk.projects.taskmanager.repository.BlacklistRepository;
20 | import com.simonk.projects.taskmanager.repository.ProcessRepository;
21 | import com.simonk.projects.taskmanager.ui.MainActivity;
22 |
23 | import java.util.List;
24 |
25 | /**
26 | * Background service for blacklist. Tries to remove users process if the process is in blacklist
27 | * Send notification to user if blocked process was found running
28 | */
29 | public class BlacklistJobService extends JobService {
30 |
31 | @Override
32 | public boolean onStartJob(JobParameters params) {
33 | Log.d("TEST", "Start job");
34 | LiveData> appInfoListData =
35 | new BlacklistRepository().getAllBlacklistApplicationInfo(getApplicationContext());
36 | appInfoListData.observeForever(new Observer>() {
37 | @Override
38 | public void onChanged(List appInfos) {
39 | Log.d("TEST", "New data. Length: " + appInfos.size());
40 | for (AppInfo appInfo : appInfos) {
41 | ProcessInfo processInfo =
42 | new ProcessRepository().getRunningProcessInfoForPackage(getApplicationContext(), appInfo.getPpackage());
43 | if (processInfo != null) {
44 | Log.d("TEST", "Send kill process: " + processInfo.getPpackage());
45 | new ProcessRepository().killProcess(getApplicationContext(), processInfo);
46 | notifyUser(appInfo);
47 | new BlacklistRepository().setBlacklistAppOpenDate(getApplicationContext(), appInfo, System.currentTimeMillis());
48 | }
49 | }
50 | appInfoListData.removeObserver(this);
51 | }
52 | });
53 | return false;
54 | }
55 |
56 | @Override
57 | public boolean onStopJob(JobParameters params) {
58 | return true;
59 | }
60 |
61 | /**
62 | * Notify user about running blocked app. Priority is set to high and sound is on
63 | * so notification will be shown on top of all other apps
64 | * @param appInfo
65 | */
66 | private void notifyUser(AppInfo appInfo) {
67 | Intent intent = new Intent(this, MainActivity.class);
68 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
69 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
70 |
71 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
72 | builder.setSmallIcon(R.drawable.ic_launcher_background)
73 | .setColor(getResources().getColor(R.color.colorPrimary))
74 | .setContentTitle(getApplicationContext().getString(R.string.notify_title))
75 | .setContentIntent(pendingIntent)
76 | .setAutoCancel(true)
77 | .setContentText(String.format(getApplicationContext().getString(R.string.notify_content), appInfo.getText()))
78 | .setDefaults(Notification.DEFAULT_ALL)
79 | .setPriority(NotificationCompat.PRIORITY_MAX);
80 |
81 | NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
82 | notificationManager.notify(1000, builder.build());
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/StringTerminalListener.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal;
2 |
3 | /**
4 | * Util class handles byte array to string transformations
5 | */
6 | public abstract class StringTerminalListener implements TerminalListener {
7 |
8 | public abstract void onInput(String input);
9 |
10 | public abstract void onError(String input);
11 |
12 | public abstract String onOutputString();
13 |
14 | @Override
15 | public void onInput(byte[] input) {
16 | onInput(new String(input));
17 | }
18 |
19 | @Override
20 | public void onError(byte[] error) {
21 | onError(new String(error));
22 | }
23 |
24 | @Override
25 | public byte[] onOutput() {
26 | return onOutputString().getBytes();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/Terminal.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal;
2 |
3 | import com.google.common.base.Charsets;
4 | import com.google.common.io.CharStreams;
5 | import com.simonk.projects.taskmanager.entity.TerminalCall;
6 | import com.simonk.projects.taskmanager.terminal.handlers.BlankCommandHandler;
7 | import com.simonk.projects.taskmanager.terminal.handlers.CdCommandHandler;
8 | import com.simonk.projects.taskmanager.terminal.handlers.EchoCommandHandler;
9 | import com.simonk.projects.taskmanager.terminal.handlers.ExportCommandHandler;
10 | import com.simonk.projects.taskmanager.terminal.handlers.PwdCommandHandler;
11 | import com.simonk.projects.taskmanager.terminal.handlers.RequestHandler;
12 |
13 | import java.io.File;
14 | import java.io.InputStreamReader;
15 | import java.util.ArrayList;
16 | import java.util.HashMap;
17 | import java.util.List;
18 | import java.util.Map;
19 | import java.util.concurrent.BlockingQueue;
20 | import java.util.concurrent.Executor;
21 | import java.util.concurrent.LinkedBlockingQueue;
22 | import java.util.concurrent.ThreadFactory;
23 | import java.util.concurrent.ThreadPoolExecutor;
24 | import java.util.concurrent.TimeUnit;
25 | import java.util.concurrent.atomic.AtomicInteger;
26 |
27 | /**
28 | * Class represents terminal of Android shell. Let you make requests and change system settings
29 | *
30 | * Note: all request is synchronized
31 | */
32 | public class Terminal {
33 |
34 | private File mCurrentDirectory;
35 |
36 | private List mRequestHandlers;
37 |
38 | private Map mEnvironmentVars;
39 |
40 | public Terminal() {
41 | mEnvironmentVars = new HashMap<>(System.getenv());
42 | mCurrentDirectory = new File("/");
43 |
44 | mRequestHandlers = new ArrayList<>();
45 | registerRequestHandlers();
46 | }
47 |
48 | private void registerRequestHandlers() {
49 | mRequestHandlers.add(new BlankCommandHandler());
50 | mRequestHandlers.add(new CdCommandHandler());
51 | mRequestHandlers.add(new PwdCommandHandler());
52 | mRequestHandlers.add(new EchoCommandHandler());
53 | mRequestHandlers.add(new ExportCommandHandler());
54 | }
55 |
56 | /**
57 | * Perform request to terminal. Uses only request filed in provided terminalRequest
58 | * @param terminalRequest
59 | * @return complete call with request and corresponding response
60 | */
61 | public TerminalCall makeNewRequest(TerminalCall terminalRequest) {
62 | String request = terminalRequest.getRequest().trim();
63 |
64 | for (RequestHandler requestHandler : mRequestHandlers) {
65 | if (requestHandler.isHandleRequest(terminalRequest)) {
66 | requestHandler.handleRequest(terminalRequest, this);
67 | return terminalRequest;
68 | }
69 | }
70 |
71 | try {
72 | Process process = Runtime.getRuntime().exec(
73 | request,
74 | convertEnvironmentVarsToString(mEnvironmentVars),
75 | mCurrentDirectory
76 | );
77 | terminalRequest.setProcess(process);
78 | } catch (Exception e) {
79 | terminalRequest.setException(new RuntimeException(
80 | String.format("Command '%s' not found", request),
81 | e
82 | ));
83 | }
84 | return terminalRequest;
85 | }
86 |
87 | /**
88 | * Sets current working directory to terminal. Works like cd command
89 | * @param currentDirectory
90 | */
91 | public void setCurrentDirectory(File currentDirectory) {
92 | mCurrentDirectory = currentDirectory;
93 | }
94 |
95 | public File getCurrentDirectory() {
96 | return mCurrentDirectory;
97 | }
98 |
99 | /**
100 | * Sets environment variables to terminal. Works like export command
101 | * @param name
102 | * @param data
103 | */
104 | public void addEnvironmentVar(String name, String data) {
105 | mEnvironmentVars.put(name, data);
106 | }
107 |
108 | public String getEnvironmentVar(String name) {
109 | return mEnvironmentVars.get(name);
110 | }
111 |
112 | private String[] convertEnvironmentVarsToString(Map environmentVarsMap) {
113 | if (environmentVarsMap.size() == 0) {
114 | return null;
115 | }
116 |
117 | List output = new ArrayList<>();
118 | for (String key : environmentVarsMap.keySet()) {
119 | output.add(String.format("%s=%s", key, environmentVarsMap.get(key)));
120 | }
121 | return output.toArray(new String[0]);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/TerminalListener.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal;
2 |
3 | /**
4 | * Listener for terminal requests. Use to get response and supply additional output to terminal
5 | */
6 | public interface TerminalListener {
7 |
8 | /**
9 | * New part of input data received
10 | * @param input
11 | */
12 | void onInput(byte[] input);
13 |
14 | /**
15 | * Start reading input stream from response
16 | */
17 | void onInputStarted();
18 |
19 |
20 | /**
21 | * Finish reading input stream from response
22 | * @param exception
23 | */
24 | void onInputFinished(Exception exception);
25 |
26 |
27 | /**
28 | * New part of error data received
29 | * @param error
30 | */
31 | void onError(byte[] error);
32 |
33 | /**
34 | * Start reading error stream from response
35 | */
36 | void onErrorStarted();
37 |
38 |
39 | /**
40 | * Finish reading error stream form response
41 | * @param exception
42 | */
43 | void onErrorFinished(Exception exception);
44 |
45 | /**
46 | * Called when terminal wait some data to send in command's process
47 | * @return
48 | */
49 | byte[] onOutput();
50 |
51 | /**
52 | * Request finished
53 | * @param exception
54 | */
55 | void onFinished(Exception exception);
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/TerminalService.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal;
2 |
3 | import android.os.Handler;
4 | import android.os.HandlerThread;
5 | import android.os.Looper;
6 | import android.os.Message;
7 | import android.util.Log;
8 |
9 | import androidx.annotation.MainThread;
10 | import androidx.annotation.WorkerThread;
11 |
12 | import com.simonk.projects.taskmanager.entity.TerminalCall;
13 | import com.simonk.projects.taskmanager.terminal.interceptors.TerminalRequestInterceptor;
14 | import com.simonk.projects.taskmanager.terminal.interceptors.TopCommandInterceptor;
15 | import com.simonk.projects.taskmanager.util.ProcessCompat;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | /**
23 | * Performs async requests to terminal. Retrieve data from terminal and dispatch it to TerminalListener
24 | */
25 | public class TerminalService {
26 |
27 | private static final int BUFFER_SIZE = 4096;
28 |
29 | private Terminal mTerminal;
30 | private TerminalThread mTerminalThread;
31 | private TerminalHandler mTerminalHandler;
32 | private MainThreadHandler mMainThreadHandler;
33 |
34 | private TerminalListener mTerminalListener;
35 |
36 | private static final int REQUEST_NEW = 1;
37 | private static final int REQUEST_INPUT = 2;
38 | private static final int REQUEST_INPUT_FINISH = 3;
39 | private static final int REQUEST_INPUT_STARTED = 4;
40 | private static final int REQUEST_ERROR = 5;
41 | private static final int REQUEST_ERROR_FINISH = 6;
42 | private static final int REQUEST_ERROR_STARTED = 7;
43 | private static final int REQUEST_FINISH = 8;
44 | private static final int REQUEST_OUTPUT = 9;
45 |
46 | private static byte[] BUFFER = new byte[BUFFER_SIZE];
47 |
48 | private List mInterceptors;
49 |
50 | private boolean mTerminated = true;
51 |
52 | private TerminalCall mCurrentTerminalCall;
53 |
54 | public TerminalService() {
55 | mTerminal = new Terminal();
56 | mTerminalThread = new TerminalThread();
57 | mTerminalThread.start();
58 | mTerminalHandler = new TerminalHandler(mTerminalThread.getLooper(), this);
59 | mMainThreadHandler = new MainThreadHandler(Looper.getMainLooper(), this);
60 |
61 | mInterceptors = new ArrayList<>();
62 | mInterceptors.add(new TopCommandInterceptor());
63 | }
64 |
65 | /**
66 | * Make async terminal request
67 | * @param terminalCall
68 | * @param terminalListener
69 | */
70 | @MainThread
71 | public void makeTerminalRequest(TerminalCall terminalCall, TerminalListener terminalListener) {
72 | mTerminalListener = terminalListener;
73 | mTerminalHandler.sendMessage(mTerminalHandler.obtainMessage(REQUEST_NEW, terminalCall));
74 | }
75 |
76 | @MainThread
77 | public void sendOutputString(String output) {
78 |
79 | }
80 |
81 | /**
82 | * Stops currently running terminal request
83 | */
84 | @MainThread public void stopTerminalRequest() {
85 | mTerminated = true;
86 | }
87 |
88 | @WorkerThread
89 | private void performTerminalRequest(TerminalCall terminalCall) {
90 | clearBuffer();
91 | mTerminated = false;
92 |
93 | TerminalCall response = mTerminal.makeNewRequest(terminalCall);
94 | mCurrentTerminalCall = response;
95 |
96 | if (response.getException() != null) {
97 | dispatchSendFinish(response.getException());
98 | return;
99 | }
100 |
101 | TerminalRequestInterceptor requestInterceptor = null;
102 | for (TerminalRequestInterceptor interceptor : mInterceptors) {
103 | if (interceptor.willIntercept(terminalCall)) {
104 | requestInterceptor = interceptor;
105 | break;
106 | }
107 | }
108 |
109 | if (response.getProcess() != null) {
110 | performProcessWork(response, requestInterceptor);
111 | } else {
112 | performInputStreamWork(response, requestInterceptor);
113 | }
114 |
115 | stop();
116 | }
117 |
118 | private void performInputStreamWork(TerminalCall response, TerminalRequestInterceptor requestInterceptor) {
119 | InputStream contentInputStream = response.getResponseInputStream();
120 | if (contentInputStream == null) {
121 | return;
122 | }
123 | try {
124 | int length = 0;
125 | while (length != -1) {
126 | if (mTerminated) {
127 | stop();
128 | return;
129 | }
130 | length = contentInputStream.read(BUFFER);
131 | if (requestInterceptor != null) {
132 | requestInterceptor.interceptInput(this, response, BUFFER);
133 | } else {
134 | dispatchSendInput(BUFFER);
135 | }
136 | }
137 | dispatchSendFinishInput(null);
138 | } catch (IOException exception) {
139 | dispatchSendFinishInput(exception);
140 | }
141 | }
142 |
143 | private void performProcessWork(TerminalCall response, TerminalRequestInterceptor requestInterceptor) {
144 | Process process = response.getProcess();
145 | performInputStreamWork(response, requestInterceptor);
146 | if (mTerminated) {
147 | process.destroy();
148 | return;
149 | }
150 |
151 | InputStream errorInputStream = response.getResponseErrorStream();
152 | try {
153 | int length = 0;
154 | while (length != -1) {
155 | if (mTerminated) {
156 | stop();
157 | return;
158 | }
159 | length = errorInputStream.read(BUFFER);
160 | dispatchSendInput(BUFFER);
161 | }
162 | dispatchSendFinishInput(null);
163 | } catch (IOException exception) {
164 | dispatchSendFinishInput(exception);
165 | }
166 | }
167 |
168 | public void dispatchSendInput(byte[] buffer) {
169 | mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(REQUEST_INPUT, buffer));
170 | }
171 |
172 | public void dispatchSendFinishInput(Exception exception) {
173 | mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(REQUEST_INPUT_FINISH, exception));
174 | }
175 |
176 | private void dispatchSendError(byte[] buffer) {
177 | mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(REQUEST_ERROR, buffer));
178 | }
179 |
180 | public void dispatchSendFinishError(Exception exception) {
181 | mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(REQUEST_ERROR_FINISH, exception));
182 | }
183 |
184 | public void dispatchSendFinish(Exception exception) {
185 | mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(REQUEST_FINISH, exception));
186 | }
187 |
188 | @WorkerThread
189 | private void performOutput(String output) {
190 | if (mCurrentTerminalCall != null) {
191 | try {
192 | mCurrentTerminalCall.getResponseOutputStream().write(output.getBytes());
193 | } catch (IOException ignore) {
194 | }
195 | }
196 | }
197 |
198 | @WorkerThread
199 | private void stop() {
200 | mTerminated = true;
201 | dispatchSendFinish(null);
202 | clearBuffer();
203 | }
204 |
205 | @MainThread
206 | private void dispatchInput(byte[] input) {
207 | if (mTerminalListener != null) {
208 | mTerminalListener.onInput(input);
209 | }
210 | }
211 |
212 | @MainThread
213 | private void dispatchInputStarted() {
214 | if (mTerminalListener != null) {
215 | mTerminalListener.onInputStarted();
216 | }
217 | }
218 |
219 | @MainThread
220 | private void dispatchErrorFinished(Exception exception) {
221 | if (mTerminalListener != null) {
222 | mTerminalListener.onErrorFinished(exception);
223 | }
224 | }
225 |
226 | @MainThread
227 | private void dispatchError(byte[] input) {
228 | if (mTerminalListener != null) {
229 | mTerminalListener.onError(input);
230 | }
231 | }
232 |
233 | @MainThread
234 | private void dispatchErrorStarted() {
235 | if (mTerminalListener != null) {
236 | mTerminalListener.onErrorStarted();
237 | }
238 | }
239 |
240 | @MainThread
241 | private void dispatchInputFinished(Exception exception) {
242 | if (mTerminalListener != null) {
243 | mTerminalListener.onInputFinished(exception);
244 | }
245 | }
246 |
247 | @MainThread
248 | private void dispatchFinished(Exception exception) {
249 | if (mTerminalListener != null) {
250 | mTerminalListener.onFinished(exception);
251 | }
252 | }
253 |
254 | private void clearBuffer() {
255 | BUFFER = new byte[BUFFER_SIZE];
256 | }
257 |
258 | /**
259 | * Handler to send information in background service thread
260 | */
261 | private static class TerminalHandler extends Handler {
262 | private final TerminalService service;
263 |
264 | TerminalHandler(Looper looper, TerminalService dispatcher) {
265 | super(looper);
266 | this.service = dispatcher;
267 | }
268 |
269 | @Override
270 | public void handleMessage(final Message msg) {
271 | switch (msg.what) {
272 | case REQUEST_NEW:
273 | service.performTerminalRequest((TerminalCall) msg.obj);
274 | break;
275 | case REQUEST_OUTPUT:
276 | service.performOutput((String) msg.obj);
277 | break;
278 | }
279 | }
280 | }
281 |
282 | /**
283 | * Handler to send information in main ui thread
284 | */
285 | private static class MainThreadHandler extends Handler {
286 | private final TerminalService service;
287 |
288 | MainThreadHandler(Looper looper, TerminalService dispatcher) {
289 | super(looper);
290 | this.service = dispatcher;
291 | }
292 |
293 | @Override
294 | public void handleMessage(final Message msg) {
295 | switch (msg.what) {
296 | case REQUEST_INPUT:
297 | service.dispatchInput((byte[]) msg.obj);
298 | break;
299 | case REQUEST_INPUT_FINISH:
300 | service.dispatchInputFinished((Exception) msg.obj);
301 | break;
302 | case REQUEST_INPUT_STARTED:
303 | service.dispatchInputStarted();
304 | break;
305 | case REQUEST_FINISH:
306 | service.dispatchFinished((Exception) msg.obj);
307 | break;
308 | case REQUEST_ERROR:
309 | service.dispatchError((byte[]) msg.obj);
310 | break;
311 | case REQUEST_ERROR_FINISH:
312 | service.dispatchErrorFinished((Exception) msg.obj);
313 | break;
314 | case REQUEST_ERROR_STARTED:
315 | service.dispatchErrorStarted();
316 | break;
317 | }
318 | }
319 | }
320 |
321 | private static class TerminalThread extends HandlerThread {
322 | public TerminalThread() {
323 | super("TaskManager-Terminal");
324 | }
325 | }
326 | }
327 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/handlers/BlankCommandHandler.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.handlers;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 |
6 | public class BlankCommandHandler extends RequestHandler {
7 |
8 | @Override
9 | public boolean isHandleRequest(TerminalCall call) {
10 | return call.getRequest().isEmpty();
11 | }
12 |
13 | @Override
14 | public void handleRequest(TerminalCall call, Terminal terminal) {
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/handlers/CdCommandHandler.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.handlers;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 |
6 | import java.io.File;
7 |
8 | public class CdCommandHandler extends RequestHandler {
9 |
10 | private static final String COMMAND = "cd";
11 |
12 | @Override
13 | public boolean isHandleRequest(TerminalCall call) {
14 | return requestMatch(call.getRequest(), COMMAND);
15 | }
16 |
17 | @Override
18 | public void handleRequest(TerminalCall call, Terminal terminal) {
19 | String request = call.getRequest();
20 | if (request.length() == 2) {
21 | return;
22 | }
23 | String newPath = request.substring(3); // remove 'cd ' part from request
24 |
25 | if (newPath.isEmpty()) {
26 | return;
27 | }
28 |
29 | if (newPath.equals("/")) {
30 | terminal.setCurrentDirectory(new File("/"));
31 | return;
32 | }
33 |
34 | String[] subPaths = newPath.split("/");
35 |
36 | File currentDirectory;
37 | if (subPaths[0].isEmpty()) {
38 | currentDirectory = new File("/");
39 | } else {
40 | currentDirectory = terminal.getCurrentDirectory();
41 | }
42 | for (String subPath : subPaths) {
43 | currentDirectory = handleSubpath(currentDirectory, subPath);
44 | }
45 | terminal.setCurrentDirectory(currentDirectory);
46 | }
47 |
48 | private File handleSubpath(File currentDirectory, String subPath) {
49 | if (subPath.equals(".")) {
50 | return currentDirectory;
51 | }
52 |
53 | if (subPath.equals("..")) {
54 | File parent = currentDirectory.getParentFile();
55 | if (parent == null) {
56 | return currentDirectory;
57 | }
58 | return parent;
59 | }
60 |
61 | return new File(currentDirectory, subPath);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/handlers/EchoCommandHandler.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.handlers;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 |
6 | import java.io.ByteArrayInputStream;
7 |
8 | public class EchoCommandHandler extends RequestHandler {
9 |
10 | private static final String COMMAND = "echo";
11 |
12 | @Override
13 | public boolean isHandleRequest(TerminalCall call) {
14 | return requestMatch(call.getRequest(), COMMAND);
15 | }
16 |
17 | @Override
18 | public void handleRequest(TerminalCall call, Terminal terminal) {
19 | String request = call.getRequest();
20 | String vars = request.substring(COMMAND.length() + 1);
21 | String[] subVars = vars.split(" ");
22 |
23 | StringBuilder responseBuilder = new StringBuilder();
24 | for (String subVar : subVars) {
25 | int dollarIndex = subVar.indexOf('$');
26 | if (dollarIndex != -1) {
27 | subVar = subVar.substring(dollarIndex + 1);
28 | }
29 | String value = terminal.getEnvironmentVar(subVar);
30 | if (value != null) {
31 | responseBuilder.append(value);
32 | }
33 | }
34 |
35 | call.setResponseInputStream(new ByteArrayInputStream(responseBuilder.toString().getBytes()));
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/handlers/ExportCommandHandler.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.handlers;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 |
6 | public class ExportCommandHandler extends RequestHandler {
7 |
8 | private static final String COMMAND = "export";
9 |
10 | @Override
11 | public boolean isHandleRequest(TerminalCall call) {
12 | return requestMatch(call.getRequest(), COMMAND);
13 | }
14 |
15 | @Override
16 | public void handleRequest(TerminalCall call, Terminal terminal) {
17 | String request = call.getRequest();
18 | String vars = request.substring(COMMAND.length() + 1);
19 | String[] subVars = vars.split(" ");
20 |
21 | for (String var : subVars) {
22 | String[] varAssignment = var.split("=");
23 | if (varAssignment.length != 2) {
24 | continue;
25 | }
26 |
27 | String name = varAssignment[0];
28 | String value = varAssignment[1];
29 | terminal.addEnvironmentVar(name, value);
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/handlers/PwdCommandHandler.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.handlers;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 |
6 | import java.io.ByteArrayInputStream;
7 |
8 |
9 | public class PwdCommandHandler extends RequestHandler {
10 |
11 | private static final String COMMAND = "pwd";
12 |
13 | @Override
14 | public boolean isHandleRequest(TerminalCall call) {
15 | return requestMatch(call.getRequest(), COMMAND);
16 | }
17 |
18 | @Override
19 | public void handleRequest(TerminalCall call, Terminal terminal) {
20 | call.setResponseInputStream(new ByteArrayInputStream(terminal.getCurrentDirectory().getAbsolutePath().getBytes()));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/handlers/RequestHandler.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.handlers;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 |
6 | /**
7 | * Interface lets you to handle a terminal request by yourself.
8 | * Don't forget to register it in Terminal
9 | */
10 | public abstract class RequestHandler {
11 |
12 | /**
13 | * Checks if call should be handled by this handler
14 | * @param call
15 | * @return
16 | */
17 | public abstract boolean isHandleRequest(TerminalCall call);
18 |
19 | /**
20 | * Handle request. Will be called only if isHandleRequest returned true
21 | * @param call
22 | * @param terminal
23 | */
24 | public abstract void handleRequest(TerminalCall call, Terminal terminal);
25 |
26 | /**
27 | * Util method. Checks is there is command in request
28 | * @param requestString
29 | * @param command
30 | * @return
31 | */
32 | boolean requestMatch(String requestString, String command) {
33 | String[] words = requestString.split(" ");
34 | return words.length > 0 && words[0].equals(command);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/interceptors/TerminalRequestInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.interceptors;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 | import com.simonk.projects.taskmanager.terminal.TerminalService;
6 |
7 | /**
8 | * Interface lets you intercept terminal response and work with response data in other way
9 | */
10 | public interface TerminalRequestInterceptor {
11 |
12 | boolean willIntercept(TerminalCall terminalCall);
13 |
14 | void interceptInput(TerminalService terminalService, TerminalCall terminalCall, byte[] data);
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/terminal/interceptors/TopCommandInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.terminal.interceptors;
2 |
3 | import com.simonk.projects.taskmanager.entity.TerminalCall;
4 | import com.simonk.projects.taskmanager.terminal.Terminal;
5 | import com.simonk.projects.taskmanager.terminal.TerminalService;
6 |
7 | /**
8 | * Interceptor read data and represents it as string.
9 | * When it faces special separator it sends whole data up to previous separator to listener
10 | */
11 | public class TopCommandInterceptor implements TerminalRequestInterceptor {
12 |
13 | private static final String COMMAND = "top";
14 |
15 | private String mData = "";
16 |
17 | @Override
18 | public boolean willIntercept(TerminalCall terminalCall) {
19 | String[] words = terminalCall.getRequest().split(" ");
20 | return words.length > 0 && words[0].equals(COMMAND);
21 | }
22 |
23 | @Override
24 | public void interceptInput(TerminalService terminalService, TerminalCall terminalCall, byte[] data) {
25 | String stringRepr = new String(data);
26 | int deviderIndex = stringRepr.indexOf("\n\n\n");
27 | if (deviderIndex != -1) {
28 | mData += stringRepr.substring(0, deviderIndex);
29 | terminalService.dispatchSendInput(mData.getBytes());
30 | mData = stringRepr.substring(deviderIndex);
31 | } else {
32 | mData += stringRepr;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/BindingActivity.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import androidx.databinding.ViewDataBinding;
7 |
8 | /**
9 | * Simple activity to support binding
10 | */
11 | public abstract class BindingActivity extends AppCompatActivity {
12 |
13 | private ViewDataBinding mBinding;
14 |
15 | @Override
16 | public void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | mBinding = initBinding();
19 | if (mBinding == null) {
20 | throw new IllegalStateException("Binding can not be null");
21 | }
22 | setContentView(mBinding.getRoot());
23 | }
24 |
25 | /**
26 | * Child must implement this method to retrieve corresponding binding
27 | * @return
28 | */
29 | protected abstract ViewDataBinding initBinding();
30 |
31 | /**
32 | * Child can override this method to specify binding subclass it want
33 | * @return
34 | */
35 | public ViewDataBinding getBinding() {
36 | return mBinding;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.app.ActivityManager;
5 | import android.content.Context;
6 | import android.content.pm.ApplicationInfo;
7 | import android.content.pm.PackageManager;
8 | import android.graphics.Color;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.os.Bundle;
12 | import android.os.Handler;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.Button;
17 | import android.widget.FrameLayout;
18 | import android.widget.ImageView;
19 | import android.widget.TextView;
20 |
21 | import androidx.annotation.NonNull;
22 | import androidx.appcompat.widget.Toolbar;
23 | import androidx.databinding.DataBindingUtil;
24 | import androidx.databinding.ViewDataBinding;
25 | import androidx.fragment.app.Fragment;
26 | import androidx.fragment.app.FragmentManager;
27 | import androidx.fragment.app.FragmentPagerAdapter;
28 | import androidx.lifecycle.Lifecycle;
29 | import androidx.lifecycle.ViewModelProviders;
30 | import androidx.recyclerview.widget.LinearLayoutManager;
31 | import androidx.recyclerview.widget.RecyclerView;
32 | import androidx.viewpager.widget.ViewPager;
33 |
34 | import com.google.android.material.tabs.TabLayout;
35 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
36 | import com.simonk.projects.taskmanager.repository.ProcessRepository;
37 | import com.simonk.projects.taskmanager.ui.blacklist.BlacklistFragment;
38 | import com.simonk.projects.taskmanager.ui.process.ChangeDetailsDialog;
39 | import com.simonk.projects.taskmanager.ui.process.CleanedDialog;
40 | import com.simonk.projects.taskmanager.ui.process.ProcessFragment;
41 | import com.simonk.projects.taskmanager.ui.process.viewmodels.ProcessViewModel;
42 | import com.simonk.projects.taskmanager.ui.terminal.TerminalFragment;
43 | import com.simonk.projects.taskmanager.ui.util.ObjectListAdapter;
44 | import com.simonk.projects.taskmanager.R;
45 | import com.simonk.projects.taskmanager.databinding.ActivityMainBinding;
46 | import com.simonk.projects.taskmanager.util.MemoryUtils;
47 |
48 | import java.io.Serializable;
49 | import java.util.ArrayList;
50 | import java.util.List;
51 |
52 | public class MainActivity extends BindingActivity {
53 |
54 | @Override
55 | public ActivityMainBinding getBinding() {
56 | return (ActivityMainBinding) super.getBinding();
57 | }
58 |
59 | @Override
60 | public ViewDataBinding initBinding() {
61 | return DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_main, null, false);
62 | }
63 |
64 | @Override
65 | public void onCreate(Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 |
68 | ViewPager viewPager = getBinding().viewPager;
69 | viewPager.setOffscreenPageLimit(2);
70 | setupViewPager(viewPager);
71 |
72 | TabLayout tabLayout = getBinding().tabLayout;
73 | tabLayout.setupWithViewPager(viewPager);
74 | }
75 |
76 | private void setupViewPager(ViewPager viewPager) {
77 | ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), getBinding().coordinator);
78 | adapter.addFragment(ProcessFragment.newInstance(),
79 | getString(R.string.apps));
80 | adapter.addFragment(BlacklistFragment.newInstance(),
81 | getString(R.string.blacklist));
82 | adapter.addFragment(TerminalFragment.newInstance(),
83 | getString(R.string.terminal));
84 | viewPager.setAdapter(adapter);
85 | }
86 |
87 | private static class ViewPagerAdapter extends FragmentPagerAdapter {
88 | private final List mFragmentList = new ArrayList<>();
89 | private final List mFragmentTitleList = new ArrayList<>();
90 |
91 | private View mRootView;
92 |
93 | public ViewPagerAdapter(FragmentManager manager, View root) {
94 | super(manager);
95 | mRootView = root;
96 | }
97 |
98 | @Override
99 | public Fragment getItem(int position) {
100 | return mFragmentList.get(position);
101 | }
102 |
103 | @Override
104 | public int getCount() {
105 | return mFragmentList.size();
106 | }
107 |
108 | public void addFragment(Fragment fragment, String title) {
109 | mFragmentList.add(fragment);
110 | mFragmentTitleList.add(title);
111 | }
112 |
113 | @Override
114 | public CharSequence getPageTitle(int position) {
115 | return mFragmentTitleList.get(position);
116 | }
117 |
118 | @Override
119 | public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
120 | super.setPrimaryItem(container, position, object);
121 | if (object instanceof TerminalFragment) {
122 | ((TerminalFragment) object).setCurrent(true);
123 | } else {
124 | ((TerminalFragment) mFragmentList.get(2)).setCurrent(false);
125 | }
126 | if (mRootView.getBackground() instanceof ColorDrawable) {
127 | int installedColor = ((ColorDrawable) mRootView.getBackground()).getColor();
128 | int newColor = installedColor;
129 | if (position == 2) {
130 | newColor = Color.parseColor("#455A64");
131 | } else {
132 | newColor = mRootView.getResources().getColor(R.color.colorPrimary);
133 |
134 | }
135 |
136 | if (installedColor != newColor) {
137 | ValueAnimator valueAnimator = ValueAnimator.ofArgb(installedColor, newColor);
138 | valueAnimator.setDuration(300);
139 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
140 | @Override
141 | public void onAnimationUpdate(ValueAnimator animation) {
142 | mRootView.setBackground(new ColorDrawable((Integer) animation.getAnimatedValue()));
143 | }
144 | });
145 | valueAnimator.start();
146 | }
147 | }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/blacklist/BlackListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.blacklist;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.Button;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.recyclerview.widget.RecyclerView;
12 |
13 | import com.simonk.projects.taskmanager.R;
14 | import com.simonk.projects.taskmanager.entity.AppInfo;
15 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
16 | import com.simonk.projects.taskmanager.ui.process.ProcessAdapter;
17 | import com.simonk.projects.taskmanager.ui.util.ObjectListAdapter;
18 |
19 | import java.text.SimpleDateFormat;
20 | import java.util.ArrayList;
21 | import java.util.Calendar;
22 | import java.util.Collections;
23 | import java.util.List;
24 |
25 | public class BlackListAdapter extends ObjectListAdapter {
26 |
27 | private BlacklistAdapterViewHolder.OnClickListener mItemClickListener;
28 | private BlacklistAdapterViewHolder.OnBlockListener mItemBlockListener;
29 |
30 | private static final int BLACK_ITEM_TYPE = 1;
31 | private static final int ALL_ITEM_TYPE = 2;
32 | private static final int BLACK_LABEL_ITEM_TYPE = 3;
33 | private static final int ALL_LABEL_ITEM_TYPE = 4;
34 |
35 | public static class AdapterDataHolder {
36 | public AppInfo appInfo;
37 | public boolean isBlacklistLabel;
38 | public boolean isAllAppsLabel;
39 |
40 | AdapterDataHolder(AppInfo appInfo) {
41 | this.appInfo = appInfo;
42 | }
43 | }
44 |
45 | public void setAppInfoList(List items) {
46 | List adapterDataHolderList = new ArrayList<>();
47 |
48 | AdapterDataHolder blacklistLabel = new AdapterDataHolder(null);
49 | blacklistLabel.isBlacklistLabel = true;
50 | adapterDataHolderList.add(blacklistLabel);
51 |
52 | for (AppInfo appInfo : items) {
53 | if (appInfo.isInBlacklist()) {
54 | adapterDataHolderList.add(new AdapterDataHolder(appInfo));
55 | }
56 | }
57 |
58 | AdapterDataHolder allAppsLabel = new AdapterDataHolder(null);
59 | allAppsLabel.isAllAppsLabel = true;
60 | adapterDataHolderList.add(allAppsLabel);
61 |
62 | for (AppInfo appInfo : items) {
63 | if (!appInfo.isInBlacklist()) {
64 | adapterDataHolderList.add(new AdapterDataHolder(appInfo));
65 | }
66 | }
67 |
68 | setItemsList(adapterDataHolderList);
69 | }
70 |
71 | @NonNull
72 | @Override
73 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
74 | LayoutInflater inflater = LayoutInflater.from(parent.getContext());
75 | if (viewType == BLACK_ITEM_TYPE || viewType == ALL_ITEM_TYPE) {
76 | View v = inflater.inflate(R.layout.blacklist_item, parent, false);
77 | return new BlacklistAdapterViewHolder(v, mItemClickListener, mItemBlockListener);
78 | }
79 | if (viewType == BLACK_LABEL_ITEM_TYPE) {
80 | TextView label = (TextView) inflater.inflate(R.layout.blacklist_item_label, parent, false);
81 | label.setText(parent.getResources().getString(R.string.blacklist_label));
82 | return new LabelViewHolder(label);
83 | }
84 | if (viewType == ALL_LABEL_ITEM_TYPE) {
85 | TextView label = (TextView) inflater.inflate(R.layout.blacklist_item_label, parent, false);
86 | label.setText(parent.getResources().getString(R.string.all_apps_label));
87 | return new LabelViewHolder(label);
88 | }
89 | return null;
90 | }
91 |
92 | @Override
93 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
94 | if (holder.getItemViewType() == BLACK_ITEM_TYPE || holder.getItemViewType() == ALL_ITEM_TYPE) {
95 | ((BlacklistAdapterViewHolder)holder).bind(getItem(position).appInfo);
96 | }
97 | }
98 |
99 | @Override
100 | public int getItemViewType(int position) {
101 | AdapterDataHolder dataHolder = getItem(position);
102 | if (dataHolder.isAllAppsLabel) {
103 | return ALL_LABEL_ITEM_TYPE;
104 | }
105 | if (dataHolder.isBlacklistLabel) {
106 | return BLACK_LABEL_ITEM_TYPE;
107 | }
108 | if (dataHolder.appInfo.isInBlacklist()) {
109 | return BLACK_ITEM_TYPE;
110 | }
111 | return ALL_ITEM_TYPE;
112 | }
113 |
114 | public void setItemClickListener(BlacklistAdapterViewHolder.OnClickListener onClickListener) {
115 | mItemClickListener = onClickListener;
116 | }
117 |
118 | public void setItemBlockListener(BlacklistAdapterViewHolder.OnBlockListener onBlockListener) {
119 | mItemBlockListener = onBlockListener;
120 | }
121 |
122 | public static class BlacklistAdapterViewHolder extends RecyclerView.ViewHolder {
123 |
124 | private TextView mName;
125 | private ImageView mImage;
126 | private TextView mPackage;
127 | private Button mActionButton;
128 | private TextView mLastOpenDate;
129 |
130 | private AppInfo mItem;
131 |
132 | private OnBlockListener mOnBlockListener;
133 |
134 | public interface OnClickListener {
135 | void onClick(View v, AppInfo ppackage);
136 | boolean onLongClick(AppInfo info);
137 | }
138 |
139 | public interface OnBlockListener {
140 | void onBlock(AppInfo appInfo);
141 | void onRemove(AppInfo appInfo);
142 | }
143 |
144 | public BlacklistAdapterViewHolder(@NonNull View itemView,
145 | OnClickListener onClickListener,
146 | OnBlockListener onBlockListener) {
147 | super(itemView);
148 | mName = itemView.findViewById(R.id.blacklist_item_text);
149 | mImage = itemView.findViewById(R.id.blacklist_item_logo);
150 | mPackage = itemView.findViewById(R.id.blacklist_item_package);
151 | mActionButton = itemView.findViewById(R.id.blacklist_action);
152 | mLastOpenDate = itemView.findViewById(R.id.blacklist_last_open);
153 | itemView.setOnClickListener(v -> {
154 | if (onClickListener != null) {
155 | onClickListener.onClick(v, mItem);
156 | }
157 | });
158 | itemView.setOnLongClickListener(v -> {
159 | if (onClickListener != null) {
160 | return onClickListener.onLongClick(mItem);
161 | }
162 | return false;
163 | });
164 | mOnBlockListener = onBlockListener;
165 | }
166 |
167 | public void bind(AppInfo info) {
168 | mItem = info;
169 | mName.setText(info.getText());
170 | mImage.setImageDrawable(info.getImage());
171 | mPackage.setText(info.getPpackage());
172 | if (info.isInBlacklist()) {
173 | mActionButton.setText(itemView.getResources().getString(R.string.remove));
174 | mActionButton.setOnClickListener((v) -> {
175 | if (mOnBlockListener != null) {
176 | mOnBlockListener.onRemove(mItem);
177 | }
178 | });
179 | } else {
180 | mActionButton.setText(itemView.getResources().getString(R.string.block));
181 | mActionButton.setOnClickListener((v) -> {
182 | if (mOnBlockListener != null) {
183 | mOnBlockListener.onBlock(mItem);
184 | }
185 | });
186 | }
187 | if (info.getLastOpenDate() != 0) {
188 | Calendar calendar = Calendar.getInstance();
189 | calendar.setTimeInMillis(info.getLastOpenDate());
190 | String date = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(calendar.getTime());
191 | mLastOpenDate.setVisibility(View.VISIBLE);
192 | mLastOpenDate.setText(itemView.getResources().getString(R.string.last_open_date, date));
193 | } else {
194 | mLastOpenDate.setVisibility(View.GONE);
195 | }
196 | }
197 | }
198 |
199 | public static class LabelViewHolder extends RecyclerView.ViewHolder {
200 | public LabelViewHolder(@NonNull View itemView) {
201 | super(itemView);
202 | }
203 | }
204 |
205 | }
206 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/blacklist/BlacklistFragment.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.blacklist;
2 |
3 | import android.app.job.JobInfo;
4 | import android.app.job.JobScheduler;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.os.Bundle;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 |
12 | import androidx.annotation.NonNull;
13 | import androidx.databinding.DataBindingUtil;
14 | import androidx.fragment.app.Fragment;
15 | import androidx.lifecycle.ViewModelProviders;
16 | import androidx.recyclerview.widget.LinearLayoutManager;
17 | import androidx.recyclerview.widget.RecyclerView;
18 |
19 | import com.simonk.projects.taskmanager.R;
20 | import com.simonk.projects.taskmanager.databinding.FragmentBlacklistBinding;
21 | import com.simonk.projects.taskmanager.databinding.FragmentProcessBinding;
22 | import com.simonk.projects.taskmanager.entity.AppInfo;
23 | import com.simonk.projects.taskmanager.services.blacklist.BlacklistJobService;
24 | import com.simonk.projects.taskmanager.ui.blacklist.viewmodels.BlacklistViewModel;
25 | import com.simonk.projects.taskmanager.ui.process.ProcessFragment;
26 |
27 | import java.util.ArrayList;
28 | import java.util.List;
29 |
30 | public class BlacklistFragment extends Fragment {
31 |
32 | private static final int JOB_SCHEDULE_ID = 1000;
33 |
34 | private RecyclerView mBlacklistRecyclerView;
35 |
36 | private BlackListAdapter mBlackListAdapter;
37 |
38 | private BlacklistViewModel mViewModel;
39 |
40 | public static BlacklistFragment newInstance() {
41 | BlacklistFragment fragment = new BlacklistFragment();
42 | return fragment;
43 | }
44 |
45 | @Override
46 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState) {
47 | View root = bindingView(inflater, container);
48 |
49 | mViewModel = ViewModelProviders.of(this).get(BlacklistViewModel.class);
50 |
51 | mBlacklistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(),
52 | RecyclerView.VERTICAL, false));
53 | mBlackListAdapter = new BlackListAdapter();
54 | mBlacklistRecyclerView.setAdapter(mBlackListAdapter);
55 |
56 | mBlackListAdapter.setItemBlockListener(new BlackListAdapter.BlacklistAdapterViewHolder.OnBlockListener() {
57 | @Override
58 | public void onBlock(AppInfo appInfo) {
59 | mViewModel.insertAppInBlacklist(appInfo);
60 | }
61 |
62 | @Override
63 | public void onRemove(AppInfo appInfo) {
64 | mViewModel.deleteAppFromBlackList(appInfo);
65 | }
66 | });
67 |
68 | runBlacklistScheduler();
69 |
70 | mViewModel.getAppsInfo().observe(this, this::updateAppsInfo);
71 |
72 | return root;
73 | }
74 |
75 | private View bindingView(LayoutInflater inflater, ViewGroup container) {
76 | FragmentBlacklistBinding binding
77 | = DataBindingUtil.inflate(inflater, R.layout.fragment_blacklist, container, false);
78 |
79 | mBlacklistRecyclerView = binding.blacklistApps;
80 |
81 | return binding.getRoot();
82 | }
83 |
84 | private void updateAppsInfo(BlacklistViewModel.AppsInfoList appsInfoList) {
85 | List infoList = new ArrayList<>();
86 | if (appsInfoList.allApps != null) {
87 | infoList.addAll(appsInfoList.allApps);
88 | }
89 | if (appsInfoList.blacklistApps != null) {
90 | infoList.addAll(appsInfoList.blacklistApps);
91 | }
92 | mBlackListAdapter.resolveActionChange(() -> {
93 | mBlackListAdapter.setAppInfoList(infoList);
94 | });
95 | }
96 |
97 | private void runBlacklistScheduler() {
98 | JobScheduler jobScheduler =
99 | (JobScheduler) requireContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
100 | jobScheduler.schedule(new JobInfo.Builder(JOB_SCHEDULE_ID,
101 | new ComponentName(requireContext(), BlacklistJobService.class))
102 | .setPeriodic(10*1000)
103 | .build());
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/blacklist/viewmodels/BlacklistViewModel.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.blacklist.viewmodels;
2 |
3 | import android.app.Application;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.lifecycle.AndroidViewModel;
7 | import androidx.lifecycle.LiveData;
8 | import androidx.lifecycle.MediatorLiveData;
9 | import androidx.lifecycle.MutableLiveData;
10 |
11 | import com.simonk.projects.taskmanager.entity.AppInfo;
12 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
13 | import com.simonk.projects.taskmanager.repository.BlacklistRepository;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | public class BlacklistViewModel extends AndroidViewModel {
19 |
20 | private BlacklistRepository mRepository;
21 |
22 | private MutableLiveData> mAllAppsInfo;
23 | private LiveData> mBlacklistInfo;
24 |
25 | private MediatorLiveData mAppsInfo;
26 |
27 | public BlacklistViewModel(@NonNull Application application) {
28 | super(application);
29 |
30 | mRepository = new BlacklistRepository();
31 |
32 | mAllAppsInfo = new MutableLiveData<>();
33 | mBlacklistInfo = new MutableLiveData<>();
34 |
35 | mAllAppsInfo.setValue(mRepository.getAllInstalledApplicationsInfo(application, true));
36 | mBlacklistInfo = mRepository.getAllBlacklistApplicationInfo(application);
37 |
38 | // important: appsInfo initialization must be after allAppsInfo and blacklistInfo initialization
39 | mAppsInfo = new MediatorLiveData<>();
40 | mAppsInfo.addSource(mAllAppsInfo, value -> {
41 | AppsInfoList appsInfoList = mAppsInfo.getValue();
42 | if (appsInfoList == null) {
43 | appsInfoList = new AppsInfoList();
44 | }
45 | appsInfoList.allApps = value;
46 | mAppsInfo.setValue(appsInfoList);
47 | });
48 | mAppsInfo.addSource(mBlacklistInfo, value -> {
49 | AppsInfoList appsInfoList = mAppsInfo.getValue();
50 | if (appsInfoList == null) {
51 | appsInfoList = new AppsInfoList();
52 | }
53 | appsInfoList.blacklistApps = value;
54 | if (appsInfoList.blacklistApps != null && appsInfoList.allApps != null) {
55 | for (AppInfo info : value) {
56 | for (int i = 0; i < appsInfoList.allApps.size(); i++) {
57 | if (appsInfoList.allApps.get(i).getPpackage().equals(info.getPpackage())) {
58 | appsInfoList.allApps.remove(i);
59 | i--;
60 | }
61 | }
62 | }
63 | }
64 | mAppsInfo.setValue(appsInfoList);
65 | });
66 | }
67 |
68 | public LiveData> getAllAppsInfo() {
69 | return mAllAppsInfo;
70 | }
71 |
72 | public LiveData> getBlacklistInfo() {
73 | return mBlacklistInfo;
74 | }
75 |
76 | public LiveData getAppsInfo() {
77 | return mAppsInfo;
78 | }
79 |
80 | public void insertAppInBlacklist(AppInfo appInfo) {
81 | mRepository.insertAppInBlacklist(getApplication(), appInfo);
82 | }
83 |
84 | public void deleteAppFromBlackList(AppInfo appInfo) {
85 | mRepository.deleteAppFromBlackList(getApplication(), appInfo);
86 | }
87 |
88 | public static class AppsInfoList {
89 | public List allApps;
90 | public List blacklistApps;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/process/ChangeDetailsDialog.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.process;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.widget.EditText;
11 |
12 | import androidx.fragment.app.DialogFragment;
13 |
14 | import com.simonk.projects.taskmanager.R;
15 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
16 | import com.simonk.projects.taskmanager.ui.MainActivity;
17 |
18 | public class ChangeDetailsDialog extends DialogFragment {
19 |
20 | public static final String PROCESS_INFO_ARG = "PROCESS_INFO_ARG";
21 |
22 | public interface OnChanged {
23 | void onChanged(ProcessInfo info, int priority);
24 | }
25 |
26 | private OnChanged mOnChanged;
27 |
28 | @Override
29 | public void onAttach(Context context) {
30 | super.onAttach(context);
31 | if (context instanceof OnChanged) {
32 | mOnChanged = (OnChanged) context;
33 | }
34 | }
35 |
36 | @Override
37 | public Dialog onCreateDialog(Bundle onSaveInstanceState) {
38 | LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
39 | View root = layoutInflater.inflate(R.layout.change_details_dialog, null);
40 |
41 | ProcessInfo processInfo =
42 | (ProcessInfo)getArguments().getSerializable(PROCESS_INFO_ARG);
43 |
44 | EditText editText = root.findViewById(R.id.details_priority);
45 | editText.setText(""+processInfo.getPriority());
46 |
47 | AlertDialog dialog = new AlertDialog.Builder(getActivity())
48 | .setView(root)
49 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
50 | @Override
51 | public void onClick(DialogInterface dialog, int which) {
52 | if (mOnChanged != null) {
53 | String p = editText.getText().toString();
54 | int ip = Integer.parseInt(p);
55 | mOnChanged.onChanged(processInfo, ip);
56 | }
57 | }
58 | })
59 | .create();
60 | return dialog;
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/process/CleanedDialog.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.process;
2 |
3 | import android.app.ActivityManager;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.content.Context;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.widget.TextView;
12 |
13 | import androidx.fragment.app.DialogFragment;
14 |
15 | import com.simonk.projects.taskmanager.R;
16 | import com.simonk.projects.taskmanager.repository.ProcessRepository;
17 | import com.simonk.projects.taskmanager.util.MemoryUtils;
18 |
19 | import java.util.List;
20 |
21 | public class CleanedDialog extends DialogFragment {
22 |
23 | public interface OnCleaned {
24 | void onCleaned();
25 | }
26 |
27 | private OnCleaned mOnCleaned;
28 |
29 | @Override
30 | public void onAttach(Context context) {
31 | super.onAttach(context);
32 | if (context instanceof OnCleaned) {
33 | mOnCleaned = (OnCleaned) context;
34 | }
35 | }
36 |
37 | @Override
38 | public Dialog onCreateDialog(Bundle onSaveInstanceState) {
39 | LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
40 | View root = layoutInflater.inflate(R.layout.cleaned_dialog, null);
41 |
42 | ActivityManager.MemoryInfo memInfo = MemoryUtils.getMemoryInfo(requireContext());
43 | float beforeAvailMemory = MemoryUtils.getAvailableMemory(memInfo);
44 | ((TextView)root.findViewById(R.id.before_memory)).setText(getString(R.string.before_cleaning, beforeAvailMemory));
45 |
46 | new ProcessRepository().killAllProcesses(requireContext());
47 |
48 | memInfo = MemoryUtils.getMemoryInfo(requireContext());
49 | float afterAvailMemory = MemoryUtils.getAvailableMemory(memInfo);
50 | ((TextView)root.findViewById(R.id.after_memory)).setText(getString(R.string.after_cleaning, afterAvailMemory));
51 |
52 | ((TextView)root.findViewById(R.id.percent_memory)).setText((100 - ((int)(beforeAvailMemory * 100 / afterAvailMemory))) + "%");
53 |
54 | AlertDialog dialog = new AlertDialog.Builder(getActivity())
55 | .setView(root)
56 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
57 | @Override
58 | public void onClick(DialogInterface dialog, int which) {
59 | if (mOnCleaned != null) {
60 | mOnCleaned.onCleaned();
61 | }
62 | }
63 | })
64 | .create();
65 | return dialog;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/process/ProcessAdapter.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.process;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import com.simonk.projects.taskmanager.R;
13 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
14 | import com.simonk.projects.taskmanager.ui.util.ObjectListAdapter;
15 |
16 | public class ProcessAdapter extends ObjectListAdapter {
17 |
18 | private ProcessAdapterViewHolder.OnClickListener mItemClickListener;
19 |
20 | @NonNull
21 | @Override
22 | public ProcessAdapterViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
23 | LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
24 | View v = inflater.inflate(R.layout.process_list_item, viewGroup, false);
25 |
26 | return new ProcessAdapterViewHolder(v, mItemClickListener);
27 | }
28 |
29 | @Override
30 | public void onBindViewHolder(@NonNull ProcessAdapterViewHolder processAdapterViewHolder, int i) {
31 | processAdapterViewHolder.bind(getItem(i));
32 | }
33 |
34 | public void setItemClickListener(ProcessAdapterViewHolder.OnClickListener onClickListener) {
35 | mItemClickListener = onClickListener;
36 | }
37 |
38 | public static class ProcessAdapterViewHolder extends RecyclerView.ViewHolder {
39 |
40 | private TextView mName;
41 | private ImageView mImage;
42 | private TextView mPackage;
43 |
44 | private ProcessInfo mItem;
45 |
46 | public interface OnClickListener {
47 | void onClick(View v, ProcessInfo ppackage);
48 | boolean onLongClick(ProcessInfo info);
49 | }
50 |
51 | public ProcessAdapterViewHolder(@NonNull View itemView, OnClickListener onClickListener) {
52 | super(itemView);
53 | mName = itemView.findViewById(R.id.process_list_item_text);
54 | mImage = itemView.findViewById(R.id.process_list_item_logo);
55 | mPackage = itemView.findViewById(R.id.process_list_item_package);
56 | itemView.setOnClickListener(v -> {
57 | if (onClickListener != null) {
58 | onClickListener.onClick(v, mItem);
59 | }
60 | });
61 | itemView.setOnLongClickListener(v -> {
62 | if (onClickListener != null) {
63 | return onClickListener.onLongClick(mItem);
64 | }
65 | return false;
66 | });
67 | }
68 |
69 | public void bind(ProcessInfo info) {
70 | mItem = info;
71 | mName.setText(info.getText());
72 | mImage.setImageDrawable(info.getImage());
73 | mPackage.setText(info.getPpackage());
74 | }
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/process/ProcessFragment.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.process;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.FrameLayout;
9 | import android.widget.TextView;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.databinding.DataBindingUtil;
13 | import androidx.fragment.app.Fragment;
14 | import androidx.lifecycle.ViewModelProviders;
15 | import androidx.recyclerview.widget.LinearLayoutManager;
16 | import androidx.recyclerview.widget.RecyclerView;
17 |
18 | import com.simonk.projects.taskmanager.R;
19 | import com.simonk.projects.taskmanager.databinding.FragmentProcessBinding;
20 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
21 | import com.simonk.projects.taskmanager.ui.process.viewmodels.ProcessViewModel;
22 |
23 | import java.util.List;
24 |
25 | public class ProcessFragment extends Fragment
26 | implements CleanedDialog.OnCleaned, ChangeDetailsDialog.OnChanged {
27 |
28 | private RecyclerView mAppsRecyclerView;
29 | private Button mClearAllButton;
30 | private TextView mAllMemory;
31 | private TextView mFreeMemory;
32 | private TextView mPercentMemory;
33 |
34 | private ProcessAdapter mProcessAdapter;
35 |
36 | private ProcessViewModel mViewModel;
37 |
38 | public static ProcessFragment newInstance() {
39 | ProcessFragment fragment = new ProcessFragment();
40 | return fragment;
41 | }
42 |
43 | @Override
44 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState) {
45 | View root = bindingView(inflater, container);
46 |
47 | mAppsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(),
48 | RecyclerView.VERTICAL, false));
49 | mProcessAdapter = new ProcessAdapter();
50 | mAppsRecyclerView.setAdapter(mProcessAdapter);
51 |
52 | mClearAllButton.setOnClickListener(new View.OnClickListener() {
53 | @Override
54 | public void onClick(View v) {
55 | CleanedDialog cleanedDialog = new CleanedDialog();
56 | cleanedDialog.setTargetFragment(ProcessFragment.this, 0);
57 | cleanedDialog.show(requireFragmentManager(), "CleanedDialog");
58 | }
59 | });
60 |
61 | mProcessAdapter.setItemClickListener(new ProcessAdapter.ProcessAdapterViewHolder.OnClickListener() {
62 | @Override
63 | public void onClick(View v, ProcessInfo info) {
64 | ViewGroup detailsView = (ViewGroup)
65 | LayoutInflater.from(requireContext()).inflate(R.layout.process_list_item_details, null);
66 | if (((FrameLayout) v.findViewById(R.id.process_list_item_details)).getChildCount() == 0) {
67 | ((TextView) detailsView.findViewById(R.id.priority)).setText(getString(R.string.priority, String.valueOf(info.getPriority())));
68 | ((TextView) detailsView.findViewById(R.id.status)).setText(getString(R.string.enabled, String.valueOf(info.isStatus())));
69 | ((TextView) detailsView.findViewById(R.id.uid)).setText(getString(R.string.uid, String.valueOf(info.isStatus())));
70 | ((TextView) detailsView.findViewById(R.id.min_sdk)).setText(getString(R.string.target_sdk, String.valueOf(info.getMinSdk())));
71 | ((TextView) detailsView.findViewById(R.id.description)).setText(getString(R.string.description, info.getDescription()));
72 | ((FrameLayout) v.findViewById(R.id.process_list_item_details)).addView(detailsView);
73 | ((Button) detailsView.findViewById(R.id.kill)).setOnClickListener(new View.OnClickListener() {
74 | @Override
75 | public void onClick(View v) {
76 | mViewModel.getRepository().killProcess(requireContext(), info);
77 | updateUi();
78 | }
79 | });
80 | } else {
81 | ((FrameLayout) v.findViewById(R.id.process_list_item_details)).removeAllViews();
82 | }
83 | }
84 |
85 | @Override
86 | public boolean onLongClick(ProcessInfo info) {
87 | Bundle bundle = new Bundle();
88 | bundle.putSerializable(ChangeDetailsDialog.PROCESS_INFO_ARG, info);
89 | ChangeDetailsDialog changeDetailsDialog = new ChangeDetailsDialog();
90 | changeDetailsDialog.setArguments(bundle);
91 | changeDetailsDialog.show(requireFragmentManager(), "ChangeDetailsDialog");
92 | return false;
93 | }
94 | });
95 |
96 | mViewModel = ViewModelProviders.of(this).get(ProcessViewModel.class);
97 | mViewModel.getAllProcessInfo().observe(this, this::updateProcessAdapter);
98 |
99 | return root;
100 | }
101 |
102 | private View bindingView(LayoutInflater inflater, ViewGroup container) {
103 | FragmentProcessBinding binding
104 | = DataBindingUtil.inflate(inflater, R.layout.fragment_process, container, false);
105 |
106 | mAppsRecyclerView = binding.activityMainRecycler;
107 | mClearAllButton = binding.clearAll;
108 | mAllMemory = binding.allMemory;
109 | mPercentMemory = binding.percentMemory;
110 | mFreeMemory = binding.freeMemory;
111 |
112 | return binding.getRoot();
113 | }
114 |
115 | @Override
116 | public void onResume() {
117 | super.onResume();
118 | updateUi();
119 | }
120 |
121 | private void updateUi() {
122 | float allMemory = mViewModel.getTotalMemory();
123 | float availMemory = mViewModel.getAvailableMemory();
124 | mAllMemory.setText("" + allMemory + "G");
125 | mFreeMemory.setText("" + availMemory + "G");
126 | mPercentMemory.setText("" + (int)(availMemory * 100 / allMemory) + "%");
127 |
128 | mViewModel.forceUpdateProcessInfo();
129 | }
130 |
131 | private void updateProcessAdapter(List processInfoList) {
132 | mProcessAdapter.resolveActionChange(() -> {
133 | mProcessAdapter.setItemsList(processInfoList);
134 | });
135 | }
136 |
137 | @Override
138 | public void onCleaned() {
139 | updateUi();
140 | }
141 |
142 | @Override
143 | public void onChanged(ProcessInfo info, int priority) {
144 | mViewModel.getRepository().changeProcessPriority(requireContext(), info, priority);
145 | updateUi();
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/process/viewmodels/ProcessViewModel.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.process.viewmodels;
2 |
3 | import android.app.ActivityManager;
4 | import android.app.Application;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.lifecycle.AndroidViewModel;
8 | import androidx.lifecycle.LiveData;
9 | import androidx.lifecycle.MutableLiveData;
10 |
11 | import com.simonk.projects.taskmanager.entity.ProcessInfo;
12 | import com.simonk.projects.taskmanager.repository.ProcessRepository;
13 | import com.simonk.projects.taskmanager.util.MemoryUtils;
14 |
15 | import java.util.List;
16 |
17 | public class ProcessViewModel extends AndroidViewModel {
18 |
19 | private ProcessRepository mRepository;
20 |
21 | private MutableLiveData> mAllProcessInfo;
22 |
23 | public ProcessViewModel(@NonNull Application application) {
24 | super(application);
25 |
26 | mRepository = new ProcessRepository();
27 |
28 | mAllProcessInfo = new MutableLiveData<>();
29 | mAllProcessInfo.setValue(mRepository.getAllProcessInfo(application));
30 | }
31 |
32 | public ProcessRepository getRepository() {
33 | return mRepository;
34 | }
35 |
36 | public float getAvailableMemory() {
37 | ActivityManager.MemoryInfo memInfo = MemoryUtils.getMemoryInfo(getApplication());
38 | return MemoryUtils.getAvailableMemory(memInfo);
39 | }
40 |
41 | public float getTotalMemory() {
42 | ActivityManager.MemoryInfo memInfo = MemoryUtils.getMemoryInfo(getApplication());
43 | return MemoryUtils.getTotalMemory(memInfo);
44 | }
45 |
46 | public void forceUpdateProcessInfo() {
47 | mAllProcessInfo.setValue(mRepository.getAllProcessInfo(getApplication()));
48 | }
49 |
50 | public LiveData> getAllProcessInfo() {
51 | return mAllProcessInfo;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/terminal/TerminalFragment.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.terminal;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.graphics.drawable.ColorDrawable;
6 | import android.os.Bundle;
7 | import android.text.Editable;
8 | import android.text.Spannable;
9 | import android.text.SpannableStringBuilder;
10 | import android.text.TextWatcher;
11 | import android.text.style.ForegroundColorSpan;
12 | import android.util.AttributeSet;
13 | import android.util.TypedValue;
14 | import android.view.Gravity;
15 | import android.view.LayoutInflater;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.widget.Button;
19 | import android.widget.EditText;
20 | import android.widget.LinearLayout;
21 | import android.widget.TextView;
22 |
23 | import androidx.annotation.NonNull;
24 | import androidx.appcompat.widget.AppCompatTextView;
25 | import androidx.databinding.DataBindingUtil;
26 | import androidx.fragment.app.Fragment;
27 | import androidx.lifecycle.ViewModelProviders;
28 |
29 | import com.simonk.projects.taskmanager.R;
30 | import com.simonk.projects.taskmanager.database.entity.TerminalSnapshotEntity;
31 | import com.simonk.projects.taskmanager.databinding.FragmentTerminalBinding;
32 | import com.simonk.projects.taskmanager.terminal.StringTerminalListener;
33 | import com.simonk.projects.taskmanager.ui.terminal.viewmodels.TerminalViewModel;
34 |
35 | import java.util.List;
36 |
37 | public class TerminalFragment extends Fragment {
38 |
39 | private ViewGroup mRootView;
40 | private Button mStopButton;
41 | private Button mClearButton;
42 |
43 | private TerminalViewModel mViewModel;
44 |
45 | /*
46 | In some cases (like app start) we dont want to request a focus to not show keyboard when it's wrong time
47 | */
48 | private boolean mCanRequestFocus = true;
49 |
50 | public static TerminalFragment newInstance() {
51 | TerminalFragment fragment = new TerminalFragment();
52 | return fragment;
53 | }
54 |
55 | @Override
56 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState) {
57 | View root = bindingView(inflater, container);
58 |
59 | mStopButton.setOnClickListener((v) -> {
60 | mViewModel.stopTerminalRequest();
61 | });
62 |
63 | mClearButton.setOnClickListener((v) -> {
64 | mViewModel.clearTerminalSnapshots();
65 | });
66 |
67 | mViewModel = ViewModelProviders.of(this).get(TerminalViewModel.class);
68 | mViewModel.getTerminalSnapshots().observe(this, this::updateUi);
69 |
70 |
71 | return root;
72 | }
73 |
74 | /**
75 | * Notify fragment that it became to primari in pager
76 | * @param current
77 | */
78 | public void setCurrent(boolean current) {
79 | mCanRequestFocus = current;
80 | }
81 |
82 | private View bindingView(LayoutInflater inflater, ViewGroup container) {
83 | FragmentTerminalBinding binding
84 | = DataBindingUtil.inflate(inflater, R.layout.fragment_terminal, container, false);
85 |
86 | mRootView = binding.terminalRoot;
87 | mStopButton = binding.terminalStopButton;
88 | mClearButton = binding.terminalClearButton;
89 |
90 | return binding.getRoot();
91 | }
92 |
93 | private void updateUi(List requestList) {
94 | mRootView.removeAllViews();
95 | for (TerminalSnapshotEntity snapshot : requestList) {
96 | EditText editText = addEditTextLayout(mRootView);
97 | editText.setText(snapshot.request);
98 | editText.setEnabled(false);
99 | TextView textView = addResponseTextView();
100 | textView.setText(snapshot.response);
101 | }
102 | addEditTextLayout(mRootView);
103 | }
104 |
105 | private class TerminalTextWatcher implements TextWatcher {
106 |
107 | @Override
108 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
109 |
110 | }
111 |
112 | @Override
113 | public void onTextChanged(CharSequence s, int start, int before, int count) {
114 | String change = s.subSequence(start, start + count).toString();
115 | if (change.equals("\n")) {
116 | makeTerminalRequest(s.toString());
117 | }
118 | }
119 |
120 | @Override
121 | public void afterTextChanged(Editable s) {
122 | String text = s.toString();
123 | if (text.contains("\n")) {
124 | s.clear();
125 | s.append(text.replace("\n", ""));
126 | }
127 | }
128 | }
129 |
130 | private void makeTerminalRequest(String request) {
131 | blockAllEditTexts(mRootView);
132 | TextView responseTextView = addResponseTextView();
133 | mViewModel.makeNewTerminalRequest(request, new StringTerminalListener() {
134 |
135 | private void printAsError(TextView textView, String error) {
136 | String text = textView.getText().toString();
137 | SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
138 | spannableStringBuilder.append(error);
139 | spannableStringBuilder.setSpan(new ForegroundColorSpan(Color.RED),
140 | text.length(),
141 | text.length()+error.length(),
142 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
143 | textView.setText(spannableStringBuilder);
144 | }
145 |
146 | @Override
147 | public void onInput(String input) {
148 | responseTextView.setText(input);
149 | }
150 |
151 | @Override
152 | public void onInputStarted() {
153 | responseTextView.setText("");
154 | }
155 |
156 | @Override
157 | public void onInputFinished(Exception exception) {
158 | if (exception != null) {
159 | printAsError(responseTextView, exception.getMessage());
160 | }
161 | }
162 |
163 | @Override
164 | public void onError(String input) {
165 | printAsError(responseTextView, input);
166 | }
167 |
168 | @Override
169 | public void onErrorStarted() {
170 | }
171 |
172 | @Override
173 | public void onErrorFinished(Exception exception) {
174 | if (exception != null) {
175 | printAsError(responseTextView, exception.getMessage());
176 | }
177 | }
178 |
179 | @Override
180 | public String onOutputString() {
181 | return null;
182 | }
183 |
184 | @Override
185 | public void onFinished(Exception exception) {
186 | if (exception != null) {
187 | printAsError(responseTextView, exception.getMessage());
188 | }
189 | finishRequest();
190 | }
191 | });
192 | }
193 |
194 | private void sendOutputString(String output) {
195 | mViewModel.sendOutputString(output);
196 | }
197 |
198 | private void finishRequest() {
199 | mViewModel.addTerminalSnapshot(new TerminalSnapshotEntity(
200 | findLastEditText(mRootView).getText().toString(),
201 | findLastTextView(mRootView).getText().toString()
202 | ));
203 | blockAllEditTexts(mRootView);
204 | addEditTextLayout(mRootView);
205 | }
206 |
207 | private void blockAllEditTexts(ViewGroup parent) {
208 | for (int i = 0; i < parent.getChildCount(); i++) {
209 | View child = parent.getChildAt(i);
210 | if (child instanceof ViewGroup) {
211 | blockAllEditTexts((ViewGroup) child);
212 | }
213 | if (child instanceof EditText) {
214 | child.setEnabled(false);
215 | if (mCanRequestFocus) {
216 | child.requestFocus();
217 | }
218 | }
219 | }
220 | }
221 |
222 | private EditText findLastEditText(ViewGroup parent) {
223 | for (int i = parent.getChildCount()-1; i >= 0; i--) {
224 | View child = parent.getChildAt(i);
225 | if (child instanceof ViewGroup) {
226 | EditText editText = findLastEditText((ViewGroup) child);
227 | if (editText != null) {
228 | return editText;
229 | }
230 | }
231 | if (child instanceof EditText) {
232 | return (EditText)child;
233 | }
234 | }
235 | return null;
236 | }
237 |
238 | private TextView findLastTextView(ViewGroup parent) {
239 | for (int i = parent.getChildCount()-1; i >= 0; i--) {
240 | View child = parent.getChildAt(i);
241 | if (child instanceof ViewGroup) {
242 | TextView textView = findLastTextView((ViewGroup) child);
243 | if (textView != null) {
244 | return textView;
245 | }
246 | }
247 | if (child instanceof TextView) {
248 | return (TextView)child;
249 | }
250 | }
251 | return null;
252 | }
253 |
254 | private TextView addResponseTextView() {
255 | int margin16 = (int) (16 * getResources().getDisplayMetrics().density);
256 |
257 | TextView textView = new TextView(requireContext());
258 | LinearLayout.LayoutParams textViewParams = new LinearLayout.LayoutParams(
259 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
260 | textViewParams.leftMargin = margin16;
261 | textViewParams.rightMargin = margin16;
262 | textViewParams.bottomMargin = margin16;
263 |
264 | textView.setTextColor(Color.parseColor("#FFFFFF"));
265 |
266 | mRootView.addView(textView, textViewParams);
267 | return textView;
268 | }
269 |
270 | private EditText addEditTextLayout(ViewGroup parent) {
271 | int margin16 = (int) (16 * getResources().getDisplayMetrics().density);
272 | int margin8 = (int) (8 * getResources().getDisplayMetrics().density);
273 |
274 | EditText terminalEditText = new EditText(requireContext());
275 | terminalEditText.addTextChangedListener(new TerminalTextWatcher());
276 | LinearLayout.LayoutParams terminalEditTextParams = new LinearLayout.LayoutParams(
277 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
278 | terminalEditTextParams.leftMargin = margin16;
279 | terminalEditTextParams.topMargin = margin8;
280 | terminalEditTextParams.rightMargin = margin16;
281 | terminalEditText.setLayoutParams(terminalEditTextParams);
282 |
283 | terminalEditText.setBackground(null);
284 | terminalEditText.setTextColor(Color.parseColor("#FFFFFF"));
285 |
286 | if (mCanRequestFocus) {
287 | terminalEditText.requestFocus();
288 | }
289 |
290 | SignTextView signTextView = new SignTextView(requireContext());
291 |
292 | View devider = new View(requireContext());
293 | ViewGroup.MarginLayoutParams deviderParams = new ViewGroup.MarginLayoutParams(
294 | ViewGroup.LayoutParams.MATCH_PARENT, (int) (getResources().getDisplayMetrics().density));
295 | devider.setLayoutParams(deviderParams);
296 | devider.setBackground(new ColorDrawable(Color.parseColor("#37474f")));
297 |
298 | LinearLayout editTextLayout = new LinearLayout(requireContext());
299 | editTextLayout.setOrientation(LinearLayout.HORIZONTAL);
300 | ViewGroup.MarginLayoutParams editTextLayoutParams = new ViewGroup.MarginLayoutParams(
301 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
302 | editTextLayout.setLayoutParams(editTextLayoutParams);
303 |
304 | editTextLayout.addView(signTextView);
305 | editTextLayout.addView(terminalEditText);
306 |
307 | editTextLayout.setOnClickListener((v) -> {
308 | String text = ((EditText)((ViewGroup)v).getChildAt(1)).getText().toString();
309 | EditText lastEditText = findLastEditText(mRootView);
310 | if (lastEditText != v && lastEditText.isEnabled()) {
311 | lastEditText.setText(text);
312 | }
313 | });
314 |
315 | parent.addView(devider);
316 | parent.addView(editTextLayout);
317 |
318 | return terminalEditText;
319 | }
320 |
321 | private static class SignTextView extends AppCompatTextView {
322 | public SignTextView(Context context) {
323 | this(context, null);
324 | }
325 |
326 | public SignTextView(Context context, AttributeSet attrs) {
327 | this(context, attrs, 0);
328 | }
329 |
330 | public SignTextView(Context context, AttributeSet attrs, int defStyleAttr) {
331 | super(context, attrs, defStyleAttr);
332 | init();
333 | }
334 |
335 | private void init() {
336 | int margin = (int) (8 * getResources().getDisplayMetrics().density);
337 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
338 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
339 | params.leftMargin = margin;
340 | params.gravity = Gravity.CENTER_VERTICAL;
341 |
342 | setLayoutParams(params);
343 |
344 | setText(">>");
345 | setTextColor(Color.parseColor("#009688"));
346 | setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
347 | }
348 | }
349 |
350 | }
351 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/terminal/viewmodels/TerminalViewModel.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.terminal.viewmodels;
2 |
3 | import android.app.Application;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.lifecycle.AndroidViewModel;
7 | import androidx.lifecycle.LiveData;
8 | import androidx.lifecycle.MutableLiveData;
9 | import androidx.lifecycle.Observer;
10 |
11 | import com.simonk.projects.taskmanager.database.DatabaseManager;
12 | import com.simonk.projects.taskmanager.database.LocalDatabase;
13 | import com.simonk.projects.taskmanager.database.entity.TerminalSnapshotEntity;
14 | import com.simonk.projects.taskmanager.entity.TerminalCall;
15 | import com.simonk.projects.taskmanager.terminal.StringTerminalListener;
16 | import com.simonk.projects.taskmanager.terminal.TerminalService;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | public class TerminalViewModel extends AndroidViewModel {
22 |
23 | private MutableLiveData> mTerminalSnapshotsArray;
24 |
25 | private TerminalService mTerminalService;
26 |
27 | public TerminalViewModel(@NonNull Application application) {
28 | super(application);
29 |
30 | mTerminalService = new TerminalService();
31 |
32 | mTerminalSnapshotsArray = new MutableLiveData<>();
33 | mTerminalSnapshotsArray.setValue(new ArrayList<>());
34 |
35 | LiveData> databaseSnapshots = DatabaseManager.loadSnapshots(application);
36 | databaseSnapshots.observeForever(new Observer>() {
37 | @Override
38 | public void onChanged(List snapshotEntityList) {
39 | mTerminalSnapshotsArray.setValue(snapshotEntityList);
40 | databaseSnapshots.removeObserver(this);
41 | }
42 | });
43 | }
44 |
45 | public void makeNewTerminalRequest(String requestString, StringTerminalListener terminalListener) {
46 | TerminalCall request = new TerminalCall();
47 | request.setRequest(requestString);
48 | mTerminalService.makeTerminalRequest(request, terminalListener);
49 | }
50 |
51 | public void stopTerminalRequest() {
52 | mTerminalService.stopTerminalRequest();
53 | }
54 |
55 | public void clearTerminalSnapshots() {
56 | stopTerminalRequest();
57 | mTerminalSnapshotsArray.setValue(new ArrayList<>());
58 | DatabaseManager.deleteAllSnapshots(getApplication());
59 | }
60 |
61 | public LiveData> getTerminalSnapshots() {
62 | return mTerminalSnapshotsArray;
63 | }
64 |
65 | public void addTerminalSnapshot(TerminalSnapshotEntity snapshot) {
66 | mTerminalSnapshotsArray.getValue().add(snapshot);
67 | DatabaseManager.insertSnapshot(getApplication(), snapshot);
68 | }
69 |
70 | public void sendOutputString(String output) {
71 | mTerminalService.sendOutputString(output);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/ui/util/ObjectListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.ui.util;
2 |
3 | import android.os.Bundle;
4 | import android.os.Parcelable;
5 | import android.util.SparseArray;
6 |
7 | import androidx.recyclerview.widget.DiffUtil;
8 | import androidx.recyclerview.widget.RecyclerView;
9 |
10 | import java.io.Serializable;
11 | import java.util.ArrayList;
12 | import java.util.Arrays;
13 | import java.util.Collections;
14 | import java.util.Comparator;
15 | import java.util.List;
16 |
17 | public abstract class ObjectListAdapter extends RecyclerView.Adapter {
18 |
19 | private static final String INITIAL_ITEMS_PARCELABLE_KEY = "INITIAL_ITEMS_PARCELABLE_KEY";
20 | private static final String INITIAL_ITEMS_SERIALIZABLE_KEY = "INITIAL_ITEMS_SERIALIZABLE_KEY";
21 |
22 | private static final String FILTERED_ITEMS_PARCELABLE_KEY = "FILTERED_ITEMS_PARCELABLE_KEY";
23 | private static final String FILTERED_ITEMS_SERIALIZABLE_KEY = "FILTERED_ITEMS_SERIALIZABLE_KEY";
24 |
25 | private static final String FILTER_REQUEST_CODE_KEY = "FILTER_CONDITION_KEY";
26 | private static final String FILTER_CONDITION_KEY = "FILTER_CONDITION_KEY";
27 |
28 | private List mFilteredItems; //-------
29 | private List mInitialItemsBeforeFiltering;
30 | private SparseArray> mFilterConditions;
31 | private Comparator mSortComparator;
32 |
33 | public ObjectListAdapter() {
34 | mInitialItemsBeforeFiltering = new ArrayList<>();
35 | mFilterConditions = new SparseArray<>();
36 | }
37 |
38 | public interface FilterCondition extends Serializable {
39 | boolean isMatch(F item);
40 | }
41 |
42 | private void refilter() {
43 | if (mFilterConditions.size() == 0) {
44 | cancelFiltering();
45 | return;
46 | }
47 |
48 | List itemsToFilter = mInitialItemsBeforeFiltering;
49 | for(int i = 0; i < mFilterConditions.size(); i++) {
50 | itemsToFilter = processFiltering(itemsToFilter, mFilterConditions.valueAt(i));
51 | }
52 | mFilteredItems = itemsToFilter;
53 | }
54 |
55 | protected void filter(FilterCondition filterCondition, int requestCode) {
56 | if (filterCondition == null) {
57 | return;
58 | }
59 |
60 | if (isFiltered(requestCode)) {
61 | mFilterConditions.delete(requestCode);
62 | refilter();
63 | }
64 |
65 | mFilteredItems = processFiltering(getItemsList(), filterCondition);
66 | mFilterConditions.append(requestCode, filterCondition);
67 | }
68 |
69 | protected void sort(Comparator comparator) {
70 | mSortComparator = comparator;
71 | if (mInitialItemsBeforeFiltering != null) {
72 | Collections.sort(mInitialItemsBeforeFiltering, comparator);
73 | }
74 | if (mFilteredItems != null) {
75 | Collections.sort(mFilteredItems, comparator);
76 | }
77 | }
78 |
79 | private List processFiltering(List itemsToFilter, FilterCondition filterCondition) {
80 | List filteredItems = new ArrayList<>();
81 |
82 | for (T item : itemsToFilter) {
83 | if (filterCondition.isMatch(item)) {
84 | filteredItems.add(item);
85 | }
86 | }
87 |
88 | return filteredItems;
89 | }
90 |
91 | public void cancelFiltering() {
92 | mFilterConditions.clear();
93 | if (mFilteredItems != null) {
94 | mFilteredItems = null;
95 | }
96 | }
97 |
98 | protected void cancelFiltering(int requestCode) {
99 | mFilterConditions.delete(requestCode);
100 | if (mFilterConditions.size() == 0) {
101 | cancelFiltering();
102 | } else {
103 | refilter();
104 | }
105 | }
106 |
107 | protected boolean isFiltered(int requestCode) {
108 | return mFilterConditions.indexOfKey(requestCode) != -1;
109 | }
110 |
111 | public boolean isFiltered() {
112 | return mFilteredItems != null;
113 | }
114 |
115 | public void setItemsList(List items) {
116 | mInitialItemsBeforeFiltering.clear();
117 | mInitialItemsBeforeFiltering.addAll(items);
118 |
119 | if (mSortComparator != null) {
120 | sort(mSortComparator);
121 | }
122 |
123 | refilter();
124 | }
125 |
126 | public List getItemsList() {
127 | if (mFilteredItems == null) {
128 | return new ArrayList<>(mInitialItemsBeforeFiltering);
129 | }
130 | return new ArrayList<>(mFilteredItems);
131 | }
132 |
133 | public List getItemsListBeforeFiltering() {
134 | return mInitialItemsBeforeFiltering;
135 | }
136 |
137 | @Override
138 | public int getItemCount() {
139 | return getItemsList() != null ? getItemsList().size() : 0;
140 | }
141 |
142 | public T getItem(int index) {
143 | if (index >= getItemsList().size()) {
144 | return null;
145 | }
146 |
147 | return getItemsList().get(index);
148 | }
149 |
150 | public void resolveActionChange(Runnable action) {
151 | List before = getItemsList();
152 | action.run();
153 | resolveItemsListChange(before);
154 | }
155 |
156 | public void resolveItemsListChange(List oldList) {
157 | List newList = getItemsList();
158 | if (oldList == null || newList == null) {
159 | return;
160 | }
161 |
162 | DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtilImpl(oldList, newList), true);
163 | result.dispatchUpdatesTo(this);
164 | }
165 |
166 | protected boolean isItemsContentEquals(T first, T second) {
167 | return first.equals(second);
168 | }
169 |
170 | private class DiffUtilImpl extends DiffUtil.Callback {
171 |
172 | private List mOldList;
173 | private List mNewList;
174 |
175 | public DiffUtilImpl(List oldList, List newList) {
176 | mOldList = oldList;
177 | mNewList = newList;
178 | }
179 |
180 | @Override
181 | public int getOldListSize() {
182 | return mOldList.size();
183 | }
184 |
185 | @Override
186 | public int getNewListSize() {
187 | return mNewList.size();
188 | }
189 |
190 | @Override
191 | public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
192 | return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
193 | }
194 |
195 | @Override
196 | public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
197 | return isItemsContentEquals(mOldList.get(oldItemPosition), mNewList.get(newItemPosition));
198 | }
199 | }
200 |
201 | @SuppressWarnings("unchecked")
202 | public Bundle packAllItems() {
203 | if (mInitialItemsBeforeFiltering.isEmpty()) {
204 | return null;
205 | }
206 |
207 | boolean itParcelable;
208 | boolean itSerializable;
209 |
210 | try {
211 | Parcelable parcelable = (Parcelable) mInitialItemsBeforeFiltering.get(0);
212 | itParcelable = true;
213 | } catch (ClassCastException e) {
214 | itParcelable = false;
215 | }
216 |
217 | try {
218 | Serializable serializable = (Serializable) mInitialItemsBeforeFiltering.get(0);
219 | itSerializable = true;
220 | } catch (ClassCastException e) {
221 | itSerializable = false;
222 | }
223 |
224 | Bundle itemsBox = new Bundle();
225 |
226 | if (mFilterConditions.size() != 0) {
227 | int[] requestCodes = new int[mFilterConditions.size()];
228 | FilterCondition[] filterConditions = new FilterCondition[mFilterConditions.size()];
229 | for (int i = 0; i < mFilterConditions.size(); i++) {
230 | requestCodes[i] = mFilterConditions.keyAt(i);
231 | filterConditions[i] = mFilterConditions.valueAt(i);
232 | }
233 | itemsBox.putIntArray(FILTER_REQUEST_CODE_KEY, requestCodes);
234 | itemsBox.putSerializable(FILTER_CONDITION_KEY, filterConditions);
235 | }
236 |
237 | if (itParcelable) {
238 | List initialParcelableList = (List) mInitialItemsBeforeFiltering;
239 | Parcelable[] parcelables = initialParcelableList.toArray(new Parcelable[] {});
240 | itemsBox.putParcelableArray(INITIAL_ITEMS_PARCELABLE_KEY, parcelables);
241 |
242 | if (isFiltered()) {
243 | List filteredParcelableList = (List) mFilteredItems;
244 | parcelables = filteredParcelableList.toArray(new Parcelable[]{});
245 | itemsBox.putParcelableArray(FILTERED_ITEMS_PARCELABLE_KEY, parcelables);
246 | }
247 |
248 | return itemsBox;
249 | }
250 | if (itSerializable) {
251 | List initialSerializableList = (List) mInitialItemsBeforeFiltering;
252 | Serializable[] serializables = initialSerializableList.toArray(new Serializable[] {});
253 | itemsBox.putSerializable(INITIAL_ITEMS_SERIALIZABLE_KEY, serializables);
254 |
255 | if (mFilteredItems != null) {
256 | List filteredSerializableList = (List) mFilteredItems;
257 | serializables = filteredSerializableList.toArray(new Serializable[]{});
258 | itemsBox.putSerializable(FILTERED_ITEMS_SERIALIZABLE_KEY, serializables);
259 | }
260 |
261 | return itemsBox;
262 | }
263 |
264 | return null;
265 | }
266 |
267 | @SuppressWarnings("unchecked")
268 | public void unpackAllItems(Bundle itemsBox) {
269 | if (itemsBox == null) {
270 | return;
271 | }
272 |
273 | int[] requestCodes = itemsBox.getIntArray(FILTER_REQUEST_CODE_KEY);
274 | FilterCondition[] filterConditions = (FilterCondition[]) itemsBox.getSerializable(FILTER_CONDITION_KEY);
275 | if (requestCodes != null && filterConditions != null) {
276 | for (int i = 0; i < requestCodes.length && i < filterConditions.length; i++) {
277 | mFilterConditions.append(requestCodes[i], filterConditions[i]);
278 | }
279 | }
280 |
281 | Parcelable[] initialParcelables = itemsBox.getParcelableArray(INITIAL_ITEMS_PARCELABLE_KEY);
282 | Parcelable[] filteredParcelables = itemsBox.getParcelableArray(FILTERED_ITEMS_PARCELABLE_KEY);
283 | if (initialParcelables != null) {
284 | if (filteredParcelables != null && mFilterConditions.size() != 0) {
285 | /* избегаем излишней фильтрации */
286 | List fixedSizeInitialList = (List) Arrays.asList(initialParcelables);
287 | List fixedSizeFilteredList = (List) Arrays.asList(filteredParcelables);
288 | mInitialItemsBeforeFiltering = new ArrayList<>(fixedSizeInitialList);
289 | mFilteredItems = new ArrayList<>(fixedSizeFilteredList);
290 | } else {
291 | setItemsList((List) Arrays.asList(initialParcelables));
292 | }
293 | }
294 |
295 | Serializable[] initialSerializables =
296 | (Serializable[]) itemsBox.getSerializable(INITIAL_ITEMS_SERIALIZABLE_KEY);
297 | Serializable[] filteredSerializables =
298 | (Serializable[]) itemsBox.getSerializable(FILTERED_ITEMS_SERIALIZABLE_KEY);
299 | if (initialSerializables != null) {
300 | if (filteredSerializables != null && mFilterConditions.size() != 0) {
301 | /* избегаем излишней фильтрации */
302 | List fixedSizeInitialList = (List) Arrays.asList(initialSerializables);
303 | List fixedSizeFilteredList = (List) Arrays.asList(filteredSerializables);
304 | mInitialItemsBeforeFiltering = new ArrayList<>(fixedSizeInitialList);
305 | mFilteredItems = new ArrayList<>(fixedSizeFilteredList);
306 | } else {
307 | setItemsList((List) Arrays.asList(initialSerializables));
308 | }
309 | }
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/util/MemoryUtils.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.util;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 |
6 | public class MemoryUtils {
7 |
8 | private static final double B_TO_GB = 1024d * 1024d * 1024d;
9 |
10 | public static ActivityManager.MemoryInfo getMemoryInfo(Context context) {
11 | ActivityManager actManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
12 | ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
13 |
14 | actManager.getMemoryInfo(memInfo);
15 | return memInfo;
16 | }
17 |
18 | public static float getAvailableMemory(ActivityManager.MemoryInfo memoryInfo) {
19 | return ((int) (memoryInfo.availMem / B_TO_GB * 100)) / 100.0f;
20 | }
21 |
22 | public static float getTotalMemory(ActivityManager.MemoryInfo memoryInfo) {
23 | return ((int) (memoryInfo.totalMem / B_TO_GB * 100)) / 100.0f;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simonk/projects/taskmanager/util/ProcessCompat.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager.util;
2 |
3 | public class ProcessCompat {
4 |
5 | public static boolean isAlive(Process process) {
6 | try {
7 | process.exitValue();
8 | return false;
9 | } catch(IllegalThreadStateException e) {
10 | return true;
11 | }
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_close.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/drawable-hdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/drawable-mdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/drawable-xhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/drawable-xxhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
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 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
17 |
18 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/blacklist_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
17 |
20 |
21 |
30 |
31 |
42 |
43 |
51 |
52 |
58 |
59 |
67 |
68 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/blacklist_item_label.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/change_details_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
20 |
21 |
25 |
26 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cleaned_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
28 |
29 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_blacklist.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_process.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
17 |
18 |
24 |
25 |
28 |
29 |
41 |
42 |
53 |
54 |
65 |
66 |
76 |
77 |
89 |
90 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_terminal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
23 |
24 |
32 |
33 |
34 |
35 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/process_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
17 |
20 |
21 |
27 |
28 |
39 |
40 |
48 |
49 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/process_list_item_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
28 |
29 |
35 |
36 |
42 |
43 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TaskManager
3 |
4 | Черный список:
5 | Все приложения:
6 | Убрать
7 | Заблокировать
8 | Последнее время открытия: %s
9 | Прилоежение из черного списка запущено!
10 | Приложение из черного списка с названием %s запущено
11 | Перед чисткой: %fG
12 | После чистки: %fG
13 |
14 | Приложения
15 | Черный список
16 | Терминал
17 |
18 | Приоритет: %s
19 | Включено: %s
20 | Uid: %s
21 | Целевой sdk: %s
22 | Описание: %s
23 |
24 | Приоритет
25 | Запись
26 |
27 | Всего ОЗУ:
28 | Свободно ОЗУ:
29 | Отчистить
30 |
31 | Отчистить
32 | Остановить
33 |
34 | Завершить
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 | #FFFFFF
8 | #616161
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TaskManager
3 |
4 | Blacklist:
5 | All apps:
6 | Remove
7 | Block
8 | Last open date: %s
9 | Blacklist application is running!
10 | Application with name %s is running but it\'s not should be
11 | Before cleaning: %fG
12 | After cleaning: %fG
13 | Apps
14 | Blacklist
15 | Terminal
16 |
17 | Priority: %s
18 | Enabled: %s
19 | Uid: %s
20 | Target sdk: %s
21 | Description: %s
22 |
23 | Priority
24 | Note
25 |
26 | All memory:
27 | Free memory:
28 | Clear all
29 |
30 | Clear
31 | Stop
32 |
33 | Kill
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/test/java/com/simonk/projects/taskmanager/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.simonk.projects.taskmanager;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.4.0'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 30 13:58:59 MSK 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshots/apps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/screenshots/apps.png
--------------------------------------------------------------------------------
/screenshots/blacklist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/screenshots/blacklist.png
--------------------------------------------------------------------------------
/screenshots/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonKab/Android-Process-Manager/e5c4ae3626beb2080bd49dc09c23d06bfdabb0c1/screenshots/terminal.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------