From e451bf0c1a73f51377933f64aed9011bc1bf9f4e Mon Sep 17 00:00:00 2001 From: darken Date: Mon, 9 Apr 2018 18:52:58 +0200 Subject: [PATCH] v3.0 rewrite --- app/build.gradle | 143 ++++++---- app/proguard-rules.pro | 3 + app/src/main/AndroidManifest.xml | 25 +- .../rootvalidator/ActivityBinderModule.java | 23 ++ .../rootvalidator/AndroidModule.java | 29 +++ .../java/eu/thedarken/rootvalidator/App.java | 45 ++++ .../thedarken/rootvalidator/AppComponent.java | 32 +++ .../eu/thedarken/rootvalidator/AppModule.java | 26 ++ .../eu/thedarken/rootvalidator/IAPHelper.java | 118 +++++++++ .../thedarken/rootvalidator/MainActivity.java | 80 ------ .../eu/thedarken/rootvalidator/RVAdapter.java | 116 --------- .../eu/thedarken/rootvalidator/RVLoader.java | 109 -------- .../rootvalidator/ValidatorFragment.java | 245 ------------------ .../rootvalidator/main/core/Criterion.java | 31 +++ .../rootvalidator/main/core/Outcome.java | 6 + .../rootvalidator/main/core/Result.java | 9 + .../rootvalidator/main/core/Settings.java | 24 ++ .../rootvalidator/main/core/TestResult.java | 19 ++ .../rootvalidator/main/core/TestSuite.java | 28 ++ .../main/core/applets/AppletBinary.java | 41 +++ .../main/core/applets/AppletBinaryResult.java | 88 +++++++ .../core/applets/AppletBinaryTestSuite.java | 156 +++++++++++ .../main/core/root/RootResult.java | 115 ++++++++ .../main/core/root/RootTestSuite.java | 61 +++++ .../main/core/selinux/SELinuxResult.java | 79 ++++++ .../main/core/selinux/SELinuxTestSuite.java | 37 +++ .../main/core/suapp/SuperUserApp.java | 49 ++++ .../main/core/suapp/SuperUserAppResult.java | 79 ++++++ .../core/suapp/SuperUserAppTestSuite.java | 189 ++++++++++++++ .../main/core/suapp/SuperUserBinary.java | 49 ++++ .../core/suapp/SuperUserBinaryResult.java | 94 +++++++ .../{ => main}/ui/AboutDialog.java | 44 ++-- .../ui/ColorFramedCircleDrawable.java | 2 +- .../rootvalidator/main/ui/MainActivity.java | 55 ++++ .../main/ui/MainActivityComponent.java | 45 ++++ .../main/ui/MainActivityPresenter.java | 16 ++ .../main/ui/validator/TestResultAdapter.java | 125 +++++++++ .../main/ui/validator/ValidatorComponent.java | 25 ++ .../main/ui/validator/ValidatorFragment.java | 245 ++++++++++++++++++ .../main/ui/validator/ValidatorPresenter.java | 134 ++++++++++ .../thedarken/rootvalidator/tests/ATest.java | 24 -- .../eu/thedarken/rootvalidator/tests/BP.java | 29 --- .../thedarken/rootvalidator/tests/Result.java | 17 -- .../rootvalidator/tests/TestInfo.java | 79 ------ .../rootvalidator/tests/busybox/BusyBox.java | 149 ----------- .../tests/busybox/BusyBoxInfo.java | 115 -------- .../tests/busybox/BusyBoxTest.java | 175 ------------- .../rootvalidator/tests/root/RootInfo.java | 105 -------- .../rootvalidator/tests/root/RootTest.java | 74 ------ .../tests/selinux/SELinuxInfo.java | 99 ------- .../tests/selinux/SELinuxTest.java | 81 ------ .../rootvalidator/tests/suapp/SuApp.java | 105 -------- .../rootvalidator/tests/suapp/SuAppInfo.java | 90 ------- .../tests/suapp/SuperUserAppTest.java | 85 ------ .../tests/subinary/SuBinary.java | 135 ---------- .../tests/subinary/SuBinaryInfo.java | 104 -------- .../tests/subinary/SuBinaryTest.java | 199 -------------- .../tools/BugsnagErrorHandler.java | 27 ++ .../rootvalidator/tools/BugsnagTree.java | 61 +++++ .../eu/thedarken/rootvalidator/tools/Cmd.java | 223 ---------------- .../thedarken/rootvalidator/tools/Logy.java | 62 ----- .../rootvalidator/tools/ShareHelper.java | 9 +- .../rootvalidator/tools/StringUtils.java | 44 ++++ .../rootvalidator/ui/EmptyRecyclerView.java | 73 ------ .../rootvalidator/ui/ShareDialog.java | 81 ------ .../res/drawable-hdpi/ic_stars_white_24dp.png | Bin 0 -> 619 bytes .../res/drawable-mdpi/ic_stars_white_24dp.png | Bin 0 -> 406 bytes .../drawable-xhdpi/ic_stars_white_24dp.png | Bin 0 -> 811 bytes .../drawable-xxhdpi/ic_stars_white_24dp.png | Bin 0 -> 1230 bytes .../drawable-xxxhdpi/ic_stars_white_24dp.png | Bin 0 -> 1690 bytes .../res/drawable/ic_indicator_negative.xml | 5 + .../res/drawable/ic_indicator_neutral.xml | 5 + .../res/drawable/ic_indicator_positive.xml | 5 + .../layout/activity_mainactivity_layout.xml | 28 +- app/src/main/res/layout/adapter_info_line.xml | 17 +- .../main/res/layout/fragment_dialog_about.xml | 22 -- .../main/res/layout/fragment_dialog_share.xml | 54 ---- .../res/layout/fragment_validator_layout.xml | 99 +++++-- app/src/main/res/layout/view_empty_start.xml | 29 --- .../main/res/layout/view_empty_working.xml | 23 -- .../{view_bp.xml => view_testpoint.xml} | 17 +- app/src/main/res/menu/validator_menu.xml | 6 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../ic_launcher.png | Bin .../main/res/mipmap-hdpi/ic_launcher_back.png | Bin 0 -> 860 bytes .../res/mipmap-hdpi/ic_launcher_frontb.png | Bin 0 -> 15246 bytes .../ic_launcher.png | Bin .../main/res/mipmap-mdpi/ic_launcher_back.png | Bin 0 -> 467 bytes .../res/mipmap-mdpi/ic_launcher_frontb.png | Bin 0 -> 7978 bytes .../ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_back.png | Bin 0 -> 1325 bytes .../res/mipmap-xhdpi/ic_launcher_frontb.png | Bin 0 -> 24131 bytes .../ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_back.png | Bin 0 -> 2576 bytes .../res/mipmap-xxhdpi/ic_launcher_frontb.png | Bin 0 -> 47118 bytes .../ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_back.png | Bin 0 -> 4240 bytes .../res/mipmap-xxxhdpi/ic_launcher_frontb.png | Bin 0 -> 77897 bytes app/src/main/res/values/strings.xml | 37 +++ build.gradle | 9 +- gradle/wrapper/gradle-wrapper.properties | 4 +- privacy_policy_for_gplay.md | 16 +- 103 files changed, 2500 insertions(+), 2970 deletions(-) create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/ActivityBinderModule.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/AndroidModule.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/App.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/AppComponent.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/AppModule.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/IAPHelper.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/MainActivity.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/RVAdapter.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/RVLoader.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/ValidatorFragment.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/Criterion.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/Outcome.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/Result.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/Settings.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/TestResult.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/TestSuite.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinary.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryResult.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryTestSuite.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootResult.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootTestSuite.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxResult.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxTestSuite.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserApp.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppResult.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppTestSuite.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinary.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinaryResult.java rename app/src/main/java/eu/thedarken/rootvalidator/{ => main}/ui/AboutDialog.java (51%) rename app/src/main/java/eu/thedarken/rootvalidator/{ => main}/ui/ColorFramedCircleDrawable.java (97%) create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivity.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityComponent.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityPresenter.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/TestResultAdapter.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorComponent.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorFragment.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorPresenter.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/ATest.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/BP.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/Result.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/TestInfo.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBox.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxInfo.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxTest.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootInfo.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootTest.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxInfo.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxTest.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuApp.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuAppInfo.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuperUserAppTest.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinary.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryInfo.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryTest.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagErrorHandler.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagTree.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tools/Cmd.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tools/Logy.java create mode 100644 app/src/main/java/eu/thedarken/rootvalidator/tools/StringUtils.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/ui/EmptyRecyclerView.java delete mode 100644 app/src/main/java/eu/thedarken/rootvalidator/ui/ShareDialog.java create mode 100644 app/src/main/res/drawable-hdpi/ic_stars_white_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stars_white_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stars_white_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stars_white_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_stars_white_24dp.png create mode 100644 app/src/main/res/drawable/ic_indicator_negative.xml create mode 100644 app/src/main/res/drawable/ic_indicator_neutral.xml create mode 100644 app/src/main/res/drawable/ic_indicator_positive.xml delete mode 100644 app/src/main/res/layout/fragment_dialog_share.xml delete mode 100644 app/src/main/res/layout/view_empty_start.xml delete mode 100644 app/src/main/res/layout/view_empty_working.xml rename app/src/main/res/layout/{view_bp.xml => view_testpoint.xml} (77%) create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename app/src/main/res/{drawable-hdpi => mipmap-hdpi}/ic_launcher.png (100%) create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_back.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_frontb.png rename app/src/main/res/{drawable-mdpi => mipmap-mdpi}/ic_launcher.png (100%) create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_back.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_frontb.png rename app/src/main/res/{drawable-xhdpi => mipmap-xhdpi}/ic_launcher.png (100%) create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_back.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_frontb.png rename app/src/main/res/{drawable-xxhdpi => mipmap-xxhdpi}/ic_launcher.png (100%) create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_back.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_frontb.png rename app/src/main/res/{drawable-xxxhdpi => mipmap-xxxhdpi}/ic_launcher.png (100%) create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_back.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_frontb.png diff --git a/app/build.gradle b/app/build.gradle index bf9f365..8f6bae4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,78 +1,125 @@ -buildscript { - repositories { - maven { url 'https://maven.fabric.io/public' } - } +apply plugin: 'com.android.application' +apply plugin: 'com.bugsnag.android.gradle' - dependencies { - classpath 'io.fabric.tools:gradle:1.21.2' - } +def gitSha() { + def p = 'git rev-parse --short HEAD'.execute([], project.rootDir) + p.waitFor() + if (p.exitValue() != 0) return "" + return p.text.trim() } -apply plugin: 'com.android.application' -apply plugin: 'io.fabric' -repositories { - maven { url 'https://maven.fabric.io/public' } +bugsnag { + overwrite true } +def versionMajor = 3 +def versionMinor = 0 +def versionTag = "" -def gitSha() { - return 'git rev-parse --short HEAD'.execute().text.trim() -} +android { + def signingPropFile = new File(System.properties['user.home'], ".appconfig/rootvalidator/signing.properties") + if (signingPropFile.canRead()) { + Properties signingProps = new Properties() + signingProps.load(new FileInputStream(signingPropFile)) -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ + signingConfigs { + release { + storeFile new File(signingProps['release.storePath']) + keyAlias signingProps['release.keyAlias'] + storePassword signingProps['release.storePassword'] + keyPassword signingProps['release.keyPassword'] + } + } + android { + buildTypes { + release { + signingConfig signingConfigs.release + } + } + } + } -//def buildTime() { -// def df = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss'Z'") -// df.setTimeZone(TimeZone.getTimeZone("GMT+1")) -// return df.format(new Date()) -//} + compileSdkVersion 27 + buildToolsVersion "27.0.3" -android { - compileSdkVersion 23 - buildToolsVersion '23.0.2' + Properties bugsnagProps = new Properties() + def bugsnagPropsFile = new File(System.properties['user.home'], ".appconfig/rootvalidator/bugsnag.properties") + if (bugsnagPropsFile.canRead()) bugsnagProps.load(new FileInputStream(bugsnagPropsFile)) defaultConfig { applicationId "eu.thedarken.rootvalidator" - minSdkVersion 10 - targetSdkVersion 23 - versionCode 26 - versionName "2.1.2" + minSdkVersion 16 + targetSdkVersion 27 + versionCode versionMajor * 10000 + versionMinor + versionName "${versionMajor}.${versionMinor}${versionTag}" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + manifestPlaceholders = [apikey_bugsnag: ""] } + + lintOptions { + abortOnError false + } + buildTypes { + debug { + minifyEnabled false + useProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } release { minifyEnabled true - shrinkResources true + useProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + manifestPlaceholders = [apikey_bugsnag: bugsnagProps.getProperty("bugsnag.apikey", "")] } applicationVariants.all { variant -> - if (variant.buildType.name == "release") { + if (variant.buildType.name != "debug") { variant.outputs.each { output -> - def file = output.outputFile - output.outputFile = new File(file.parent, "RootValidator-v" + defaultConfig.versionName + "-" + defaultConfig.versionCode + "-" + gitSha() + ".apk") + output.outputFileName = applicationId + "-v" + defaultConfig.versionName + "(" + defaultConfig.versionCode + ")-" + variant.buildType.name.toUpperCase() + "-" + gitSha() + ".apk" } } } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } ext { - supportLibVersion = '23.1.1' + supportLibVersion = '27.1.1' + daggerVersion = '2.13' } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:support-annotations:${supportLibVersion}" - compile "com.android.support:appcompat-v7:${supportLibVersion}" - compile "com.android.support:recyclerview-v7:${supportLibVersion}" - compile "com.android.support:cardview-v7:${supportLibVersion}" - compile 'com.melnykov:floatingactionbutton:1.3.0' - compile 'de.hdodenhof:circleimageview:2.0.0' - compile 'com.google.android.gms:play-services-ads:8.4.0' - compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') { - transitive = true; - } + implementation "com.android.support:support-annotations:${supportLibVersion}" + implementation "com.android.support:appcompat-v7:${supportLibVersion}" + implementation "com.android.support:recyclerview-v7:${supportLibVersion}" + implementation "com.android.support:cardview-v7:${supportLibVersion}" + implementation "com.android.support:design:${supportLibVersion}" + + implementation 'eu.darken.rxshell:core:1.0.4' + implementation 'eu.darken.rxshell:root:1.0.4' + implementation 'eu.darken.mvpbakery:library:0.4.0' + + implementation 'com.bugsnag:bugsnag-android:4.3.1' + + implementation 'com.jakewharton.timber:timber:4.6.1' + compileOnly 'org.jetbrains:annotations:15.0' + + //Dagger + implementation "com.google.dagger:dagger:${daggerVersion}" + annotationProcessor "com.google.dagger:dagger-compiler:${daggerVersion}" + androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:${daggerVersion}" + implementation "com.google.dagger:dagger-android:${daggerVersion}" + implementation "com.google.dagger:dagger-android-support:${daggerVersion}" + + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.1.9' + + implementation 'com.android.billingclient:billing:1.0' + implementation 'com.jakewharton:butterknife:8.8.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ce61f49..87c396e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -17,3 +17,6 @@ #} -keepattributes SourceFile,LineNumberTable + +# https://github.com/google/dagger/issues/645 +-dontwarn com.google.errorprone.annotations.* \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a61e076..bf683ff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,41 +6,30 @@ - - - - - - - - + diff --git a/app/src/main/java/eu/thedarken/rootvalidator/ActivityBinderModule.java b/app/src/main/java/eu/thedarken/rootvalidator/ActivityBinderModule.java new file mode 100644 index 0000000..fda075e --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/ActivityBinderModule.java @@ -0,0 +1,23 @@ +package eu.thedarken.rootvalidator; + +import android.app.Activity; + +import dagger.Binds; +import dagger.Module; +import dagger.android.ActivityKey; +import dagger.android.AndroidInjector; +import dagger.multibindings.IntoMap; +import eu.thedarken.rootvalidator.main.ui.MainActivity; +import eu.thedarken.rootvalidator.main.ui.MainActivityComponent; + +@Module(subcomponents = { + MainActivityComponent.class +}) +abstract class ActivityBinderModule { + + @Binds + @IntoMap + @ActivityKey(MainActivity.class) + abstract AndroidInjector.Factory main(MainActivityComponent.Builder impl); + +} \ No newline at end of file diff --git a/app/src/main/java/eu/thedarken/rootvalidator/AndroidModule.java b/app/src/main/java/eu/thedarken/rootvalidator/AndroidModule.java new file mode 100644 index 0000000..c2d5ebd --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/AndroidModule.java @@ -0,0 +1,29 @@ +package eu.thedarken.rootvalidator; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import dagger.Module; +import dagger.Provides; + + +@Module +class AndroidModule { + private final App app; + + AndroidModule(App app) {this.app = app;} + + @Provides + @AppComponent.Scope + Context context() { + return app.getApplicationContext(); + } + + @Provides + @AppComponent.Scope + SharedPreferences defaultPreferences(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/App.java b/app/src/main/java/eu/thedarken/rootvalidator/App.java new file mode 100644 index 0000000..c43d3f9 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/App.java @@ -0,0 +1,45 @@ +package eu.thedarken.rootvalidator; + +import android.app.Activity; +import android.app.Application; + +import com.bugsnag.android.Bugsnag; +import com.bugsnag.android.Client; + +import javax.inject.Inject; + +import eu.darken.mvpbakery.injection.ComponentSource; +import eu.darken.mvpbakery.injection.ManualInjector; +import eu.darken.mvpbakery.injection.activity.HasManualActivityInjector; +import eu.thedarken.rootvalidator.tools.BugsnagErrorHandler; +import eu.thedarken.rootvalidator.tools.BugsnagTree; +import timber.log.Timber; + +public class App extends Application implements HasManualActivityInjector { + + @Inject BugsnagTree bugsnagTree; + @Inject BugsnagErrorHandler errorHandler; + @Inject AppComponent appComponent; + @Inject ComponentSource activityInjector; + + @Override + public void onCreate() { + super.onCreate(); + if (BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree()); + DaggerAppComponent.builder() + .androidModule(new AndroidModule(this)) + .build() + .injectMembers(this); + + Timber.plant(bugsnagTree); + Client bugsnagClient = Bugsnag.init(this); + bugsnagClient.beforeNotify(errorHandler); + + Timber.d("Bugsnag setup done!"); + } + + @Override + public ManualInjector activityInjector() { + return activityInjector; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/thedarken/rootvalidator/AppComponent.java b/app/src/main/java/eu/thedarken/rootvalidator/AppComponent.java new file mode 100644 index 0000000..8e4cdc3 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/AppComponent.java @@ -0,0 +1,32 @@ +package eu.thedarken.rootvalidator; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import dagger.Component; +import dagger.MembersInjector; + + +@AppComponent.Scope +@Component(modules = { + ActivityBinderModule.class, + AndroidModule.class, + AppModule.class +}) +public interface AppComponent extends MembersInjector { + void inject(App app); + + @Component.Builder + interface Builder { + Builder androidModule(AndroidModule module); + + AppComponent build(); + } + + @Documented + @javax.inject.Scope + @Retention(RetentionPolicy.RUNTIME) + @interface Scope { + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/AppModule.java b/app/src/main/java/eu/thedarken/rootvalidator/AppModule.java new file mode 100644 index 0000000..dff745a --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/AppModule.java @@ -0,0 +1,26 @@ +package eu.thedarken.rootvalidator; + +import java.util.Arrays; +import java.util.List; + +import dagger.Module; +import dagger.Provides; +import eu.thedarken.rootvalidator.main.core.TestSuite; +import eu.thedarken.rootvalidator.main.core.applets.AppletBinaryTestSuite; +import eu.thedarken.rootvalidator.main.core.root.RootTestSuite; +import eu.thedarken.rootvalidator.main.core.selinux.SELinuxTestSuite; +import eu.thedarken.rootvalidator.main.core.suapp.SuperUserAppTestSuite; + +@Module +public class AppModule { + @Provides + List actionModules( + AppletBinaryTestSuite boxTest, + RootTestSuite rootTest, + SuperUserAppTestSuite suAppTest, + SELinuxTestSuite seLinuxTest + ) { + return Arrays.asList(rootTest, suAppTest, boxTest, seLinuxTest); + } + +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/IAPHelper.java b/app/src/main/java/eu/thedarken/rootvalidator/IAPHelper.java new file mode 100644 index 0000000..91e0de2 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/IAPHelper.java @@ -0,0 +1,118 @@ +package eu.thedarken.rootvalidator; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.BehaviorSubject; +import timber.log.Timber; + +@AppComponent.Scope +public class IAPHelper implements PurchasesUpdatedListener, BillingClientStateListener { + static final String SKU_UPGRADE_DONATE = "upgrade.donate"; + private final BehaviorSubject> upgradesPublisher = BehaviorSubject.createDefault(new ArrayList<>()); + private final BillingClient billingClient; + + public static class Upgrade { + enum Type { + DONATE, UNKNOWN + } + + private final Purchase purchase; + private final Type type; + + Upgrade(Purchase purchase) { + this.purchase = purchase; + if (purchase.getSku().endsWith(SKU_UPGRADE_DONATE)) type = Type.DONATE; + else type = Type.UNKNOWN; + } + + public Type getType() { + return type; + } + } + + @Inject + public IAPHelper(Context context) { + billingClient = BillingClient.newBuilder(context).setListener(this).build(); + billingClient.startConnection(this); + } + + @Override + public void onBillingSetupFinished(int responseCode) { + Timber.d("onBillingSetupFinished(responseCode=%d)", responseCode); + if (BillingClient.BillingResponse.OK == responseCode) { + final Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP); + Timber.d("queryPurchases(): code=%d, purchases=%s", purchasesResult.getResponseCode(), purchasesResult.getPurchasesList()); + onPurchasesUpdated(purchasesResult.getResponseCode(), purchasesResult.getPurchasesList()); + } + } + + @Override + public void onBillingServiceDisconnected() { + Timber.d("onBillingServiceDisconnected()"); + } + + @Override + public void onPurchasesUpdated(int responseCode, @Nullable List purchases) { + Timber.d("onPurchasesUpdated(responseCode=%d, purchases=%s)", responseCode, purchases); + if (purchases != null) notifyOfPurchases(purchases); + } + + private void notifyOfPurchases(List purchases) { + Timber.d("notifyOfPurchases(%s)", purchases); + List upgrades = new ArrayList<>(); + for (Purchase p : purchases) upgrades.add(new Upgrade(p)); + upgradesPublisher.onNext(upgrades); + } + + public void check() { + Single.create((SingleOnSubscribe) e -> e.onSuccess(billingClient.queryPurchases(BillingClient.SkuType.INAPP))) + .subscribeOn(Schedulers.io()) + .filter(r -> r.getResponseCode() == 0 && r.getPurchasesList() != null) + .map(Purchase.PurchasesResult::getPurchasesList) + .subscribe(this::notifyOfPurchases, Timber::e); + } + + public Observable isPremiumVersion() { + if (BuildConfig.DEBUG) { + return upgradesPublisher.map(egal -> !true); + } + return upgradesPublisher.map(upgrades -> { + boolean proVersion = false; + for (Upgrade upgrade : upgrades) { + if (upgrade.getType().equals(Upgrade.Type.DONATE)) { + proVersion = true; + break; + } + } + return proVersion; + }); + } + + private BillingFlowParams buildSKUDonateUpgrade() { + return BillingFlowParams.newBuilder() + .setSku(SKU_UPGRADE_DONATE) + .setType(BillingClient.SkuType.INAPP) + .build(); + } + + public void donate(Activity activity) { + billingClient.launchBillingFlow(activity, buildSKUDonateUpgrade()); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/MainActivity.java b/app/src/main/java/eu/thedarken/rootvalidator/MainActivity.java deleted file mode 100644 index d31bfe8..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/MainActivity.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.FrameLayout; - -import com.crashlytics.android.Crashlytics; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdView; - -import java.io.File; - -import io.fabric.sdk.android.Fabric; - -public class MainActivity extends AppCompatActivity { - private Fragment mFragment; - private AdView mAdView; - private FrameLayout mAdContainer; - private static final String FILE_NO_ADS = "no_ads"; - - public String getFragmentClass() { - return ValidatorFragment.class.getName(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Fabric.with(this, new Crashlytics()); - setContentView(R.layout.activity_mainactivity_layout); - mFragment = getSupportFragmentManager().findFragmentByTag(getFragmentClass()); - if (mFragment == null) { - mFragment = Fragment.instantiate(this, getFragmentClass()); - getSupportFragmentManager().beginTransaction().replace(R.id.content, mFragment, getFragmentClass()).commit(); - } - mAdContainer = (FrameLayout) findViewById(R.id.fl_ad_container); - File noAdsFile = new File(getExternalFilesDir(null), FILE_NO_ADS); - if (!noAdsFile.exists()) { - mAdContainer.setVisibility(View.VISIBLE); - mAdView = (AdView) findViewById(R.id.adv_banner); - AdRequest adRequest = new AdRequest.Builder() - .addTestDevice(AdRequest.DEVICE_ID_EMULATOR) - .addTestDevice("C34CF836138E576F0C3CC8BBF6B19388") - .addTestDevice("A6A278A2F9CF28BD949FC2265AEAE62F") - .build(); - mAdView.loadAd(adRequest); - } else { - mAdContainer.setVisibility(View.GONE); - } - } - - @Override - protected void onResume() { - if (mAdView != null) - mAdView.resume(); - super.onResume(); - } - - @Override - protected void onPause() { - if (mAdView != null) - mAdView.pause(); - super.onPause(); - } - - @Override - protected void onDestroy() { - if (mAdView != null) - mAdView.destroy(); - super.onDestroy(); - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/thedarken/rootvalidator/RVAdapter.java b/app/src/main/java/eu/thedarken/rootvalidator/RVAdapter.java deleted file mode 100644 index 81a6bee..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/RVAdapter.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.ArrayList; - -import de.hdodenhof.circleimageview.CircleImageView; -import eu.thedarken.rootvalidator.tests.BP; -import eu.thedarken.rootvalidator.tests.TestInfo; - - -public class RVAdapter extends RecyclerView.Adapter { - private final ArrayList mData; - private static final int NORMAL_TYPE = 0; - - public RVAdapter(ArrayList data) { - mData = data; - } - - public ArrayList getData() { - return mData; - } - - @Override - public int getItemViewType(int position) { - return NORMAL_TYPE; - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case NORMAL_TYPE: { - View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_info_line, parent, false); - return new RVInfoHolder(v); - } - default: { - return null; - } - } - } - - @Override - public int getItemCount() { - return mData.size(); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (getItemViewType(position) == NORMAL_TYPE) { - TestInfo info = mData.get(position); - RVInfoHolder infoHolder = (RVInfoHolder) holder; - infoHolder.inject(info); - } - } - - class RVInfoHolder extends RecyclerView.ViewHolder { - private CircleImageView mCircularImageView; - private TextView mTitle; - private LinearLayout mPointsContainer; - private ImageView mExpandIcon; - - public RVInfoHolder(View itemView) { - super(itemView); - mCircularImageView = (CircleImageView) itemView.findViewById(R.id.civ_icon); - mTitle = (TextView) itemView.findViewById(R.id.tv_title); - mPointsContainer = (LinearLayout) itemView.findViewById(R.id.ll_points); - mExpandIcon = (ImageView) itemView.findViewById(R.id.iv_expand_button); - } - - private Context getContext() { - return itemView.getContext(); - } - - public void inject(TestInfo info) { - mCircularImageView.setImageDrawable(info.getIcon(getContext())); - mTitle.setText(info.getPrimaryInfo(getContext())); - if (info.getCriterias(getContext()).isEmpty()) { - mExpandIcon.setVisibility(View.GONE); - mPointsContainer.setVisibility(View.GONE); - itemView.setOnClickListener(null); - } else { - mExpandIcon.setVisibility(View.VISIBLE); - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mPointsContainer.setVisibility(mPointsContainer.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); - mExpandIcon.setImageResource(mPointsContainer.getVisibility() == View.VISIBLE ? R.drawable.ic_expand_less_white_24dp : R.drawable.ic_expand_more_white_24dp); - } - }); - mPointsContainer.removeAllViews(); - for (BP bp : info.getCriterias(getContext())) { - View bpLine = LayoutInflater.from(getContext()).inflate(R.layout.view_bp, mPointsContainer, false); - TextView text = (TextView) bpLine.findViewById(R.id.tv_bp_text); - CircleImageView point = (CircleImageView) bpLine.findViewById(R.id.civ_bp_icon); - text.setText(bp.getText()); - point.setImageDrawable(bp.getPoint()); - mPointsContainer.addView(bpLine); - } - } - } - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/RVLoader.java b/app/src/main/java/eu/thedarken/rootvalidator/RVLoader.java deleted file mode 100644 index cb8e190..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/RVLoader.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator; - -import android.content.Context; -import android.support.v4.content.AsyncTaskLoader; - -import java.util.ArrayList; - -import eu.thedarken.rootvalidator.tests.TestInfo; -import eu.thedarken.rootvalidator.tests.busybox.BusyBoxInfo; -import eu.thedarken.rootvalidator.tests.busybox.BusyBoxTest; -import eu.thedarken.rootvalidator.tests.root.RootInfo; -import eu.thedarken.rootvalidator.tests.root.RootTest; -import eu.thedarken.rootvalidator.tests.selinux.SELinuxInfo; -import eu.thedarken.rootvalidator.tests.selinux.SELinuxTest; -import eu.thedarken.rootvalidator.tests.suapp.SuAppInfo; -import eu.thedarken.rootvalidator.tests.suapp.SuperUserAppTest; -import eu.thedarken.rootvalidator.tests.subinary.SuBinaryInfo; -import eu.thedarken.rootvalidator.tests.subinary.SuBinaryTest; -import eu.thedarken.rootvalidator.tools.ApiHelper; -import eu.thedarken.rootvalidator.tools.Logy; - -public class RVLoader extends AsyncTaskLoader> { - public static final int ID = 5; - private static final String TAG = "RV:RVLoader"; - private ArrayList mData; - - public RVLoader(Context context) { - super(context); - } - - @Override - public void deliverResult(ArrayList data) { - - if (isReset()) { - if (mData != null) - onReleaseResources(mData); - } - ArrayList oldData = mData; - mData = data; - - if (isStarted()) { - super.deliverResult(data); - } - - if (oldData != null) - onReleaseResources(oldData); - } - - @Override - protected void onStartLoading() { - forceLoad(); - } - - @Override - protected void onStopLoading() { - cancelLoad(); - } - - @Override - public void onCanceled(ArrayList data) { - onReleaseResources(data); - } - - @Override - protected void onReset() { - onStopLoading(); - - if (mData != null) { - onReleaseResources(mData); - mData = null; - } - } - - protected void onReleaseResources(ArrayList data) { - } - - @Override - public ArrayList loadInBackground() { - Logy.d(TAG, "loadInBackground start..."); - long dur = System.currentTimeMillis(); - ArrayList result = new ArrayList<>(); - - RootInfo resultRoot = new RootTest(getContext()).test(); - result.add(resultRoot); - - SuBinaryInfo resultSuBinaryTest = new SuBinaryTest(getContext()).test(); - result.add(resultSuBinaryTest); - - SuAppInfo resultSuAppTest = new SuperUserAppTest(getContext(), resultSuBinaryTest).test(); - result.add(resultSuAppTest); - - BusyBoxInfo resultBusyBoxTest = new BusyBoxTest(getContext()).test(); - result.add(resultBusyBoxTest); - - SELinuxInfo resultSeLinuxTest = new SELinuxTest(getContext()).test(); - if (ApiHelper.hasJellyBeanMR1()) - result.add(resultSeLinuxTest); - - Logy.d(TAG, "loadInBackground done:" + (System.currentTimeMillis() - dur)); - return result; - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/thedarken/rootvalidator/ValidatorFragment.java b/app/src/main/java/eu/thedarken/rootvalidator/ValidatorFragment.java deleted file mode 100644 index cdc3e04..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/ValidatorFragment.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.AnticipateOvershootInterpolator; -import android.view.animation.LayoutAnimationController; -import android.view.animation.TranslateAnimation; - -import com.melnykov.fab.FloatingActionButton; - -import java.util.ArrayList; - -import eu.thedarken.rootvalidator.tests.TestInfo; -import eu.thedarken.rootvalidator.tools.Logy; -import eu.thedarken.rootvalidator.tools.ShareHelper; -import eu.thedarken.rootvalidator.ui.AboutDialog; -import eu.thedarken.rootvalidator.ui.EmptyRecyclerView; -import eu.thedarken.rootvalidator.ui.ShareDialog; - - -public class ValidatorFragment extends Fragment implements LoaderManager.LoaderCallbacks>, ShareDialog.ShareCallback { - private static final String TAG = "RV:ValidatorFragment"; - private FloatingActionButton mFab; - private EmptyRecyclerView mRecyclerView; - private View mEmptyStartView; - private View mEmptyWorkingView; - private ViewGroup mListContainer; - private RVAdapter mAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) { - if (savedInstanceState != null) { - if (savedInstanceState.containsKey("data")) { - ArrayList theData = savedInstanceState.getParcelableArrayList("data"); - mAdapter = new RVAdapter(theData); - - } - } - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.fragment_validator_layout, container, false); - mListContainer = (ViewGroup) layout.findViewById(R.id.ll_list_container); - mFab = (FloatingActionButton) layout.findViewById(R.id.fab); - mRecyclerView = (EmptyRecyclerView) layout.findViewById(R.id.recyclerview); - mEmptyStartView = inflater.inflate(R.layout.view_empty_start, mListContainer, false); - mEmptyWorkingView = inflater.inflate(R.layout.view_empty_working, mListContainer, false); - return layout; - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - AnimationSet set = new AnimationSet(true); - Animation fadeIn = new AlphaAnimation(0.0f, 1.0f); - fadeIn.setDuration(350); - set.addAnimation(fadeIn); - Animation dropDown = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f, - Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f - ); - dropDown.setDuration(400); - set.addAnimation(dropDown); - LayoutAnimationController controller = new LayoutAnimationController(set, 0.2f); - mRecyclerView.setLayoutAnimation(controller); - - mFab.attachToRecyclerView(mRecyclerView); - mFab.setVisibility(View.INVISIBLE); - mEmptyStartView.setVisibility(View.GONE); - mEmptyWorkingView.setVisibility(View.GONE); - mListContainer.addView(mEmptyStartView); - mListContainer.addView(mEmptyWorkingView); - mRecyclerView.setEmptyView(mEmptyStartView); - mRecyclerView.setItemAnimator(new DefaultItemAnimator()); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setHasOptionsMenu(true); - setFABShare(mAdapter != null); - animateFAB(false); - if (mAdapter != null) { - mRecyclerView.setAdapter(mAdapter); - mAdapter.notifyDataSetChanged(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - if (mAdapter != null && !mAdapter.getData().isEmpty()) { - outState.putParcelableArrayList("data", mAdapter.getData()); - } - super.onSaveInstanceState(outState); - } - - private void animateFAB(boolean out) { - Animation animation; - if (out) { - animation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1.0f); - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { - mFab.setVisibility(View.INVISIBLE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - }); - } else { - animation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0); - - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - mFab.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animation animation) { - - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - }); - } - animation.setDuration(600); - animation.setInterpolator(new AnticipateOvershootInterpolator(1.2f)); - mFab.startAnimation(animation); - } - - private void setFABShare(boolean share) { - if (share) { - mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_share_white_24dp)); - mFab.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ShareDialog dialog = ShareDialog.instantiate(ValidatorFragment.this); - dialog.showDialog(getActivity()); - } - }); - } else { - mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_root)); - mFab.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mRecyclerView.setEmptyView(mEmptyWorkingView); - getLoaderManager().restartLoader(RVLoader.ID, null, ValidatorFragment.this); - animateFAB(true); - } - }); - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.validator_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public Loader> onCreateLoader(int id, Bundle args) { - Logy.d(TAG, "onCreateLoader()"); - return new RVLoader(getActivity()); - } - - @Override - public void onLoadFinished(Loader> loader, ArrayList data) { - Logy.d(TAG, "onLoadFinished(" + data.size() + ")"); - mAdapter = new RVAdapter(data); - mRecyclerView.setAdapter(mAdapter); - mAdapter.notifyDataSetChanged(); - - setFABShare(mAdapter != null); - animateFAB(false); - } - - @Override - public void onLoaderReset(Loader> loader) { - Logy.d(TAG, "onLoaderReset()"); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.about) { - AboutDialog dialog = AboutDialog.instantiate(); - dialog.showDialog(getActivity()); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onShare(boolean extended) { - ArrayList toShare = new ArrayList<>(); - for (TestInfo testInfo : mAdapter.getData()) { - toShare.addAll(testInfo.getDetails(getActivity())); - if (extended) { - toShare.add("RAW:"); - toShare.addAll(testInfo.getRaw()); - } - toShare.add("##### " + testInfo.getTitle() + " #####\n"); - } - ShareHelper.share(getActivity(), "RootValidator Results", toShare); - } - -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/Criterion.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Criterion.java new file mode 100644 index 0000000..d2595e0 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Criterion.java @@ -0,0 +1,31 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core; + +import android.content.Context; + +public class Criterion implements Result { + + private final Outcome outcome; + private final String description; + + public Criterion(Outcome type, String description) { + this.outcome = type; + this.description = description; + } + + @Override + public Outcome getOutcome() { + return outcome; + } + + @Override + public String getPrimaryInfo(Context context) { + return description; + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/Outcome.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Outcome.java new file mode 100644 index 0000000..90d31af --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Outcome.java @@ -0,0 +1,6 @@ +package eu.thedarken.rootvalidator.main.core; + + +public enum Outcome { + POSITIVE, NEUTRAL, NEGATIVE +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/Result.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Result.java new file mode 100644 index 0000000..cb0ba6a --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Result.java @@ -0,0 +1,9 @@ +package eu.thedarken.rootvalidator.main.core; + +import android.content.Context; + +public interface Result { + Outcome getOutcome(); + + String getPrimaryInfo(Context context); +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/Settings.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Settings.java new file mode 100644 index 0000000..6ce72e8 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/Settings.java @@ -0,0 +1,24 @@ +package eu.thedarken.rootvalidator.main.core; + +import android.content.SharedPreferences; + +import javax.inject.Inject; + +import eu.thedarken.rootvalidator.AppComponent; + +@AppComponent.Scope +public class Settings { + private static final String KEY_LAST_NAG_TIME = "core.upgrade.lastnag"; + private final SharedPreferences preferences; + + @Inject + public Settings(SharedPreferences preferences) {this.preferences = preferences;} + + public long getLastUpgradeNagTime() { + return preferences.getLong(KEY_LAST_NAG_TIME, 0); + } + + public void setLastUpgradeNagTime(long time) { + preferences.edit().putLong(KEY_LAST_NAG_TIME, time).apply(); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/TestResult.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/TestResult.java new file mode 100644 index 0000000..a6f7b7c --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/TestResult.java @@ -0,0 +1,19 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core; + +import android.content.Context; + +import java.util.List; + +public interface TestResult extends Result { + + String getLabel(Context context); + + List getCriteria(Context context); +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/TestSuite.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/TestSuite.java new file mode 100644 index 0000000..6e87502 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/TestSuite.java @@ -0,0 +1,28 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core; + +import android.content.Context; + +import java.util.List; + +import io.reactivex.Single; + +public abstract class TestSuite { + private final Context context; + + public TestSuite(Context context) { + this.context = context; + } + + public Context getContext() { + return context; + } + + public abstract Single> test(); +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinary.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinary.java new file mode 100644 index 0000000..36478ee --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinary.java @@ -0,0 +1,41 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.applets; + +public class AppletBinary { + private final String path; + private final boolean isPrimary; + private final boolean isExecutable; + private final String version; + private final String permission; + private final String owner; + private final String group; + + + public AppletBinary(String path, boolean isPrimary, String permission, String owner, String group, boolean isExecutable, String version) { + this.path = path; + this.isPrimary = isPrimary; + this.permission = permission; + this.owner = owner; + this.group = group; + this.isExecutable = isExecutable; + this.version = version; + } + + public boolean isPrimary() { + return isPrimary; + } + + public String getPath() { + return path; + } + + public String getVersion() { + return version; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryResult.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryResult.java new file mode 100644 index 0000000..d869bd1 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryResult.java @@ -0,0 +1,88 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.applets; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Outcome; +import eu.thedarken.rootvalidator.main.core.TestResult; + +public class AppletBinaryResult implements TestResult { + private final List binaries; + + public AppletBinaryResult(Builder builder) { + this.binaries = builder.binaries; + } + + @Override + public String getLabel(Context context) { + return context.getString(R.string.label_applet_source_test); + } + + public List getBinaries() { + return binaries; + } + + public AppletBinary getPrimary() { + for (AppletBinary bb : binaries) { + if (bb.isPrimary()) return bb; + } + return null; + } + + @Override + public Outcome getOutcome() { + if (getPrimary() == null) { + return Outcome.NEGATIVE; + } else { + return Outcome.POSITIVE; + } + } + + @Override + public String getPrimaryInfo(Context context) { + if (getBinaries().size() == 0) { + return context.getString(R.string.label_no_applet_source); + } else if (getBinaries().size() == 1) { + return context.getString(R.string.label_applet_source_found); + } else { + return context.getString(R.string.label_multiple_applet_sources); + } + } + + @Override + public List getCriteria(Context context) { + List criteria = new ArrayList<>(); + AppletBinary primBB = getPrimary(); + if (primBB != null) { + criteria.add(new Criterion(Outcome.POSITIVE, context.getString(R.string.msg_available_via_path))); + criteria.add(new Criterion(Outcome.POSITIVE, primBB.getPath() + "\n" + primBB.getVersion())); + } else { + criteria.add(new Criterion(Outcome.NEGATIVE, context.getString(R.string.msg_not_available_via_path))); + } + return criteria; + } + + public static class Builder { + private final List binaries = new ArrayList<>(); + + Builder binaries(AppletBinary binary) { + this.binaries.add(binary); + return this; + } + + public AppletBinaryResult build() { + return new AppletBinaryResult(this); + } + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryTestSuite.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryTestSuite.java new file mode 100644 index 0000000..8851175 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/applets/AppletBinaryTestSuite.java @@ -0,0 +1,156 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.applets; + +import android.content.Context; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Inject; + +import eu.darken.rxshell.cmd.Cmd; +import eu.darken.rxshell.cmd.RxCmdShell; +import eu.thedarken.rootvalidator.main.core.TestResult; +import eu.thedarken.rootvalidator.main.core.TestSuite; +import io.reactivex.Single; + +public class AppletBinaryTestSuite extends TestSuite { + + @Inject + public AppletBinaryTestSuite(Context context) { + super(context); + } + + @Override + public Single> test() { + return Single.create(emitter -> { + final AppletBinaryResult.Builder builder = new AppletBinaryResult.Builder(); + final RxCmdShell shell = RxCmdShell.builder().build(); + + Cmd.Result result = Cmd.builder("echo $PATH").execute(shell); + + final HashSet possibleBinaryLocations = new HashSet<>(); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() == 1) { + for (String s : Arrays.asList(result.getOutput().get(0).split(":"))) { + possibleBinaryLocations.add(s + "/busybox"); + possibleBinaryLocations.add(s + "/toybox"); + } + } + + final HashSet binaryLocations = new HashSet<>(); + for (String s : possibleBinaryLocations) { + result = Cmd.builder("ls" + s + "/busybox").execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + binaryLocations.add(s + "/busybox"); + } + result = Cmd.builder("ls" + s + "/toybox").execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + binaryLocations.add(s + "/toybox"); + } + } + + // Wasn't in $PATH, where then? + possibleBinaryLocations.clear(); + possibleBinaryLocations.add("/sbin/busybox"); + possibleBinaryLocations.add("/system/bin/busybox"); + possibleBinaryLocations.add("/system/xbin/busybox"); + possibleBinaryLocations.add("/system/bin/failsafe/busybox"); + possibleBinaryLocations.add("/system/sd/xbin/busybox"); + possibleBinaryLocations.add("/data/local/busybox"); + possibleBinaryLocations.add("/data/local/bin/busybox"); + possibleBinaryLocations.add("/data/local/xbin/busybox"); + + possibleBinaryLocations.add("/sbin/toybox"); + possibleBinaryLocations.add("/system/bin/toybox"); + possibleBinaryLocations.add("/system/xbin/toybox"); + possibleBinaryLocations.add("/system/bin/failsafe/toybox"); + possibleBinaryLocations.add("/system/sd/xbin/toybox"); + possibleBinaryLocations.add("/data/local/toybox"); + possibleBinaryLocations.add("/data/local/bin/toybox"); + possibleBinaryLocations.add("/data/local/xbin/toybox"); + + for (String s : possibleBinaryLocations) { + result = Cmd.builder("ls " + s).execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + binaryLocations.add(s); + } + } + + String primaryBinary = null; + result = Cmd.builder("command -v mount").execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + String mountApplet = result.getOutput().get(0); + result = Cmd.builder("stat -c %N " + mountApplet).execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + if (result.getOutput().get(0).contains("busybox")) { + result = Cmd.builder("command -v busybox").execute(shell); + } else { + result = Cmd.builder("command -v toybox").execute(shell); + } + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + primaryBinary = result.getOutput().get(0); + } + } + } + + for (String path : binaryLocations) { + builder.binaries(checkBinary(path, path.equals(primaryBinary))); + } + + emitter.onSuccess(Collections.singletonList(builder.build())); + }); + } + + private static final Pattern PERMISSION_PATTERN = Pattern.compile("^([\\w-]+)\\s+([\\w]+)\\s+([\\w]+)(?:[\\W\\w]+)$"); + private static final Pattern BUSYBOX_VERSION_PATTERN = Pattern.compile("^(?i:busybox)\\s([\\W\\w]+)\\s(?:\\([\\W\\w]+\\))\\s(?:multi-call binary.)$"); + + + private AppletBinary checkBinary(String path, boolean isPrimary) { + String permission = null; + String owner = null; + String group = null; + boolean isExecutable; + String version = null; + final RxCmdShell shell = RxCmdShell.builder().build(); + + Cmd.Result result = Cmd.builder("ls -l " + path).execute(shell); + + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + Matcher matcher = PERMISSION_PATTERN.matcher(result.getOutput().get(0)); + if (matcher.matches()) { + permission = matcher.group(1); + owner = matcher.group(2); + group = matcher.group(3); + } + } + + result = Cmd.builder(path).execute(shell); + isExecutable = result.getExitCode() == Cmd.ExitCode.OK; + + if (result.getOutput().size() > 0) { + Matcher versionMatcher = BUSYBOX_VERSION_PATTERN.matcher(result.getOutput().get(0)); + if (versionMatcher.matches()) { + version = versionMatcher.group(1); + } + } + + if (version == null) { + result = Cmd.builder(path + " --version").execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + version = result.getOutput().get(0); + } + } + + return new AppletBinary(path, isPrimary, permission, owner, group, isExecutable, version); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootResult.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootResult.java new file mode 100644 index 0000000..496698a --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootResult.java @@ -0,0 +1,115 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.root; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +import eu.darken.rxshell.root.Root; +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Outcome; +import eu.thedarken.rootvalidator.main.core.TestResult; + +public class RootResult implements TestResult { + private final Root.State state; + private final boolean exitCodeIssue; + private final boolean launchIssue; + private final boolean idIssue; + + RootResult(Builder builder) { + this.state = builder.state; + this.exitCodeIssue = builder.exitCodeIssue; + this.launchIssue = builder.launchIssue; + this.idIssue = builder.idIssue; + } + + @Override + public String getLabel(Context context) { + return context.getString(R.string.label_general_root_test); + } + + @Override + public Outcome getOutcome() { + if (state == Root.State.ROOTED) { + return Outcome.POSITIVE; + } else if (state == Root.State.DENIED) { + return Outcome.NEUTRAL; + } else { + return Outcome.NEGATIVE; + } + } + + @Override + public String getPrimaryInfo(Context context) { + if (state == Root.State.ROOTED) { + return context.getString(R.string.label_root_available); + } else if (state == Root.State.DENIED) { + return context.getString(R.string.label_root_denied); + } else { + return context.getString(R.string.label_root_unavailable); + } + } + + @Override + public List getCriteria(Context context) { + List criteria = new ArrayList<>(); + if (launchIssue) { + criteria.add(new Criterion(Outcome.NEGATIVE, context.getString(R.string.msg_failed_to_launch_shell))); + } else { + criteria.add(new Criterion(Outcome.POSITIVE, context.getString(R.string.msg_root_shell_launched))); + } + if (!launchIssue) { + if (idIssue) { + criteria.add(new Criterion(Outcome.NEGATIVE, context.getString(R.string.msg_id_unexpected))); + } else { + criteria.add(new Criterion(Outcome.POSITIVE, context.getString(R.string.msg_id_expected))); + } + if (exitCodeIssue) { + criteria.add(new Criterion(Outcome.NEUTRAL, context.getString(R.string.msg_bad_exitcodes))); + } else { + criteria.add(new Criterion(Outcome.POSITIVE, context.getString(R.string.msg_good_exitcodes))); + } + } + return criteria; + } + + static class Builder { + private boolean exitCodeIssue = false; + private Root.State state = Root.State.UNAVAILABLE; + private boolean launchIssue = false; + private boolean idIssue = false; + + + Builder state(Root.State state) { + this.state = state; + return this; + } + + Builder launchIssue(boolean launchIssue) { + this.launchIssue = launchIssue; + return this; + } + + Builder idIssue(boolean idIssue) { + this.idIssue = idIssue; + return this; + } + + Builder exitCodeIssue(boolean exitCodeIssue) { + this.exitCodeIssue = exitCodeIssue; + return this; + } + + RootResult build() { + return new RootResult(this); + } + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootTestSuite.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootTestSuite.java new file mode 100644 index 0000000..81ab812 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/root/RootTestSuite.java @@ -0,0 +1,61 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.root; + +import android.content.Context; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import eu.darken.rxshell.cmd.Cmd; +import eu.darken.rxshell.cmd.RxCmdShell; +import eu.darken.rxshell.root.Root; +import eu.thedarken.rootvalidator.main.core.TestResult; +import eu.thedarken.rootvalidator.main.core.TestSuite; +import eu.thedarken.rootvalidator.tools.StringUtils; +import io.reactivex.Single; + +public class RootTestSuite extends TestSuite { + + @Inject + public RootTestSuite(Context context) { + super(context); + } + + @Override + public Single> test() { + return Single.create(emitter -> { + final RootResult.Builder resultBuilder = new RootResult.Builder(); + + final Root root = new Root.Builder().build().blockingGet(); + final Root.State state = root.getState(); + resultBuilder.state(state); + + if (state != Root.State.ROOTED) { + { + final Cmd.Result result = Cmd.builder("id").execute(RxCmdShell.builder().root(true).build()); + resultBuilder.launchIssue(result.getExitCode() != Cmd.ExitCode.OK && result.getOutput().isEmpty()); + resultBuilder.idIssue(!StringUtils.join(result.merge()).contains("uid=0")); + } + { + final File testFile = new File("/cache/rootvalidator.tmp"); + Cmd.builder("echo test > " + testFile.getPath(), "chmod 444 " + testFile.getPath()).execute(RxCmdShell.builder().root(true).build()); + resultBuilder.exitCodeIssue(testFile.exists()); + if (testFile.exists()) { + Cmd.builder("rm " + testFile.getPath()).execute(RxCmdShell.builder().root(true).build()); + } + } + } + emitter.onSuccess(Collections.singletonList(resultBuilder.build())); + }); + + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxResult.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxResult.java new file mode 100644 index 0000000..ff6704e --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxResult.java @@ -0,0 +1,79 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.selinux; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +import eu.darken.rxshell.root.SELinux; +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Outcome; +import eu.thedarken.rootvalidator.main.core.TestResult; + +public class SELinuxResult implements TestResult { + private final SELinux.State state; + + SELinuxResult(Builder builder) { + this.state = builder.state; + } + + @Override + public String getLabel(Context context) { + return context.getString(R.string.label_selinux_test); + } + + public SELinux.State getState() { + return state; + } + + @Override + public Outcome getOutcome() { + if (getState() == SELinux.State.ENFORCING) { + return Outcome.NEUTRAL; + } else if (getState() == SELinux.State.DISABLED) { + return Outcome.NEUTRAL; + } else { + return Outcome.POSITIVE; + } + } + + @Override + public String getPrimaryInfo(Context context) { + if (getState() == SELinux.State.ENFORCING) { + return context.getString(R.string.label_selinux_enforcing); + } else if (getState() == SELinux.State.PERMISSIVE) { + return context.getString(R.string.label_selinux_permissive); + } else if (getState() == SELinux.State.DISABLED) { + return context.getString(R.string.label_selinux_disabled); + } else { + return context.getString(R.string.label_selinux_unknown); + } + } + + @Override + public List getCriteria(Context context) { + return new ArrayList<>(); + } + + static class Builder { + SELinux.State state = SELinux.State.ENFORCING; + + Builder state(SELinux.State state) { + this.state = state; + return this; + } + + SELinuxResult build() { + return new SELinuxResult(this); + } + } + +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxTestSuite.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxTestSuite.java new file mode 100644 index 0000000..e798309 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/selinux/SELinuxTestSuite.java @@ -0,0 +1,37 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.selinux; + +import android.content.Context; + +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import eu.darken.rxshell.root.SELinux; +import eu.thedarken.rootvalidator.main.core.TestResult; +import eu.thedarken.rootvalidator.main.core.TestSuite; +import io.reactivex.Single; + +public class SELinuxTestSuite extends TestSuite { + @Inject + public SELinuxTestSuite(Context context) { + super(context); + } + + @Override + public Single> test() { + return Single.create(emitter -> { + final SELinuxResult.Builder resultBuilder = new SELinuxResult.Builder(); + final SELinux seLinux = new SELinux.Builder().build().blockingGet(); + resultBuilder.state(seLinux.getState()); + emitter.onSuccess(Collections.singletonList(resultBuilder.build())); + }); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserApp.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserApp.java new file mode 100644 index 0000000..8df82e0 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserApp.java @@ -0,0 +1,49 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.suapp; + +import android.support.annotation.Nullable; + +import eu.darken.rxshell.root.SuApp; +import eu.darken.rxshell.root.SuBinary; + +public class SuperUserApp extends SuApp { + + @Nullable private final String name; + private final boolean systemApp; + private final boolean isPrimary; + + public SuperUserApp(SuBinary.Type type, + @Nullable String pkg, + @Nullable String versionName, + @Nullable Integer versionCode, + @Nullable String apkPath, + @Nullable String name, + boolean systemApp, + boolean isPrimary + + ) { + super(type, pkg, versionName, versionCode, apkPath); + this.name = name; + this.systemApp = systemApp; + this.isPrimary = isPrimary; + } + + @Nullable + public String getName() { + return name; + } + + public boolean isSystemApp() { + return systemApp; + } + + public boolean isPrimary() { + return isPrimary; + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppResult.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppResult.java new file mode 100644 index 0000000..58a0dd7 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppResult.java @@ -0,0 +1,79 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.suapp; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Outcome; +import eu.thedarken.rootvalidator.main.core.TestResult; + +public class SuperUserAppResult implements TestResult { + private final List suApps; + + public SuperUserAppResult(Builder builder) { + super(); + this.suApps = builder.suApps; + } + + @Override + public String getLabel(Context context) { + return context.getString(R.string.label_superapp_test); + } + + @Override + public Outcome getOutcome() { + if (suApps.size() > 1) { + return Outcome.NEUTRAL; + } else if (suApps.size() == 1) { + return Outcome.POSITIVE; + } else { + return Outcome.NEGATIVE; + } + } + + @Override + public String getPrimaryInfo(Context context) { + if (suApps.size() > 1) { + return context.getString(R.string.label_multiple_superapps); + } else if (suApps.size() == 1) { + return context.getString(R.string.label_superapp_found); + } else { + return context.getString(R.string.label_no_superapp); + } + } + + @Override + public List getCriteria(Context context) { + List criteria = new ArrayList<>(); + for (SuperUserApp suApp : suApps) { + String output = suApp.getName() + "\n" + + suApp.getPackageName() + " @ " + suApp.getVersionName() + " (" + suApp.getVersionCode() + ")"; + criteria.add(new Criterion(suApp.isPrimary() ? Outcome.POSITIVE : Outcome.NEUTRAL, output)); + } + return criteria; + } + + static class Builder { + + private List suApps = new ArrayList<>(); + + Builder apps(List suApps) { + this.suApps.addAll(suApps); + return this; + } + + SuperUserAppResult build() { + return new SuperUserAppResult(this); + } + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppTestSuite.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppTestSuite.java new file mode 100644 index 0000000..82922db --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserAppTestSuite.java @@ -0,0 +1,189 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.suapp; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Inject; + +import eu.darken.rxshell.cmd.Cmd; +import eu.darken.rxshell.cmd.RxCmdShell; +import eu.darken.rxshell.root.SuApp; +import eu.darken.rxshell.root.SuBinary; +import eu.thedarken.rootvalidator.main.core.TestResult; +import eu.thedarken.rootvalidator.main.core.TestSuite; +import io.reactivex.Single; + + +public class SuperUserAppTestSuite extends TestSuite { + + @Inject + public SuperUserAppTestSuite(Context context) { + super(context); + } + + @Override + public Single> test() { + return Single.create(emitter -> { + final SuperUserBinaryResult binaryResult = getBinaryResult(); + final SuperUserAppResult appResult = getAppResult(binaryResult); + emitter.onSuccess(Arrays.asList(binaryResult, appResult)); + }); + } + + SuperUserBinaryResult getBinaryResult() { + SuperUserBinaryResult.Builder binaryBuilder = new SuperUserBinaryResult.Builder(); + Set suBinaryLocations = new HashSet<>(); + Set possibleLocations = new HashSet<>(); + final RxCmdShell shell = RxCmdShell.builder().build(); + + final Cmd.Result pathResult = Cmd.builder("echo $PATH").execute(shell); + + if (pathResult.getExitCode() == Cmd.ExitCode.OK && pathResult.getOutput().size() == 1) { + for (String s : Arrays.asList(pathResult.getOutput().get(0).split(":"))) { + possibleLocations.add(s + "/su"); + } + } + + // More possible locations + possibleLocations.add("/sbin/su"); + possibleLocations.add("/system/bin/su"); + possibleLocations.add("/system/xbin/su"); + possibleLocations.add("/system/bin/failsafe/su"); + possibleLocations.add("/system/sd/xbin/su"); + possibleLocations.add("/data/local/su"); + possibleLocations.add("/data/local/bin/su"); + possibleLocations.add("/data/local/xbin/su"); + + for (String s : possibleLocations) { + final Cmd.Result fileResult = Cmd.builder("ls " + s).execute(shell); + if (fileResult.getExitCode() == Cmd.ExitCode.OK && fileResult.getOutput().size() > 0) { + suBinaryLocations.add(s); + } + } + + String primarySuBinary = null; + final Cmd.Result locatePrimaryResult = Cmd.builder("command -v su").execute(shell); + if (locatePrimaryResult.getExitCode() == Cmd.ExitCode.OK && locatePrimaryResult.getOutput().size() == 1) { + primarySuBinary = locatePrimaryResult.getOutput().get(0); + } + List binaries = new ArrayList<>(); + for (String suPath : suBinaryLocations) { + boolean primary = suPath.equals(primarySuBinary); + binaries.add(getBinary(suPath, primary)); + } + binaryBuilder.binaries(binaries); + return binaryBuilder.build(); + } + + private static final Pattern SUBINARY_PERMISSION_PATTERN = Pattern.compile("^([\\w-]+)\\s+([\\w]+)\\s+([\\w]+)(?:[\\W\\w]+)$"); + + private SuperUserBinary getBinary(String path, boolean primary) { + SuBinary.Type type = SuBinary.Type.UNKNOWN; + String permission = null; + String owner = null; + String group = null; + String version = null; + String extra = null; + final List raw = new ArrayList<>(); + + final RxCmdShell shell = new RxCmdShell.Builder().build(); + final Cmd.Result result = Cmd.builder("ls -l " + path).execute(shell); + if (result.getExitCode() == Cmd.ExitCode.OK && result.getOutput().size() > 0) { + Matcher matcher = SUBINARY_PERMISSION_PATTERN.matcher(result.getOutput().get(0)); + if (matcher.matches()) { + permission = matcher.group(1); + owner = matcher.group(2); + group = matcher.group(3); + } + } + + Cmd.Result versionResult = Cmd.builder("su --version").execute(shell); + if (versionResult.getExitCode() != Cmd.ExitCode.OK && versionResult.getExitCode() != 127) { + versionResult = Cmd.builder("su --V", "su -version", "su -v", "su -V").execute(shell); + } + + if (versionResult.getExitCode() == Cmd.ExitCode.OK) { + raw.addAll(versionResult.getOutput()); + for (String line : versionResult.getOutput()) { + for (Map.Entry entry : SuBinary.Builder.PATTERNMAP.entrySet()) { + Matcher matcher = entry.getKey().matcher(line); + if (matcher.matches()) { + type = entry.getValue(); + if (matcher.groupCount() == 1) { + version = matcher.group(1); + } else if (matcher.groupCount() == 2) { + extra = matcher.group(2); + } + break; + } + } + } + } + return new SuperUserBinary(type, path, version, extra, raw, primary, permission, owner, group); + } + + SuperUserAppResult getAppResult(SuperUserBinaryResult binaryResult) { + SuperUserAppResult.Builder appBuilder = new SuperUserAppResult.Builder(); + + SuperUserBinary.Type binaryType = SuBinary.Type.NONE; + if (binaryResult.getPrimary() != null) binaryType = binaryResult.getPrimary().getType(); + + final List apps = new ArrayList<>(); + for (Map.Entry entry : SuApp.Builder.SUAPPS.entrySet()) { + + // Otherwise we show the settings app as superuser app on stock ROMs + if (binaryType != SuBinary.Type.CYANOGENMOD && entry.getKey() == SuBinary.Type.CYANOGENMOD) { + continue; + } + + for (String pkg : entry.getValue()) { + try { + PackageInfo app = getContext().getPackageManager().getPackageInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES); + + String name = null; + String apkPath = null; + boolean systemApp = false; + boolean isPrimary = binaryType == entry.getKey(); + if (app.applicationInfo != null) { + name = (String) app.applicationInfo.loadLabel(getContext().getPackageManager()); + apkPath = app.applicationInfo.publicSourceDir; + systemApp = (app.applicationInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; + } + + SuperUserApp suApp = new SuperUserApp( + SuperUserBinary.Type.UNKNOWN, + app.packageName, + app.versionName, + app.versionCode, + apkPath, + name, + systemApp, + isPrimary + ); + apps.add(suApp); + } catch (PackageManager.NameNotFoundException ignored) { + } + } + } + appBuilder.apps(apps); + return appBuilder.build(); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinary.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinary.java new file mode 100644 index 0000000..381fa28 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinary.java @@ -0,0 +1,49 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.suapp; + +import android.support.annotation.Nullable; + +import java.util.List; + +import eu.darken.rxshell.root.SuBinary; + +public class SuperUserBinary extends SuBinary { + + private boolean primary; + private final String permission; + private final String owner; + private final String group; + + public SuperUserBinary(Type type, String path, @Nullable String version, @Nullable String extra, List raw, + boolean primary, String permission, String owner, String group) { + super(type, path, version, extra, raw); + + this.primary = primary; + this.permission = permission; + this.owner = owner; + this.group = group; + } + + public boolean isPrimary() { + return primary; + } + + public String getPermission() { + return permission; + } + + public String getOwner() { + return owner; + } + + public String getGroup() { + return group; + } + +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinaryResult.java b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinaryResult.java new file mode 100644 index 0000000..df9f045 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/core/suapp/SuperUserBinaryResult.java @@ -0,0 +1,94 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.core.suapp; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Outcome; +import eu.thedarken.rootvalidator.main.core.TestResult; + +public class SuperUserBinaryResult implements TestResult { + private final List binaries; + + public SuperUserBinaryResult(Builder builder) { + super(); + this.binaries = builder.binaries; + } + + @Override + public String getLabel(Context context) { + return context.getString(R.string.label_subinary_test); + } + + public SuperUserBinary getPrimary() { + for (SuperUserBinary suBinary : binaries) { + if (suBinary.isPrimary()) { + return suBinary; + } + } + return null; + } + + @Override + public Outcome getOutcome() { + if (binaries.isEmpty() || (binaries.size() == 1 && getPrimary() == null)) { + return Outcome.NEGATIVE; + } else if (binaries.size() > 1) { + return Outcome.NEUTRAL; + } else { + return Outcome.POSITIVE; + } + } + + @Override + public String getPrimaryInfo(Context context) { + if (binaries.isEmpty() || (binaries.size() == 1 && getPrimary() == null)) { + return context.getString(R.string.label_no_subinary); + } else { + return context.getString(R.string.label_subinary_available); + } + } + + @Override + public List getCriteria(Context context) { + List criteria = new ArrayList<>(); + if (getPrimary() != null) { + criteria.add(new Criterion(Outcome.POSITIVE, context.getString(R.string.msg_subinary_via_path))); + } else { + criteria.add(new Criterion(Outcome.NEGATIVE, context.getString(R.string.msg_subinary_not_via_path))); + } + for (SuperUserBinary binary : binaries) { + Outcome type = Outcome.NEUTRAL; + if (binary.isPrimary()) type = Outcome.POSITIVE; + String output = binary.getPath() + "\n" + + binary.getPermission() + " " + binary.getOwner() + ":" + binary.getGroup() + "\n" + + binary.getRaw(); + criteria.add(new Criterion(type, output)); + } + return criteria; + } + + + static class Builder { + private final List binaries = new ArrayList<>(); + + Builder binaries(List binaries) { + this.binaries.addAll(binaries); + return this; + } + + public SuperUserBinaryResult build() { + return new SuperUserBinaryResult(this); + } + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/ui/AboutDialog.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/AboutDialog.java similarity index 51% rename from app/src/main/java/eu/thedarken/rootvalidator/ui/AboutDialog.java rename to app/src/main/java/eu/thedarken/rootvalidator/main/ui/AboutDialog.java index 17140d6..cccb535 100644 --- a/app/src/main/java/eu/thedarken/rootvalidator/ui/AboutDialog.java +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/AboutDialog.java @@ -5,7 +5,7 @@ * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 */ -package eu.thedarken.rootvalidator.ui; +package eu.thedarken.rootvalidator.main.ui; import android.app.Dialog; import android.net.Uri; @@ -26,8 +26,6 @@ import eu.thedarken.rootvalidator.tools.Helpers; public class AboutDialog extends DialogFragment { - private Button mGPlus, mWeb, mTwitter; - private TextView mMessage; public static AboutDialog instantiate() { return new AboutDialog(); @@ -47,35 +45,27 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { return dialog; } + @SuppressWarnings("HardCodedStringLiteral") @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View dialogLayout = inflater.inflate(R.layout.fragment_dialog_about, container); - mMessage = (TextView) dialogLayout.findViewById(R.id.tv_message); - mMessage.setText(getString(R.string.about_version_message, getString(R.string.app_name), BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); - mGPlus = (Button) dialogLayout.findViewById(R.id.bt_gplus); - mGPlus.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Helpers.openLink(v.getContext(),Uri.parse("https://plus.google.com/116634499773478773276")); - dismiss(); - } + TextView message = dialogLayout.findViewById(R.id.tv_message); + message.setText(getString(R.string.about_version_message, getString(R.string.app_name), BuildConfig.VERSION_NAME, String.valueOf(BuildConfig.VERSION_CODE))); + Button gplus = dialogLayout.findViewById(R.id.bt_gplus); + gplus.setOnClickListener(v -> { + Helpers.openLink(v.getContext(), Uri.parse("https://plus.google.com/116634499773478773276")); + dismiss(); }); - mWeb = (Button) dialogLayout.findViewById(R.id.bt_www); - mWeb.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Helpers.openLink(v.getContext(),Uri.parse("http://www.darken.eu")); - dismiss(); - } + Button web = dialogLayout.findViewById(R.id.bt_www); + web.setOnClickListener(v -> { + Helpers.openLink(v.getContext(), Uri.parse("http://www.darken.eu")); + dismiss(); }); - mTwitter = (Button) dialogLayout.findViewById(R.id.bt_twitter); - mTwitter.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Helpers.openLink(v.getContext(),Uri.parse("http://www.twitter.com/d4rken")); - dismiss(); - } + Button twitter = dialogLayout.findViewById(R.id.bt_twitter); + twitter.setOnClickListener(v -> { + Helpers.openLink(v.getContext(), Uri.parse("http://www.twitter.com/d4rken")); + dismiss(); }); return dialogLayout; } diff --git a/app/src/main/java/eu/thedarken/rootvalidator/ui/ColorFramedCircleDrawable.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/ColorFramedCircleDrawable.java similarity index 97% rename from app/src/main/java/eu/thedarken/rootvalidator/ui/ColorFramedCircleDrawable.java rename to app/src/main/java/eu/thedarken/rootvalidator/main/ui/ColorFramedCircleDrawable.java index 2e8dd75..d440855 100644 --- a/app/src/main/java/eu/thedarken/rootvalidator/ui/ColorFramedCircleDrawable.java +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/ColorFramedCircleDrawable.java @@ -5,7 +5,7 @@ * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 */ -package eu.thedarken.rootvalidator.ui; +package eu.thedarken.rootvalidator.main.ui; import android.graphics.Canvas; import android.graphics.ColorFilter; diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivity.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivity.java new file mode 100644 index 0000000..8b3cfa5 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivity.java @@ -0,0 +1,55 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.ui; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; + +import javax.inject.Inject; + +import eu.darken.mvpbakery.base.MVPBakery; +import eu.darken.mvpbakery.base.ViewModelRetainer; +import eu.darken.mvpbakery.injection.ComponentSource; +import eu.darken.mvpbakery.injection.InjectedPresenter; +import eu.darken.mvpbakery.injection.ManualInjector; +import eu.darken.mvpbakery.injection.PresenterInjectionCallback; +import eu.darken.mvpbakery.injection.fragment.HasManualFragmentInjector; +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.ui.validator.ValidatorFragment; + +public class MainActivity extends AppCompatActivity implements MainActivityPresenter.View, HasManualFragmentInjector { + + @Inject ComponentSource componentSource; + + public String getFragmentClass() { + return ValidatorFragment.class.getName(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + MVPBakery.builder() + .presenterFactory(new InjectedPresenter<>(this)) + .presenterRetainer(new ViewModelRetainer<>(this)) + .addPresenterCallback(new PresenterInjectionCallback<>(this)) + .attach(this); + + setContentView(R.layout.activity_mainactivity_layout); + Fragment fragment = getSupportFragmentManager().findFragmentByTag(getFragmentClass()); + if (fragment == null) { + fragment = Fragment.instantiate(this, getFragmentClass()); + getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment, getFragmentClass()).commit(); + } + } + + @Override + public ManualInjector supportFragmentInjector() { + return componentSource; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityComponent.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityComponent.java new file mode 100644 index 0000000..ab90ed9 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityComponent.java @@ -0,0 +1,45 @@ +package eu.thedarken.rootvalidator.main.ui; + + +import android.support.v4.app.Fragment; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import dagger.Binds; +import dagger.Module; +import dagger.Subcomponent; +import dagger.android.support.FragmentKey; +import dagger.multibindings.IntoMap; +import eu.darken.mvpbakery.injection.PresenterComponent; +import eu.darken.mvpbakery.injection.activity.ActivityComponent; +import eu.thedarken.rootvalidator.main.ui.validator.ValidatorComponent; +import eu.thedarken.rootvalidator.main.ui.validator.ValidatorFragment; + +@MainActivityComponent.Scope +@Subcomponent(modules = { + MainActivityComponent.FragmentBinderModule.class +}) +public interface MainActivityComponent extends ActivityComponent, PresenterComponent { + + @Subcomponent.Builder + abstract class Builder extends ActivityComponent.Builder { + + } + + @javax.inject.Scope + @Retention(RetentionPolicy.RUNTIME) + @interface Scope { + } + + @Module(subcomponents = { + ValidatorComponent.class + }) + abstract class FragmentBinderModule { + + @Binds + @IntoMap + @FragmentKey(ValidatorFragment.class) + abstract Factory volumes(ValidatorComponent.Builder impl); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityPresenter.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityPresenter.java new file mode 100644 index 0000000..490d240 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/MainActivityPresenter.java @@ -0,0 +1,16 @@ +package eu.thedarken.rootvalidator.main.ui; + +import javax.inject.Inject; + +import eu.darken.mvpbakery.base.Presenter; +import eu.darken.mvpbakery.injection.ComponentPresenter; + +public class MainActivityPresenter extends ComponentPresenter { + + @Inject + public MainActivityPresenter() { + } + + interface View extends Presenter.View { + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/TestResultAdapter.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/TestResultAdapter.java new file mode 100644 index 0000000..7377d76 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/TestResultAdapter.java @@ -0,0 +1,125 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.ui.validator; + +import android.content.Context; +import android.support.annotation.DrawableRes; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Result; +import eu.thedarken.rootvalidator.main.core.TestResult; + + +public class TestResultAdapter extends RecyclerView.Adapter { + private final List data = new ArrayList<>(); + private static final int NORMAL_TYPE = 0; + + @Override + public int getItemViewType(int position) { + return NORMAL_TYPE; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case NORMAL_TYPE: + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_info_line, parent, false); + return new RVInfoHolder(v); + default: + return null; + } + } + + @Override + public int getItemCount() { + return data.size(); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (getItemViewType(position) == NORMAL_TYPE) { + TestResult info = data.get(position); + RVInfoHolder infoHolder = (RVInfoHolder) holder; + infoHolder.bind(info); + } + } + + public void setData(List data) { + this.data.clear(); + if (data != null) this.data.addAll(data); + } + + class RVInfoHolder extends RecyclerView.ViewHolder { + @BindView(R.id.icon) ImageView colcircl; + @BindView(R.id.test_title) TextView title; + @BindView(R.id.point_container) LinearLayout container; + @BindView(R.id.expand_button) ImageView expand; + + RVInfoHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + private Context getContext() { + return itemView.getContext(); + } + + public void bind(TestResult info) { + colcircl.setImageResource(getOutcomeDrawable(info)); + title.setText(info.getPrimaryInfo(getContext())); + final List criteria = info.getCriteria(getContext()); + if (criteria.isEmpty()) { + expand.setVisibility(View.GONE); + container.setVisibility(View.GONE); + itemView.setOnClickListener(null); + } else { + expand.setVisibility(View.VISIBLE); + itemView.setOnClickListener(v -> { + container.setVisibility(container.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); + expand.setImageResource(container.getVisibility() == View.VISIBLE ? R.drawable.ic_expand_less_white_24dp : R.drawable.ic_expand_more_white_24dp); + }); + container.removeAllViews(); + for (Criterion criterion : criteria) { + View bpLine = LayoutInflater.from(getContext()).inflate(R.layout.view_testpoint, container, false); + ImageView point = bpLine.findViewById(R.id.point_icon); + TextView text = bpLine.findViewById(R.id.point_text); + text.setText(criterion.getPrimaryInfo(getContext())); + point.setImageResource(getOutcomeDrawable(criterion)); + container.addView(bpLine); + } + } + } + + @DrawableRes + int getOutcomeDrawable(Result result) { + switch (result.getOutcome()) { + case POSITIVE: + return R.drawable.ic_indicator_positive; + case NEUTRAL: + return R.drawable.ic_indicator_neutral; + case NEGATIVE: + return R.drawable.ic_indicator_negative; + default: + return R.drawable.ic_indicator_negative; + } + } + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorComponent.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorComponent.java new file mode 100644 index 0000000..9809a20 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorComponent.java @@ -0,0 +1,25 @@ +package eu.thedarken.rootvalidator.main.ui.validator; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import dagger.Subcomponent; +import eu.darken.mvpbakery.injection.PresenterComponent; +import eu.darken.mvpbakery.injection.fragment.FragmentComponent; + + +@ValidatorComponent.Scope +@Subcomponent() +public interface ValidatorComponent extends PresenterComponent, FragmentComponent { + @Subcomponent.Builder + abstract class Builder extends FragmentComponent.Builder { + + } + + @Documented + @javax.inject.Scope + @Retention(RetentionPolicy.RUNTIME) + @interface Scope { + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorFragment.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorFragment.java new file mode 100644 index 0000000..7422b22 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorFragment.java @@ -0,0 +1,245 @@ +/* + * Project Root Validator + * + * @link https://github.com/d4rken/rootvalidator + * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 + */ + +package eu.thedarken.rootvalidator.main.ui.validator; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.AnticipateOvershootInterpolator; +import android.view.animation.LayoutAnimationController; +import android.view.animation.TranslateAnimation; + +import java.util.List; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import eu.darken.mvpbakery.base.MVPBakery; +import eu.darken.mvpbakery.base.ViewModelRetainer; +import eu.darken.mvpbakery.injection.InjectedPresenter; +import eu.darken.mvpbakery.injection.PresenterInjectionCallback; +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.TestResult; +import eu.thedarken.rootvalidator.main.ui.AboutDialog; +import eu.thedarken.rootvalidator.tools.ShareHelper; + + +public class ValidatorFragment extends Fragment implements ValidatorPresenter.View { + + @BindView(R.id.fab) FloatingActionButton fab; + @BindView(R.id.recyclerview) RecyclerView recyclerView; + @BindView(R.id.box_intro) View introBox; + @BindView(R.id.box_working) View workingBox; + + @Inject ValidatorPresenter presenter; + + private final TestResultAdapter adapter = new TestResultAdapter(); + private Unbinder unbinder; + private Snackbar upgradeBar; + private boolean showDonate; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.fragment_validator_layout, container, false); + unbinder = ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onDestroyView() { + unbinder.unbind(); + super.onDestroyView(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + AnimationSet set = new AnimationSet(true); + Animation fadeIn = new AlphaAnimation(0.0f, 1.0f); + fadeIn.setDuration(350); + set.addAnimation(fadeIn); + Animation dropDown = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f, + Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f + ); + dropDown.setDuration(400); + set.addAnimation(dropDown); + LayoutAnimationController controller = new LayoutAnimationController(set, 0.2f); + recyclerView.setLayoutAnimation(controller); + + recyclerView.setAdapter(adapter); + + fab.setVisibility(View.INVISIBLE); + introBox.setVisibility(View.VISIBLE); + workingBox.setVisibility(View.GONE); + recyclerView.setVisibility(View.GONE); + recyclerView.setItemAnimator(new DefaultItemAnimator()); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + MVPBakery.builder() + .presenterFactory(new InjectedPresenter<>(this)) + .presenterRetainer(new ViewModelRetainer<>(this)) + .addPresenterCallback(new PresenterInjectionCallback<>(this)) + .attach(this); + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + + animateFAB(false); + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_root)); + fab.setOnClickListener(v -> presenter.onTestAll()); + } + + private void animateFAB(boolean out) { + Animation animation; + if (out) { + animation = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1.0f); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { } + + @Override + public void onAnimationEnd(Animation animation) { + fab.setVisibility(View.INVISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { } + }); + } else { + animation = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0); + + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + fab.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animation animation) { } + + @Override + public void onAnimationRepeat(Animation animation) { } + }); + } + animation.setDuration(600); + animation.setInterpolator(new AnticipateOvershootInterpolator(1.2f)); + fab.startAnimation(animation); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.validator_menu, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.donate).setVisible(showDonate); + super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.about) { + AboutDialog dialog = AboutDialog.instantiate(); + dialog.showDialog(getActivity()); + return true; + } else if (item.getItemId() == R.id.donate) { + presenter.onDonateClicked(getActivity()); + } + return super.onOptionsItemSelected(item); + } + + @Override + public void display(List testData) { + adapter.setData(testData); + adapter.notifyDataSetChanged(); + + introBox.setVisibility(View.GONE); + workingBox.setVisibility(View.GONE); + recyclerView.setVisibility(View.VISIBLE); + + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_share_white_24dp)); + fab.setOnClickListener(v -> presenter.onShare()); + animateFAB(false); + } + + @Override + public void share(List shareData) { + ShareHelper.share(getActivity(), getString(R.string.label_export_title), shareData); + } + + @Override + public void showWorking() { + animateFAB(true); + introBox.setVisibility(View.GONE); + workingBox.setVisibility(View.VISIBLE); + } + + @Override + public void showNagBar(boolean show) { + if (getView() == null) return; + if (show) { + Snackbar.make(getView(), R.string.donate_description, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.action_donate, v -> presenter.onDonateClicked(getActivity())) + .addCallback(new Snackbar.Callback() { + @Override + public void onShown(Snackbar sb) { + ValidatorFragment.this.upgradeBar = sb; + super.onShown(sb); + } + + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + upgradeBar = null; + super.onDismissed(transientBottomBar, event); + } + }) + .show(); + } else { + if (upgradeBar != null) upgradeBar.dismiss(); + } + } + + @Override + public void showDonate(boolean showDonate) { + if (getActivity() == null) return; + this.showDonate = showDonate; + getActivity().invalidateOptionsMenu(); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorPresenter.java b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorPresenter.java new file mode 100644 index 0000000..a2089da --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/main/ui/validator/ValidatorPresenter.java @@ -0,0 +1,134 @@ +package eu.thedarken.rootvalidator.main.ui.validator; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import eu.darken.mvpbakery.base.Presenter; +import eu.darken.mvpbakery.injection.ComponentPresenter; +import eu.thedarken.rootvalidator.IAPHelper; +import eu.thedarken.rootvalidator.R; +import eu.thedarken.rootvalidator.main.core.Criterion; +import eu.thedarken.rootvalidator.main.core.Settings; +import eu.thedarken.rootvalidator.main.core.TestResult; +import eu.thedarken.rootvalidator.main.core.TestSuite; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.Disposables; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +@ValidatorComponent.Scope +public class ValidatorPresenter extends ComponentPresenter { + + private final Context context; + private final List tests; + private List testData; + private final IAPHelper iapHelper; + private final Settings settings; + private Disposable upgradeSub = Disposables.disposed(); + + @Inject + ValidatorPresenter(Context context, List tests, IAPHelper iapHelper, Settings settings) { + this.context = context; + this.tests = tests; + this.iapHelper = iapHelper; + this.settings = settings; + } + + @Override + public void onBindChange(@Nullable View view) { + super.onBindChange(view); + iapHelper.check(); + if (testData != null) { + onView(v -> v.display(testData)); + } + if (getView() != null && upgradeSub.isDisposed()) { + upgradeSub = iapHelper.isPremiumVersion() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(isProVersion -> { + onView(v -> v.showDonate(!isProVersion)); + long delta = System.currentTimeMillis() - settings.getLastUpgradeNagTime(); + if (delta > 3 * 60 * 1000) { + // Every 3 hours at most. + settings.setLastUpgradeNagTime(System.currentTimeMillis()); + ValidatorPresenter.this.onView(v -> v.showNagBar(!isProVersion)); + } + }); + } else { + upgradeSub.dispose(); + } + } + + void onTestAll() { + onView(View::showWorking); + Single.create((SingleOnSubscribe>) emitter -> { + Timber.d("loadInBackground start..."); + long dur = System.currentTimeMillis(); + ArrayList results = new ArrayList<>(); + + for (TestSuite suite : tests) { + results.addAll(suite.test().blockingGet()); + } + + Timber.d("loadInBackground done: %dms", (System.currentTimeMillis() - dur)); + emitter.onSuccess(results); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess(testInfos -> ValidatorPresenter.this.testData = testInfos) + .subscribe(testInfos -> onView(v -> v.display(testInfos))); + + } + + void onShare() { + if (testData == null) return; + ArrayList toShare = new ArrayList<>(); + for (TestResult testResult : testData) { + toShare.addAll(getDetails(context, testResult)); + toShare.add("##########\n"); + } + onView(v -> v.share(toShare)); + } + + static List getDetails(Context context, TestResult testResult) { + List details = new ArrayList<>(); + details.add("##### " + testResult.getLabel(context) + " #####"); + details.add(context.getString(R.string.label_export_section_result)); + details.add(" " + testResult.getPrimaryInfo(context)); + + final List criteria = testResult.getCriteria(context); + if (criteria.size() > 0) details.add(context.getString(R.string.label_export_section_extras)); + for (Criterion criterion : criteria) { + details.add(" " + criterion.getPrimaryInfo(context)); + if (criteria.indexOf(criterion) != criteria.size() - 1 && criteria.size() > 1) { + details.add(" ---"); + } + } + return details; + } + + void onDonateClicked(Activity activity) { + iapHelper.donate(activity); + } + + interface View extends Presenter.View { + void share(List shareData); + + void display(List testData); + + void showWorking(); + + void showNagBar(boolean show); + + void showDonate(boolean showDonate); + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/ATest.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/ATest.java deleted file mode 100644 index bec1552..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/ATest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests; - -import android.content.Context; - -public abstract class ATest { - private final Context mContext; - - public ATest(Context context) { - mContext = context; - } - - public Context getContext() { - return mContext; - } - - public abstract TestInfo test(); -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/BP.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/BP.java deleted file mode 100644 index f2b03cd..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/BP.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests; - -import android.graphics.drawable.Drawable; - - -public class BP { - private final Drawable mPoint; - private final String mText; - - public BP(Drawable point, String text) { - mPoint = point; - mText = text; - } - - public String getText() { - return mText; - } - - public Drawable getPoint() { - return mPoint; - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/Result.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/Result.java deleted file mode 100644 index f2684f9..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/Result.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests; - -import android.os.Parcelable; - - -public abstract class Result implements Parcelable { - public enum Outcome { - POSITIVE, NEUTRAL, NEGATIVE - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/TestInfo.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/TestInfo.java deleted file mode 100644 index 2bdf3e6..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/TestInfo.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.R; -import eu.thedarken.rootvalidator.ui.ColorFramedCircleDrawable; - -public abstract class TestInfo implements Parcelable { - private final List mRaw = new ArrayList<>(); - protected final String mTestTitle; - - protected TestInfo(String testTitle) { - mTestTitle = testTitle; - } - - public String getTitle() { - return mTestTitle; - } - - public abstract Result.Outcome getOutcome(); - - public Drawable getIcon(Context context) { - if (getOutcome() == Result.Outcome.POSITIVE) { - return getPositiveD(context); - } else if (getOutcome() == Result.Outcome.NEGATIVE) { - return getNegativeD(context); - } else { - return getNeutralD(context); - } - } - - public static Drawable getPositiveD(Context context) { - return new ColorFramedCircleDrawable(null, 48, context.getResources().getColor(R.color.positive)); - } - - public static Drawable getNeutralD(Context context) { - return new ColorFramedCircleDrawable(null, 48, context.getResources().getColor(R.color.neutral)); - } - - public static Drawable getNegativeD(Context context) { - return new ColorFramedCircleDrawable(null, 48, context.getResources().getColor(R.color.negative)); - } - - public String getPrimaryInfo(Context context) { - return null; - } - - public List getCriterias(Context context) { - return null; - } - - public List getRaw() { - return mRaw; - } - - public List getDetails(Context context) { - List details = new ArrayList<>(); - details.add("##### " + getTitle() + " #####"); - details.add("Result:"); - details.add(" " + getPrimaryInfo(context)); - details.add("Extras:"); - for (BP bp : getCriterias(context)) { - details.add(" " + bp.getText()); - } - return details; - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBox.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBox.java deleted file mode 100644 index b1a97fa..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBox.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.busybox; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class BusyBox implements Parcelable { - protected static final String[] APPLETS = {"[", "[[", "acpid", "addgroup", "adduser", "adjtimex", "ar", "arp", "arping", "ash", "awk", "basename", "beep", "blkid", - "brctl", "bunzip2", "bzcat", "bzip2", "cal", "cat", "catv", "chat", "chattr", "chgrp", "chmod", "chown", "chpasswd", "chpst", "chroot", "chrt", - "chvt", "cksum", "clear", "cmp", "comm", "cp", "cpio", "crond", "crontab", "cryptpw", "cut", "date", "dc", "dd", "deallocvt", "delgroup", - "deluser", "depmod", "devmem", "df", "dhcprelay", "diff", "dirname", "dmesg", "dnsd", "dnsdomainname", "dos2unix", "dpkg", "du", "dumpkmap", - "dumpleases", "echo", "ed", "egrep", "eject", "env", "envdir", "envuidgid", "expand", "expr", "fakeidentd", "false", "fbset", "fbsplash", - "fdflush", "fdformat", "fdisk", "fgrep", "find", "findfs", "flash_lock", "flash_unlock", "fold", "free", "freeramdisk", "fsck", "fsck.minix", - "fsync", "ftpd", "ftpget", "ftpput", "fuser", "getopt", "getty", "grep", "gunzip", "gzip", "hd", "hdparm", "head", "hexdump", "hostid", "hostname", - "httpd", "hush", "hwclock", "id", "ifconfig", "ifdown", "ifenslave", "ifplugd", "ifup", "inetd", "init", "inotifyd", "insmod", "install", "ionice", - "ip", "ipaddr", "ipcalc", "ipcrm", "ipcs", "iplink", "iproute", "iprule", "iptunnel", "kbd_mode", "kill", "killall", "killall5", "klogd", "last", - "length", "less", "linux32", "linux64", "linuxrc", "ln", "loadfont", "loadkmap", "logger", "login", "logname", "logread", "losetup", "lpd", "lpq", - "lpr", "ls", "lsattr", "lsmod", "lzmacat", "lzop", "lzopcat", "makemime", "man", "md5sum", "mdev", "mesg", "microcom", "mkdir", "mkdosfs", - "mkfifo", "mkfs.minix", "mkfs.vfat", "mknod", "mkpasswd", "mkswap", "mktemp", "modprobe", "more", "mount", "mountpoint", "mt", "mv", "nameif", - "nc", "netstat", "nice", "nmeter", "nohup", "nslookup", "od", "openvt", "passwd", "patch", "pgrep", "pidof", "ping", "ping6", "pipe_progress", - "pivot_root", "pkill", "popmaildir", "printenv", "printf", "ps", "pscan", "pwd", "raidautorun", "rdate", "rdev", "readlink", "readprofile", - "realpath", "reformime", "renice", "reset", "resize", "rm", "rmdir", "rmmod", "route", "rpm", "rpm2cpio", "rtcwake", "run-parts", "runlevel", - "runsv", "runsvdir", "rx", "script", "scriptreplay", "sed", "sendmail", "seq", "setarch", "setconsole", "setfont", "setkeycodes", "setlogcons", - "setsid", "setuidgid", "sh", "sha1sum", "sha256sum", "sha512sum", "showkey", "slattach", "sleep", "softlimit", "sort", "split", - "start-stop-daemon", "stat", "strings", "stty", "su", "sulogin", "sum", "sv", "svlogd", "swapoff", "swapon", "switch_root", "sync", "sysctl", - "syslogd", "tac", "tail", "tar", "taskset", "tcpsvd", "tee", "telnet", "telnetd", "test", "tftp", "tftpd", "time", "timeout", "top", "touch", "tr", - "traceroute", "true", "tty", "ttysize", "udhcpc", "udhcpd", "udpsvd", "umount", "uname", "uncompress", "unexpand", "uniq", "unix2dos", "unlzma", - "unlzop", "unzip", "uptime", "usleep", "uudecode", "uuencode", "vconfig", "vi", "vlock", "volname", "watch", "watchdog", "wc", "wget", "which", - "who", "whoami", "xargs", "yes", "zcat", "zcip"}; - private final File mPath; - private final boolean mPrimary; - boolean mExecutable = true; - String mVersion; - String mPermission; - String mOwner; - String mGroup; - private final List mAvailableApplets = new ArrayList<>(); - private final List mMissingApplets = new ArrayList<>(Arrays.asList(APPLETS)); - - public BusyBox(File path, boolean primary) { - this.mPath = path; - this.mPrimary = primary; - } - - public boolean isSufficient() { - return true; - } - - public boolean isPrimary() { - return mPrimary; - } - - public List getMissingApplets() { - return mMissingApplets; - } - - public List getAvailableApplets() { - return mAvailableApplets; - } - - public String getGroup() { - return mGroup; - } - - public String getOwner() { - return mOwner; - } - - public String getPermission() { - return mPermission; - } - - public boolean isExecutable() { - return mExecutable; - } - - public File getPath() { - return mPath; - } - - public String getVersion() { - return mVersion; - } - - @Override - public String toString() { - return "mPath:" + mPath.getAbsolutePath() + " | " + - "primary:" + mPrimary + " | " + - "mExecutable:" + mExecutable + " | " + - "mVersion:" + mVersion + " | " + - "mPermission:" + mPermission + " | " + - "mOwner:" + mOwner + " | " + - "mGroup:" + mGroup + " | " + - "applets:" + "HAVE-" + mAvailableApplets.size() + " MIA-" + mMissingApplets.size() + " | " + - "mPath:" + mPath.getAbsolutePath(); - } - - public BusyBox(Parcel in) { - mPath = new File(in.readString()); - mPrimary = in.readByte() != 0; - mExecutable = in.readByte() != 0; - mVersion = in.readString(); - mPermission = in.readString(); - mOwner = in.readString(); - mGroup = in.readString(); - in.readStringList(mAvailableApplets); - in.readStringList(mMissingApplets); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mPath.getAbsolutePath()); - out.writeByte((byte) (mPrimary ? 1 : 0)); - out.writeByte((byte) (mExecutable ? 1 : 0)); - out.writeString(mVersion); - out.writeString(mPermission); - out.writeString(mOwner); - out.writeString(mGroup); - out.writeStringList(mAvailableApplets); - out.writeStringList(mMissingApplets); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - public BusyBox createFromParcel(Parcel in) { - return new BusyBox(in); - } - - public BusyBox[] newArray(int size) { - return new BusyBox[size]; - } - }; -} - diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxInfo.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxInfo.java deleted file mode 100644 index f0460ec..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxInfo.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.busybox; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.tests.BP; -import eu.thedarken.rootvalidator.tests.Result; -import eu.thedarken.rootvalidator.tests.TestInfo; - -public class BusyBoxInfo extends TestInfo { - private final List mBusyBoxes = new ArrayList<>(); - - public BusyBoxInfo() { - super("BusyBox Test"); - } - - public List getBusyBoxes() { - return mBusyBoxes; - } - - public BusyBox getPrimary() { - for (BusyBox bb : mBusyBoxes) { - if (bb.isPrimary()) { - return bb; - } - } - return null; - } - - @Override - public Result.Outcome getOutcome() { - if (getPrimary() == null || !getPrimary().isSufficient()) { - return Result.Outcome.NEGATIVE; - } else { - return Result.Outcome.POSITIVE; - } - } - - @Override - public String getPrimaryInfo(Context context) { - if (getBusyBoxes().size() == 0) { - return "No busybox"; - } else if (getBusyBoxes().size() == 1) { - return "Busybox found"; - } else { - return "Multiple busybox's"; - } - } - - @Override - public List getCriterias(Context context) { - List criterias = new ArrayList<>(); - BusyBox primBB = getPrimary(); - if (primBB != null) { - criterias.add(new BP(getPositiveD(context), "Primary busybox is avaiable via $PATH.")); - if (primBB.isSufficient()) { - criterias.add(new BP(getPositiveD(context), "Primary busybox offers most common applets.")); - } else { - criterias.add(new BP(getNegativeD(context), "Primary busybox may lack some common applets.")); - } - } else { - criterias.add(new BP(getNegativeD(context), "No busybox avaiable via $PATH.")); - } - for (BusyBox bb : mBusyBoxes) { - Drawable d = getNeutralD(context); - if (bb.isPrimary()) - d = getPositiveD(context); - if (bb.getAvailableApplets().isEmpty()) - d = getNegativeD(context); - String builder = (bb.getVersion() + ", " + bb.getAvailableApplets().size() + " applets.\n") - + bb.getPermission() + " " + bb.getOwner() + " " + bb.getGroup() + "\n" - + bb.getPath().getAbsolutePath(); - criterias.add(new BP(d, builder)); - } - return criterias; - } - - public BusyBoxInfo(Parcel in) { - this(); - in.readList(mBusyBoxes, BusyBox.class.getClassLoader()); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeList(mBusyBoxes); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - public BusyBoxInfo createFromParcel(Parcel in) { - return new BusyBoxInfo(in); - } - - public BusyBoxInfo[] newArray(int size) { - return new BusyBoxInfo[size]; - } - }; - -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxTest.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxTest.java deleted file mode 100644 index fb8ece5..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/busybox/BusyBoxTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.busybox; - -import android.content.Context; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import eu.thedarken.rootvalidator.tests.ATest; -import eu.thedarken.rootvalidator.tools.Cmd; -import eu.thedarken.rootvalidator.tools.Logy; - -public class BusyBoxTest extends ATest { - private static final String TAG = "RV:BusyboxTest"; - private List mRaw; - - public BusyBoxTest(Context context) { - super(context); - } - - @Override - public BusyBoxInfo test() { - BusyBoxInfo result = new BusyBoxInfo(); - mRaw = result.getRaw(); - result.getBusyBoxes().addAll(locateBusyboxes()); - for (BusyBox busybox : result.getBusyBoxes()) { - checkBusybox(busybox); - Logy.d(TAG, busybox.toString()); - } - return result; - } - - private static final Pattern BUSYBOX_PERMISSION_PATTERN = Pattern.compile("^([\\w-]+)\\s+([\\w]+)\\s+([\\w]+)(?:[\\W\\w]+)$"); - private static final Pattern BUSYBOX_VERSION_PATTERN = Pattern.compile("^(?i:busybox)\\s([\\W\\w]+)\\s(?:\\([\\W\\w]+\\))\\s(?:multi-call binary.)$"); - - private static final Pattern TYPE_LOCATE_PATTERN = Pattern.compile("^(?:busybox)\\s(?:[\\w\\s]+)\\s((?:\\/[\\w]+)+)$"); - - private List locateBusyboxes() { - HashSet busyBoxBinaryLocations = new HashSet<>(); - HashSet possibleBinaryLocations = new HashSet<>(); - - Cmd cmd = new Cmd(); - cmd.setRaw(mRaw); - cmd.addCommand("echo $PATH"); - cmd.execute(); - - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() == 1) { - for (String s : Arrays.asList(cmd.getOutput().get(0).split(":"))) { - possibleBinaryLocations.add(s + "/busybox"); - } - } - - for (String s : possibleBinaryLocations) { - cmd.clearCommands(); - cmd.addCommand("ls" + s + "/busybox"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - String path = s + "/busybox"; - busyBoxBinaryLocations.add(path); - } - } - - // Wasn't in $PATH, where then? - possibleBinaryLocations.clear(); - possibleBinaryLocations.add("/sbin/busybox"); - possibleBinaryLocations.add("/system/bin/busybox"); - possibleBinaryLocations.add("/system/xbin/busybox"); - possibleBinaryLocations.add("/system/bin/failsafe/busybox"); - possibleBinaryLocations.add("/system/sd/xbin/busybox"); - possibleBinaryLocations.add("/data/local/busybox"); - possibleBinaryLocations.add("/data/local/bin/busybox"); - possibleBinaryLocations.add("/data/local/xbin/busybox"); - - for (String s : possibleBinaryLocations) { - cmd.clearCommands(); - cmd.addCommand("ls " + s); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - busyBoxBinaryLocations.add(s); - } - } - - String primaryBusybox = null; - cmd.clearCommands(); - cmd.addCommand("busybox"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK) { - // Busybox was in path - cmd.clearCommands(); - cmd.addCommand("type busybox"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - Matcher matcher = TYPE_LOCATE_PATTERN.matcher(cmd.getOutput().get(0)); - if (matcher.matches()) { - busyBoxBinaryLocations.add(matcher.group(1)); - primaryBusybox = matcher.group(1); - } - } - } - cmd.clearCommands(); - List busyboxes = new ArrayList<>(); - for (String bbPath : busyBoxBinaryLocations) - busyboxes.add(new BusyBox(new File(bbPath), bbPath.equals(primaryBusybox))); - return busyboxes; - } - - private void checkBusybox(BusyBox binary) { - Cmd cmd = new Cmd(); - cmd.setRaw(mRaw); - cmd.addCommand("ls -l " + binary.getPath().getAbsolutePath()); - cmd.execute(); - - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - Matcher matcher = BUSYBOX_PERMISSION_PATTERN.matcher(cmd.getOutput().get(0)); - if (matcher.matches()) { - binary.mPermission = matcher.group(1); - binary.mOwner = matcher.group(2); - binary.mGroup = matcher.group(3); - } - } - - cmd.clearCommands(); - cmd.addCommand(binary.getPath().getAbsolutePath()); - cmd.execute(); - - if (cmd.getExitCode() != Cmd.OK) { - binary.mExecutable = false; - return; - } - if (cmd.getOutput().size() == 0) - return; - - Matcher versionMatcher = BUSYBOX_VERSION_PATTERN.matcher(cmd.getOutput().get(0)); - if (versionMatcher.matches()) { - binary.mVersion = versionMatcher.group(1); - } - - int cmdstart = 0; - // Skip first rubish lines - while (cmdstart < 25) { - if (cmd.getOutput().get(cmdstart).contains("Currently defined functions")) { - break; - } - cmdstart++; - } - // Okay, start glueing the found cmds toegether - StringBuilder gluedlines = new StringBuilder(); - for (String s : cmd.getOutput().subList(++cmdstart, cmd.getOutput().size())) { - gluedlines.append(s); - } - - String listedApplets = gluedlines.toString(); - - int keep = 0; - // Now check which applets are contained in the glued string - while (keep < binary.getMissingApplets().size()) { - if (!listedApplets.contains(binary.getMissingApplets().get(keep))) { - keep++; - } else { - binary.getAvailableApplets().add(binary.getMissingApplets().remove(keep)); - } - } - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootInfo.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootInfo.java deleted file mode 100644 index d1b3a01..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootInfo.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.root; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.tests.BP; -import eu.thedarken.rootvalidator.tests.Result; -import eu.thedarken.rootvalidator.tests.TestInfo; -import eu.thedarken.rootvalidator.tests.busybox.BusyBoxInfo; -import eu.thedarken.rootvalidator.tools.Cmd; - -public class RootInfo extends TestInfo { - boolean mGotRoot = false; - boolean mSuLaunchesShell = false; - boolean mBinaryIssue = false; - int mExitCode = 99; - - public RootInfo() { - super("General Root Test"); - } - - @Override - public Result.Outcome getOutcome() { - if (mGotRoot) { - return Result.Outcome.POSITIVE; - } else if (!mBinaryIssue) { - return Result.Outcome.NEUTRAL; - } else { - return Result.Outcome.NEGATIVE; - } - } - - @Override - public String getPrimaryInfo(Context context) { - if (mGotRoot) { - return "Root available!"; - } else if (!mBinaryIssue) { - return "Root denied?"; - } else { - return "Root unavailable"; - } - } - - @Override - public List getCriterias(Context context) { - List criterias = new ArrayList<>(); - if (mBinaryIssue) - criterias.add(new BP(getNegativeD(context), "su/id/echo did not execute.")); - if (mSuLaunchesShell) - criterias.add(new BP(getPositiveD(context), "su binary launches a shell.")); - if (mGotRoot) - criterias.add(new BP(getPositiveD(context), "Root UID obtained.")); - if (mExitCode != Cmd.OK) - criterias.add(new BP(getNegativeD(context), "Root shell had non OK exitcode (" + mExitCode + ").")); - return criterias; - } - - @Override - public String toString() { - return "mGotRoot:" + mGotRoot + " | " + - "mSuLaunchesShell:" + mSuLaunchesShell + " | " + - "mBinaryIssue:" + mBinaryIssue; - } - - public RootInfo(Parcel in) { - this(); - mGotRoot = in.readByte() != 0; - mSuLaunchesShell = in.readByte() != 0; - mBinaryIssue = in.readByte() != 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeByte((byte) (mGotRoot ? 1 : 0)); - out.writeByte((byte) (mSuLaunchesShell ? 1 : 0)); - out.writeByte((byte) (mBinaryIssue ? 1 : 0)); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new BusyBoxInfo.Creator() { - public RootInfo createFromParcel(Parcel in) { - return new RootInfo(in); - } - - public RootInfo[] newArray(int size) { - return new RootInfo[size]; - } - }; - -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootTest.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootTest.java deleted file mode 100644 index 5d65b00..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/root/RootTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.root; - -import android.content.Context; - -import java.io.File; - -import eu.thedarken.rootvalidator.tests.ATest; -import eu.thedarken.rootvalidator.tools.Cmd; -import eu.thedarken.rootvalidator.tools.Logy; - -public class RootTest extends ATest { - private static final String TAG = "RV:RootTest"; - - public RootTest(Context context) { - super(context); - } - - @Override - public RootInfo test() { - RootInfo result = new RootInfo(); - Cmd cmd = new Cmd(); - cmd.setRaw(result.getRaw()); - cmd.setRuntimeExec("su"); - cmd.addCommand("echo -RVEOF-"); - cmd.addCommand("id"); - cmd.execute(); - result.mExitCode = cmd.getExitCode(); - if (cmd.getExitCode() == Cmd.OK || cmd.getExitCode() == Cmd.OUT_OF_RANGE) { - for (String line : cmd.getOutput()) { - if (line.contains("uid=0")) { - Logy.i(TAG, "Root try successfull, we got ROOT :D!"); - result.mGotRoot = true; - } else if (line.contains("-RVEOF-")) { - result.mSuLaunchesShell = true; - } - } - if (result.mSuLaunchesShell && !result.mGotRoot) - Logy.i(TAG, "'su' launches a shell, but 'id' seemed to fail."); - } else if (cmd.getExitCode() == Cmd.COMMAND_NOT_FOUND) { - // System has no id binary??? - Logy.i(TAG, "IOException, System has no id or echo binary?"); - result.mBinaryIssue = true; - cmd.clearCommands(); - cmd.addCommand("echo -RVEOF-"); - File testFolder = new File("/cache/rootvalidator.tmp"); - cmd.addCommand("echo test > " + testFolder.getAbsolutePath()); - cmd.execute(); - - if (cmd.getExitCode() == Cmd.OK) { - result.mGotRoot = true; - result.mSuLaunchesShell = true; - } else { - for (String line : cmd.getOutput()) { - if (line.contains("-RVEOF-")) { - Logy.i(TAG, "'su' launched a shell, but write test was unsuccessful (" + testFolder.getAbsolutePath() + ")"); - result.mSuLaunchesShell = true; - } - } - } - } else if (cmd.getExitCode() == Cmd.PROBLEM) { - // Unknown error, check if su was denied by su app? - Logy.i(TAG, "Couldn't launch root shell."); - } - Logy.d(TAG, result.toString()); - return result; - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxInfo.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxInfo.java deleted file mode 100644 index c7d0635..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxInfo.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.selinux; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.tests.BP; -import eu.thedarken.rootvalidator.tests.Result; -import eu.thedarken.rootvalidator.tests.TestInfo; -import eu.thedarken.rootvalidator.tools.ApiHelper; - -public class SELinuxInfo extends TestInfo { - private State mState = State.UNKNOWN; - - public enum State { - UNKNOWN, DISABLED, PERMISSIVE, ENFORCING - } - - public SELinuxInfo() { - super("SELinux Test"); - } - - public State getState() { - return mState; - } - - public void setState(State state) { - mState = state; - } - - @Override - public Result.Outcome getOutcome() { - if (getState() == State.ENFORCING) { - return Result.Outcome.NEUTRAL; - } else if (getState() == State.UNKNOWN && ApiHelper.hasJellyBeanMR1()) { - return Result.Outcome.NEGATIVE; - } else { - return Result.Outcome.POSITIVE; - } - } - - @Override - public String getPrimaryInfo(Context context) { - if (getState() == State.ENFORCING) { - return "SELinux is enforcing"; - } else if (getState() == State.PERMISSIVE) { - return "SELinux is permissive"; - } else if (getState() == State.DISABLED) { - return "SELinux is disabled"; - } else { - return "SELinux state is unknown"; - } - } - - @Override - public List getCriterias(Context context) { - return new ArrayList<>(); - } - - @Override - public String toString() { - return "mState:" + mState.name(); - } - - public SELinuxInfo(Parcel in) { - this(); - mState = State.valueOf(in.readString()); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mState.name()); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - public SELinuxInfo createFromParcel(Parcel in) { - return new SELinuxInfo(in); - } - - public SELinuxInfo[] newArray(int size) { - return new SELinuxInfo[size]; - } - }; -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxTest.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxTest.java deleted file mode 100644 index 152be9f..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/selinux/SELinuxTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.selinux; - -import android.content.Context; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; - -import eu.thedarken.rootvalidator.tests.ATest; -import eu.thedarken.rootvalidator.tools.Cmd; -import eu.thedarken.rootvalidator.tools.Logy; - -public class SELinuxTest extends ATest { - private final static String SELINUX_GETENFORCE_DISABLED = "Disabled"; - private final static String SELINUX_GETENFORCE_PERMISSIVE = "Permissive"; - private final static String SELINUX_GETENFORCE_ENFORCING = "Enforcing"; - private static final String TAG = "RV:SELinuxTest"; - - public SELinuxTest(Context context) { - super(context); - } - - @Override - public SELinuxInfo test() { - SELinuxInfo result = new SELinuxInfo(); - // First known firmware with SELinux built-in was a 4.2 (17) leak - if (android.os.Build.VERSION.SDK_INT >= 17) { - if (result.getState() == SELinuxInfo.State.UNKNOWN) { - // Detect enforcing through sysfs, not always present - File f = new File("/sys/fs/selinux/enforce"); - if (f.exists()) { - try { - InputStream is = new FileInputStream(f.getAbsoluteFile()); - try { - int value = is.read(); - result.getRaw().add("read:" + f.getAbsolutePath() + " == " + value); - if (value == '1') { - result.setState(SELinuxInfo.State.ENFORCING); - } - } finally { - is.close(); - } - } catch (Exception e) { - result.getRaw().add("Couldn't read " + f.getAbsolutePath()); - } - } else { - result.getRaw().add("Does not exist: " + f.getAbsolutePath()); - } - } - if (result.getState() == SELinuxInfo.State.UNKNOWN) { - Cmd cmd = new Cmd(); - cmd.setRaw(result.getRaw()); - cmd.addCommand("getenforce"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK) { - for (String line : cmd.getOutput()) { - if (line.contains(SELINUX_GETENFORCE_DISABLED)) { - result.setState(SELinuxInfo.State.DISABLED); - break; - } else if (line.contains(SELINUX_GETENFORCE_PERMISSIVE)) { - result.setState(SELinuxInfo.State.PERMISSIVE); - break; - } else if (line.contains(SELINUX_GETENFORCE_ENFORCING)) { - result.setState(SELinuxInfo.State.ENFORCING); - break; - } - } - } - } - } - Logy.d(TAG, result.toString()); - return result; - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuApp.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuApp.java deleted file mode 100644 index e077ed4..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuApp.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.suapp; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.io.File; - -import eu.thedarken.rootvalidator.tests.busybox.BusyBoxInfo; -import eu.thedarken.rootvalidator.tests.subinary.SuBinary; - -public class SuApp implements Parcelable { - private final String mPackageName; - String mVersionName; - int mVersionCode; - String mName; - File mPath; - SuBinary.Type mType; - boolean mSystemApp = false; - - - public SuApp(String packageName) { - this.mPackageName = packageName; - } - - public String getName() { - return mName; - } - - public boolean isSystemApp() { - return mSystemApp; - } - - public String getPackageName() { - return mPackageName; - } - - public String getVersionName() { - return mVersionName; - } - - public int getVersionCode() { - return mVersionCode; - } - - public File getPath() { - return mPath; - } - - public SuBinary.Type getType() { - return mType; - } - - @Override - public String toString() { - return "mPackageName:" + mPackageName + " | " + - "mVersionName:" + mVersionName + " | " + - "mVersionCode:" + mVersionCode + " | " + - "mName:" + mName + " | " + - "mPrimaryPath:" + mPath + " | " + - "mSystemApp:" + mSystemApp; - } - - public SuApp(Parcel in) { - mPackageName = in.readString(); - mVersionName = in.readString(); - mVersionCode = in.readInt(); - mName = in.readString(); - mPath = new File(in.readString()); - mSystemApp = in.readByte() != 0; - mType = SuBinary.Type.valueOf(in.readString()); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mPackageName); - out.writeString(mVersionName); - out.writeInt(mVersionCode); - out.writeString(mName); - out.writeString(mPath.getAbsolutePath()); - out.writeByte((byte) (mSystemApp ? 1 : 0)); - out.writeString(mType.name()); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new BusyBoxInfo.Creator() { - public SuApp createFromParcel(Parcel in) { - return new SuApp(in); - } - - public SuApp[] newArray(int size) { - return new SuApp[size]; - } - }; -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuAppInfo.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuAppInfo.java deleted file mode 100644 index af12d2a..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuAppInfo.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.suapp; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.tests.BP; -import eu.thedarken.rootvalidator.tests.Result; -import eu.thedarken.rootvalidator.tests.TestInfo; - -public class SuAppInfo extends TestInfo { - private final ArrayList mSuApps = new ArrayList<>(); - - public SuAppInfo() { - super("SuperUser App Test"); - } - - public List getSuperUserApps() { - return mSuApps; - } - - @Override - public Result.Outcome getOutcome() { - if (getSuperUserApps().size() > 1) { - return Result.Outcome.NEUTRAL; - } else if (getSuperUserApps().size() == 1) { - return Result.Outcome.POSITIVE; - } else { - return Result.Outcome.NEGATIVE; - } - } - - @Override - public String getPrimaryInfo(Context context) { - if (getSuperUserApps().size() > 1) { - return "Multiple superuser apps"; - } else if (getSuperUserApps().size() == 1) { - return "Superuser app found"; - } else { - return "No superuser app found"; - } - } - - @Override - public List getCriterias(Context context) { - List criterias = new ArrayList<>(); - for (SuApp suApp : mSuApps) { - String output = (suApp.getType() + "\n") - + suApp.getPackageName() + " @ " + suApp.getVersionName() + " (" + suApp.getVersionCode() + ")\n" - + suApp.getPath().getAbsolutePath(); - criterias.add(new BP(getPositiveD(context), output)); - } - return criterias; - } - - public SuAppInfo(Parcel in) { - this(); - in.readList(mSuApps, SuApp.class.getClassLoader()); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeList(mSuApps); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - public SuAppInfo createFromParcel(Parcel in) { - return new SuAppInfo(in); - } - - public SuAppInfo[] newArray(int size) { - return new SuAppInfo[size]; - } - }; -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuperUserAppTest.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuperUserAppTest.java deleted file mode 100644 index 033315f..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/suapp/SuperUserAppTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.suapp; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import eu.thedarken.rootvalidator.tests.ATest; -import eu.thedarken.rootvalidator.tests.subinary.SuBinary; -import eu.thedarken.rootvalidator.tests.subinary.SuBinaryInfo; -import eu.thedarken.rootvalidator.tools.Logy; - -public class SuperUserAppTest extends ATest { - - private static final String TAG = "RV:SuperUserAppTest"; - private static final Map SUAPP_MAPPING; - private final SuBinaryInfo mResultSuBinaryTest; - - static { - SUAPP_MAPPING = new HashMap<>(); - SUAPP_MAPPING.put(SuBinary.Type.CHAINFIRE_SUPERSU, new String[]{"eu.chainfire.supersu"}); - SUAPP_MAPPING.put(SuBinary.Type.KOUSH_SUPERUSER, new String[]{"com.koushikdutta.superuser"}); - SUAPP_MAPPING.put(SuBinary.Type.CHAINSDD_SUPERUSER, new String[]{"com.noshufou.android.su"}); - SUAPP_MAPPING.put(SuBinary.Type.KINGUSER, new String[]{"com.kingroot.kinguser"}); - SUAPP_MAPPING.put(SuBinary.Type.VROOT, new String[]{"com.mgyun.shua.su", "com.mgyun.superuser"}); - SUAPP_MAPPING.put(SuBinary.Type.VENOMSU, new String[]{"com.m0narx.su"}); - SUAPP_MAPPING.put(SuBinary.Type.KINGOUSER, new String[]{"com.kingouser.com"}); - SUAPP_MAPPING.put(SuBinary.Type.MIUI, new String[]{"com.miui.uac", "com.lbe.security.miui"}); - SUAPP_MAPPING.put(SuBinary.Type.CYANOGENMOD, new String[]{"com.android.settings"}); - SUAPP_MAPPING.put(SuBinary.Type.QIHOO_360, new String[]{"com.qihoo.permmgr"}); - SUAPP_MAPPING.put(SuBinary.Type.QIHOO_360, new String[]{"com.qihoo.permroot"}); - SUAPP_MAPPING.put(SuBinary.Type.MIUI, new String[]{"com.lbe.security.miui"}); - SUAPP_MAPPING.put(SuBinary.Type.BAIDU_EASYROOT, new String[]{"com.baidu.easyroot"}); - SUAPP_MAPPING.put(SuBinary.Type.DIANXINOSSUPERUSER, new String[]{"com.dianxinos.superuser"}); - SUAPP_MAPPING.put(SuBinary.Type.BAIYI_MOBILE_EASYROOT, new String[]{"com.baiyi_mobile.easyroot"}); - SUAPP_MAPPING.put(SuBinary.Type.TENCENT_APPMANAGER, new String[]{"com.tencent.qrom.appmanager"}); - SUAPP_MAPPING.put(SuBinary.Type.SE_SUPERUSER, new String[]{"me.phh.superuser"}); - } - - public SuperUserAppTest(Context context, SuBinaryInfo resultSuBinaryTest) { - super(context); - mResultSuBinaryTest = resultSuBinaryTest; - } - - @Override - public SuAppInfo test() { - SuAppInfo result = new SuAppInfo(); - for (Map.Entry entry : SUAPP_MAPPING.entrySet()) { - // Otherwise we show the settings app as superuser app on stock ROMs - if (mResultSuBinaryTest.getPrimary() == null || mResultSuBinaryTest.getPrimary().getType() != SuBinary.Type.CYANOGENMOD && entry.getKey() == SuBinary.Type.CYANOGENMOD) - continue; - for (String pkg : entry.getValue()) { - try { - PackageInfo app = getContext().getPackageManager().getPackageInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES); - SuApp suApp = new SuApp(app.packageName); - suApp.mVersionName = app.versionName; - suApp.mVersionCode = app.versionCode; - - if (app.applicationInfo != null) { - suApp.mName = (String) app.applicationInfo.loadLabel(getContext().getPackageManager()); - suApp.mPath = new File(app.applicationInfo.publicSourceDir); - suApp.mSystemApp = (app.applicationInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; - } - suApp.mType = entry.getKey(); - result.getSuperUserApps().add(suApp); - } catch (PackageManager.NameNotFoundException ignored) { - } - } - } - for (SuApp suApp : result.getSuperUserApps()) - Logy.d(TAG, suApp.toString()); - return result; - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinary.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinary.java deleted file mode 100644 index 5023192..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinary.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.subinary; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.io.File; - -import eu.thedarken.rootvalidator.tests.busybox.BusyBoxInfo; - -public class SuBinary implements Parcelable { - - public enum Type { - CHAINFIRE_SUPERSU, - KOUSH_SUPERUSER, - KINGUSER, - VROOT, - KINGOUSER, - MIUI, - VENOMSU, - CYANOGENMOD, - CHAINSDD_SUPERUSER, - BAIDU_EASYROOT, - QIHOO_360, - DIANXINOSSUPERUSER, - BAIYI_MOBILE_EASYROOT, - TENCENT_APPMANAGER, - SE_SUPERUSER, - UNKNOWN, - NONE - } - - final File mPath; - final boolean mPrimary; - Type mType = Type.NONE; - String mVersion; - String mExtra; - String mPermission; - String mOwner; - String mGroup; - - public SuBinary(File path, boolean primary) { - this.mPath = path; - this.mPrimary = primary; - } - - public Type getType() { - return mType; - } - - public String getVersion() { - return mVersion; - } - - public String getExtra() { - return mExtra; - } - - public File getPath() { - return mPath; - } - - public String getPermission() { - return mPermission; - } - - public boolean isPrimary() { - return mPrimary; - } - - public String getOwner() { - return mOwner; - } - - public String getGroup() { - return mGroup; - } - - @Override - public String toString() { - return "mType:" + mType + " | " + - "mVersion:" + mVersion + " | " + - "mExtra:" + mExtra + " | " + - "mOwner:" + mOwner + " | " + - "mGroup:" + mGroup + " | " + - "primary:" + mPrimary + " | " + - "mPath:" + mPath + " | " + - "mPermission:" + mPermission; - } - - public SuBinary(Parcel in) { - mPath = new File(in.readString()); - mPrimary = in.readByte() != 0; - mType = Type.valueOf(in.readString()); - mVersion = in.readString(); - mExtra = in.readString(); - mPermission = in.readString(); - mOwner = in.readString(); - mGroup = in.readString(); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mPath.getAbsolutePath()); - out.writeByte((byte) (mPrimary ? 1 : 0)); - out.writeString(mType.name()); - out.writeString(mVersion); - out.writeString(mExtra); - out.writeString(mPermission); - out.writeString(mOwner); - out.writeString(mGroup); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new BusyBoxInfo.Creator() { - public SuBinary createFromParcel(Parcel in) { - return new SuBinary(in); - } - - public SuBinary[] newArray(int size) { - return new SuBinary[size]; - } - }; - -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryInfo.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryInfo.java deleted file mode 100644 index 3531683..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.subinary; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.tests.BP; -import eu.thedarken.rootvalidator.tests.Result; -import eu.thedarken.rootvalidator.tests.TestInfo; - -public class SuBinaryInfo extends TestInfo { - private final List mSuBinaries = new ArrayList<>(); - - public SuBinaryInfo() { - super("SuBinary Test"); - } - - public SuBinary getPrimary() { - for (SuBinary suBinary : mSuBinaries) { - if (suBinary.isPrimary()) { - return suBinary; - } - } - return null; - } - - public List getSuBinaries() { - return mSuBinaries; - } - - @Override - public Result.Outcome getOutcome() { - if (getSuBinaries().isEmpty() || (getSuBinaries().size() == 1 && getPrimary() == null)) { - return Result.Outcome.NEGATIVE; - } else if (getSuBinaries().size() > 1) { - return Result.Outcome.NEUTRAL; - } else { - return Result.Outcome.POSITIVE; - } - } - - @Override - public String getPrimaryInfo(Context context) { - return "Superuser binary"; - } - - @Override - public List getCriterias(Context context) { - List criterias = new ArrayList<>(); - SuBinary primSU = getPrimary(); - if (primSU != null) { - criterias.add(new BP(getPositiveD(context), "su binary is available via $PATH.")); - } else { - criterias.add(new BP(getNegativeD(context), "su binary not available via $PATH.")); - } - for (SuBinary sub : mSuBinaries) { - Drawable d = getNeutralD(context); - if (sub.isPrimary()) - d = getPositiveD(context); - String output = (sub.getType() + "\n") - + sub.getVersion() + " " + sub.getExtra() + "\n" - + sub.getPermission() + " " + sub.getOwner() + " " + sub.getGroup() + "\n" - + sub.getPath().getAbsolutePath(); - criterias.add(new BP(d, output)); - } - return criterias; - } - - public SuBinaryInfo(Parcel in) { - this(); - in.readList(mSuBinaries, SuBinary.class.getClassLoader()); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeList(mSuBinaries); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - public SuBinaryInfo createFromParcel(Parcel in) { - return new SuBinaryInfo(in); - } - - public SuBinaryInfo[] newArray(int size) { - return new SuBinaryInfo[size]; - } - }; -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryTest.java b/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryTest.java deleted file mode 100644 index 9616852..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tests/subinary/SuBinaryTest.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tests.subinary; - -import android.content.Context; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import eu.thedarken.rootvalidator.tests.ATest; -import eu.thedarken.rootvalidator.tools.Cmd; -import eu.thedarken.rootvalidator.tools.Logy; - -public class SuBinaryTest extends ATest { - private static final String TAG = "RV:SuBinaryTest"; - private static final Map PATTERNMAP; - - static { - PATTERNMAP = new HashMap<>(); - // Chainfire SU "2.25:SUPERSU" - PATTERNMAP.put(Pattern.compile("^([0-9\\.]*):(SUPERSU)$"), SuBinary.Type.CHAINFIRE_SUPERSU); - // Koush SU "16 com.koushikdutta.superuser" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.koushikdutta\\.superuser)$"), SuBinary.Type.KOUSH_SUPERUSER); - // KingUser "3.43:kinguser_su" - PATTERNMAP.put(Pattern.compile("^([0-9\\.]*):(kinguser_su)$"), SuBinary.Type.KINGUSER); - // KingoRoot "13 com.kingouser.com" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.kingouser\\.com)$"), SuBinary.Type.KINGOUSER); - // Cyanogen Mod e.g. "16 com.android.settings" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.android\\.settings)$"), SuBinary.Type.CYANOGENMOD); - // Cyanogen Mod clone e.g. "16 cm-su" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(cm-su)$"), SuBinary.Type.CYANOGENMOD); - // ChainsDD "3.3" or "3.1l" or "2.3.1-abcdefgh" etc. - PATTERNMAP.put(Pattern.compile("^(3\\.(?:3|2|1|0))(l?)$"), SuBinary.Type.CHAINSDD_SUPERUSER); - PATTERNMAP.put(Pattern.compile("^(3\\.0)-(beta2)$"), SuBinary.Type.CHAINSDD_SUPERUSER); - PATTERNMAP.put(Pattern.compile("^(3\\.1\\.1)(l?)$"), SuBinary.Type.CHAINSDD_SUPERUSER); - PATTERNMAP.put(Pattern.compile("^(3\\.0\\.3\\.2)(l?)$"), SuBinary.Type.CHAINSDD_SUPERUSER); - PATTERNMAP.put(Pattern.compile("^(3\\.0\\.(?:3|2|1))(l?)$"), SuBinary.Type.CHAINSDD_SUPERUSER); - PATTERNMAP.put(Pattern.compile("^(2.3.(?:1|2))(-[abcdefgh]{1,8})$"), SuBinary.Type.CHAINSDD_SUPERUSER); - // VROOT "11 com.mgyun.shua.su" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.mgyun\\.shua\\.su)$"), SuBinary.Type.VROOT); - // VenomSU, TEAM Venom "Venom SuperUser v21" - PATTERNMAP.put(Pattern.compile("^(?:Venom\\WSuperUser)\\W(v[0-9]+)$"), SuBinary.Type.VENOMSU); - // Qihoo 360 "360.cn es 1.6.0.6" com.qihoo.permmgr - PATTERNMAP.put(Pattern.compile("^(360\\Wcn\\Wes)\\W?([0-9\\.]+)$"), SuBinary.Type.QIHOO_360); - // MIUI "15 com.lbe.security.miui" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.lbe\\.security\\.miui)$"), SuBinary.Type.MIUI); - // Baidu Easyroot "15 com.baidu.easyroot" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.baidu\\.easyroot)$"), SuBinary.Type.BAIDU_EASYROOT); - // Koush SuperUser clone "26 com.dianxinos.superuser" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.dianxinos\\.superuser)$"), SuBinary.Type.DIANXINOSSUPERUSER); - // Koush SuperUser clone "16 com.baiyi_mobile.easyroot" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.baiyi_mobile\\.easyroot)$"), SuBinary.Type.BAIYI_MOBILE_EASYROOT); - // CyanogenMod SuperUser clone "16 com.tencent.qrom.appmanager" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(com\\.tencent\\.qrom\\.appmanager)$"), SuBinary.Type.TENCENT_APPMANAGER); - // https://github.com/seSuperuser/Superuser "16 me.phh.superuser cm-su" - PATTERNMAP.put(Pattern.compile("^([0-9]*)\\W(me\\.phh\\.superuser)\\W([\\W\\w]*)$"), SuBinary.Type.SE_SUPERUSER); - } - - private List mRaw; - - public SuBinaryTest(Context context) { - super(context); - } - - @Override - public SuBinaryInfo test() { - SuBinaryInfo result = new SuBinaryInfo(); - mRaw = result.getRaw(); - result.getSuBinaries().addAll(locateSuBinaries()); - for (SuBinary binary : result.getSuBinaries()) { - checkBinary(binary); - Logy.d(TAG, binary.toString()); - } - return result; - } - - private static final Pattern SUBINARY_PERMISSION_PATTERN = Pattern.compile("^([\\w-]+)\\s+([\\w]+)\\s+([\\w]+)(?:[\\W\\w]+)$"); - - - private static final Pattern TYPE_LOCATE_PATTERN = Pattern.compile("^(?:su)\\s(?:[\\w\\s]+)\\s((?:\\/[\\w]+)+)$"); - - private List locateSuBinaries() { - HashSet suBinaryLocations = new HashSet<>(); - HashSet possibleLocations = new HashSet<>(); - - Cmd cmd = new Cmd(); - cmd.setRaw(mRaw); - cmd.addCommand("echo $PATH"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() == 1) { - for (String s : Arrays.asList(cmd.getOutput().get(0).split(":"))) { - possibleLocations.add(s + "/su"); - } - } - - // More possible locations - possibleLocations.add("/sbin/su"); - possibleLocations.add("/system/bin/su"); - possibleLocations.add("/system/xbin/su"); - possibleLocations.add("/system/bin/failsafe/su"); - possibleLocations.add("/system/sd/xbin/su"); - possibleLocations.add("/data/local/su"); - possibleLocations.add("/data/local/bin/su"); - possibleLocations.add("/data/local/xbin/su"); - - for (String s : possibleLocations) { - cmd.clearCommands(); - cmd.addCommand("ls " + s); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - suBinaryLocations.add(s); - } - } - - String primarySuBinary = null; - cmd.clearCommands(); - cmd.addCommand("su --version"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK) { - // su binary was in mPath - cmd.clearCommands(); - cmd.addCommand("type su"); - cmd.execute(); - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - Matcher matcher = TYPE_LOCATE_PATTERN.matcher(cmd.getOutput().get(0)); - if (matcher.matches()) { - suBinaryLocations.add(matcher.group(1)); - primarySuBinary = matcher.group(1); - } - } - } - List binaries = new ArrayList<>(); - for (String suPath : suBinaryLocations) { - boolean primary = suPath.equals(primarySuBinary); - binaries.add(new SuBinary(new File(suPath), primary)); - } - return binaries; - } - - private void checkBinary(SuBinary binary) { - Cmd cmd = new Cmd(); - cmd.setRaw(mRaw); - cmd.setTimeout(5000); - cmd.addCommand("ls -l " + binary.getPath().getAbsolutePath()); - cmd.execute(); - - if (cmd.getExitCode() == Cmd.OK && cmd.getOutput().size() > 0) { - Matcher matcher = SUBINARY_PERMISSION_PATTERN.matcher(cmd.getOutput().get(0)); - if (matcher.matches()) { - binary.mPermission = matcher.group(1); - binary.mOwner = matcher.group(2); - binary.mGroup = matcher.group(3); - } - } - - cmd.clearCommands(); - cmd.addCommand("su --version"); - cmd.execute(); - if (cmd.getExitCode() != Cmd.OK && cmd.getExitCode() != Cmd.COMMAND_NOT_FOUND) { - cmd.clearCommands(); - cmd.addCommand("su --V"); - cmd.addCommand("su -version"); - cmd.addCommand("su -v"); - cmd.addCommand("su -V"); - cmd.execute(); - } - if (cmd.getExitCode() != Cmd.COMMAND_NOT_FOUND) - binary.mType = SuBinary.Type.UNKNOWN; - if (cmd.getExitCode() == Cmd.OK) { - for (String line : cmd.getOutput()) { - for (Map.Entry entry : PATTERNMAP.entrySet()) { - Matcher matcher = entry.getKey().matcher(line); - if (matcher.matches()) { - binary.mType = entry.getValue(); - if (matcher.groupCount() == 1) { - binary.mVersion = matcher.group(1); - } else if (matcher.groupCount() == 2) { - binary.mExtra = matcher.group(2); - } - break; - } - } - } - } - } - -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagErrorHandler.java b/app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagErrorHandler.java new file mode 100644 index 0000000..7067326 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagErrorHandler.java @@ -0,0 +1,27 @@ +package eu.thedarken.rootvalidator.tools; + + +import com.bugsnag.android.BeforeNotify; + +import javax.inject.Inject; + +import eu.thedarken.rootvalidator.AppComponent; +import eu.thedarken.rootvalidator.BuildConfig; + + +@AppComponent.Scope +public class BugsnagErrorHandler implements BeforeNotify { + private final BugsnagTree bugsnagTree; + + @Inject + public BugsnagErrorHandler(BugsnagTree bugsnagTree) { + this.bugsnagTree = bugsnagTree; + } + + @Override + public boolean run(com.bugsnag.android.Error error) { + bugsnagTree.update(error); + + return !BuildConfig.DEBUG; + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagTree.java b/app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagTree.java new file mode 100644 index 0000000..c8914f7 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/tools/BugsnagTree.java @@ -0,0 +1,61 @@ +package eu.thedarken.rootvalidator.tools; + +import android.support.annotation.NonNull; +import android.util.Log; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Locale; + +import javax.inject.Inject; + +import eu.thedarken.rootvalidator.AppComponent; +import timber.log.Timber; + +@AppComponent.Scope +public class BugsnagTree extends Timber.DebugTree { + private static final int BUFFER_SIZE = 200; + + // Adding one to the initial size accounts for the add before remove. + private final Deque buffer = new ArrayDeque<>(BUFFER_SIZE + 1); + + @Inject + public BugsnagTree() { + } + + @Override + protected void log(int priority, String tag, String message, Throwable t) { + message = System.currentTimeMillis() + " " + priorityToString(priority) + "/" + tag + ": " + message; + synchronized (buffer) { + buffer.addLast(message); + if (buffer.size() > BUFFER_SIZE) { + buffer.removeFirst(); + } + } + } + + public void update(@NonNull com.bugsnag.android.Error error) { + synchronized (buffer) { + int i = 1; + for (String message : buffer) error.addToTab("Log", String.format(Locale.US, "%03d", i++), message); + error.addToTab("Log", String.format(Locale.US, "%03d", i), Log.getStackTraceString(error.getException())); + } + } + + private static String priorityToString(int priority) { + switch (priority) { + case Log.ERROR: + return "E"; + case Log.WARN: + return "W"; + case Log.INFO: + return "I"; + case Log.DEBUG: + return "D"; + case Log.VERBOSE: + return "V"; + default: + return String.valueOf(priority); + } + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tools/Cmd.java b/app/src/main/java/eu/thedarken/rootvalidator/tools/Cmd.java deleted file mode 100644 index 22a95a2..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tools/Cmd.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tools; - -import android.util.Log; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.List; - -import eu.thedarken.rootvalidator.BuildConfig; - -public class Cmd { - public static final int INIT = 99; - public static final int OK = 0; - public static final int PROBLEM = 1; - - public static final int COMMAND_NOT_FOUND = 127; - public static final int INTERRUPTED = 130; - public static final int OUT_OF_RANGE = 255; - private int mExitcode = INIT; - private Integer mTimeout = 0; - private Executor mExecutor; - private final ArrayList mCommands = new ArrayList<>(); - private ArrayList mOutput = new ArrayList<>(); - private ArrayList mErrors = new ArrayList<>(); - private long SHELLDELAY = 100; - private boolean DEBUG = BuildConfig.DEBUG; - private final String TAG = Cmd.class.getName(); - private boolean mUseExit = true; - private String mRuntimeExec = "sh"; - private List mRaw; - - public void execute() { - mExecutor = new Executor(mCommands); - if (DEBUG) { - Log.d(TAG, "SHELLDELAY:" + SHELLDELAY); - } - mExecutor.start(); - try { - if (mTimeout == 0) { - mExecutor.join(); - } else { - Log.i(TAG, "timeout is " + mTimeout); - mExecutor.join(mTimeout); - } - if (mOutput == null) - mOutput = new ArrayList<>(); - if (mErrors == null) - mErrors = new ArrayList<>(); - } catch (InterruptedException e) { - mExecutor.interrupt(); - Thread.currentThread().interrupt(); - } - } - - private class Executor extends Thread { - private final String TAG = Executor.class.getName(); - private Process q = null; - private ArrayList commands = new ArrayList<>(); - private int exitcode = INIT; - private StreamHarvester error_harvester; - private StreamHarvester output_harvester; - - public Executor(ArrayList commands) { - this.commands = commands; - } - - @Override - public void run() { - try { - if (mRaw != null) - mRaw.add("### START ###"); - if (SHELLDELAY > 0) - Thread.sleep(SHELLDELAY); - if (mRaw != null) - mRaw.add("Process:" + mRuntimeExec); - q = Runtime.getRuntime().exec(mRuntimeExec); - OutputStreamWriter os = new OutputStreamWriter(q.getOutputStream()); - // loop commands - for (String s : commands) { - os.write(s + "\n"); - os.flush(); - if (DEBUG) - Log.d(TAG, s); - } - if (mUseExit) - os.write("exit\n"); - os.flush(); - os.close(); - - error_harvester = new StreamHarvester(q.getErrorStream(), "Error"); - output_harvester = new StreamHarvester(q.getInputStream(), "Output"); - - error_harvester.start(); - output_harvester.start(); - - exitcode = q.waitFor(); - - error_harvester.join(); - output_harvester.join(); - - if (DEBUG) - Log.d(this.TAG, "Exitcode: " + exitcode); - } catch (InterruptedException interrupt) { - if (DEBUG) - Log.w(this.TAG, "Interrupted!"); - exitcode = INTERRUPTED; - return; - } catch (IOException e) { - if (DEBUG) - Log.w(this.TAG, "IOException, command failed? not found?"); - exitcode = COMMAND_NOT_FOUND; - } finally { - if (q != null) - q.destroy(); - if (output_harvester != null) - Cmd.this.mOutput = output_harvester.getHarvest(); - if (error_harvester != null) - Cmd.this.mErrors = error_harvester.getHarvest(); - Cmd.this.mExitcode = this.exitcode; - - if (mRaw != null) { - mRaw.addAll(this.commands); - mRaw.add("Exitcode:" + Cmd.this.mExitcode); - mRaw.addAll(Cmd.this.mOutput); - mRaw.addAll(Cmd.this.mErrors); - mRaw.add("### END ###"); - mRaw.add(" "); - } - } - } - } - - public void setRaw(List raw) { - mRaw = raw; - } - - public String getRuntimeExec() { - return mRuntimeExec; - } - - public void setRuntimeExec(String runtimeExec) { - mRuntimeExec = runtimeExec; - } - - public void addCommand(String c) { - mCommands.add(c); - } - - public void clearCommands() { - this.mCommands.clear(); - } - - public void setTimeout(int ms) { - mTimeout = ms; - } - - public void useExit(boolean useExit) { - this.mUseExit = useExit; - } - - public void setShellDelay(long ms) { - SHELLDELAY = ms; - } - - public int getCommandCount() { - return this.mCommands.size(); - } - - public ArrayList getOutput() { - return mOutput; - } - - public ArrayList getErrors() { - return mErrors; - } - - public int getExitCode() { - return mExitcode; - } - - class StreamHarvester extends Thread { - private final String TAG = StreamHarvester.class.getName(); - private InputStream is; - private ArrayList output = new ArrayList(); - private String type; - - StreamHarvester(InputStream is, String type) { - this.is = is; - this.type = type; - } - - public void run() { - try { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - String line; - while ((line = br.readLine()) != null) { - output.add(line); - if (DEBUG) - Log.v(this.TAG, type + ":" + output.size() + ":" + line); - } - br.close(); - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - - public ArrayList getHarvest() { - return output; - } - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tools/Logy.java b/app/src/main/java/eu/thedarken/rootvalidator/tools/Logy.java deleted file mode 100644 index 9d04489..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/tools/Logy.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.tools; - -import android.util.Log; - -import eu.thedarken.rootvalidator.BuildConfig; - -public class Logy { - public static final int SILENT = -1; - public static final int NORMAL = 0; - public static final int DEBUG = 1; - public static final int VERBOSE = 2; - - public static int sLoglevel = BuildConfig.DEBUG ? VERBOSE : NORMAL; - - public static void v(String c, String s) { - if (sLoglevel >= VERBOSE) { - if (s == null) - s = "\"NULL\""; - Log.v(c, s); - } - } - - public static void d(String c, String s) { - if (sLoglevel >= DEBUG) { - if (s == null) - s = "\"NULL\""; - Log.d(c, s); - } - } - - public static void i(String c, String s) { - if (sLoglevel >= NORMAL) { - if (s == null) - s = "\"NULL\""; - Log.i(c, s); - } - } - - public static void w(String c, String s) { - if (sLoglevel >= SILENT) { - if (s == null) - s = "\"NULL\""; - Log.w(c, s); - } - } - - public static void e(String c, String s) { - if (sLoglevel >= SILENT) { - if (s == null) - s = "\"NULL\""; - Log.e(c, s); - } - } - -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tools/ShareHelper.java b/app/src/main/java/eu/thedarken/rootvalidator/tools/ShareHelper.java index 7c38f3e..c7addf2 100644 --- a/app/src/main/java/eu/thedarken/rootvalidator/tools/ShareHelper.java +++ b/app/src/main/java/eu/thedarken/rootvalidator/tools/ShareHelper.java @@ -11,26 +11,25 @@ import android.content.Intent; import android.widget.Toast; -import java.util.ArrayList; +import java.util.List; import eu.thedarken.rootvalidator.R; public class ShareHelper { - public static void share(Activity activity, String subject, ArrayList out) { + public static void share(Activity activity, String subject, List out) { Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject); StringBuilder _printOut = new StringBuilder(); - for (String s : out) - _printOut.append(s + "\n"); + for (String s : out) _printOut.append(s + "\n"); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, _printOut.toString()); try { activity.startActivity(Intent.createChooser(sharingIntent, activity.getResources().getString(R.string.share))); } catch (Exception e) { e.printStackTrace(); - Toast.makeText(activity, "No app available to share results.", Toast.LENGTH_SHORT).show(); + Toast.makeText(activity, R.string.msg_error_no_app_share, Toast.LENGTH_SHORT).show(); } } } diff --git a/app/src/main/java/eu/thedarken/rootvalidator/tools/StringUtils.java b/app/src/main/java/eu/thedarken/rootvalidator/tools/StringUtils.java new file mode 100644 index 0000000..8a58ff0 --- /dev/null +++ b/app/src/main/java/eu/thedarken/rootvalidator/tools/StringUtils.java @@ -0,0 +1,44 @@ +package eu.thedarken.rootvalidator.tools; + + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +public class StringUtils { + public static String join(@Nullable Object[] objects) { + if (objects == null) return null; + return join(Arrays.asList(objects)); + } + + public static String join(@Nullable Collection collection) { + return join(collection, ", "); + } + + public static String join(@Nullable Collection collection, @NonNull String separator) { + if (collection == null) return null; + if (collection.isEmpty()) return "{}"; + int total = collection.size() * separator.length(); + for (Object s : collection) { + if (s != null) total += s.toString().length(); + } + + StringBuilder sb = new StringBuilder(total + 2); + sb.append("{"); + Iterator it = collection.iterator(); + while (it.hasNext()) { + Object item = it.next(); + if (item != null) sb.append(item.toString()); + if (it.hasNext()) sb.append(separator); + } + sb.append("}"); + return sb.toString(); + } + + public static boolean isEmpty(String string) { + return string == null || string.length() == 0; + } +} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/ui/EmptyRecyclerView.java b/app/src/main/java/eu/thedarken/rootvalidator/ui/EmptyRecyclerView.java deleted file mode 100644 index 761cd5b..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/ui/EmptyRecyclerView.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.ui; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.view.View; - -public class EmptyRecyclerView extends RecyclerView { - private View mEmptyView; - final private AdapterDataObserver observer = new AdapterDataObserver() { - @Override - public void onChanged() { - checkIfEmpty(); - } - - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - checkIfEmpty(); - } - - @Override - public void onItemRangeRemoved(int positionStart, int itemCount) { - checkIfEmpty(); - } - }; - - public EmptyRecyclerView(Context context) { - super(context); - } - - public EmptyRecyclerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - private void checkIfEmpty() { - if (mEmptyView != null) { - boolean emptyViewVisible = getAdapter() == null || getAdapter().getItemCount() == 0; - mEmptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE); - setVisibility(emptyViewVisible ? GONE : VISIBLE); - } - } - - @Override - public void setAdapter(Adapter adapter) { - final Adapter oldAdapter = getAdapter(); - if (oldAdapter != null) { - oldAdapter.unregisterAdapterDataObserver(observer); - } - super.setAdapter(adapter); - if (adapter != null) { - adapter.registerAdapterDataObserver(observer); - } - checkIfEmpty(); - } - - public void setEmptyView(View emptyView) { - if (mEmptyView != null) - mEmptyView.setVisibility(GONE); - this.mEmptyView = emptyView; - checkIfEmpty(); - } -} diff --git a/app/src/main/java/eu/thedarken/rootvalidator/ui/ShareDialog.java b/app/src/main/java/eu/thedarken/rootvalidator/ui/ShareDialog.java deleted file mode 100644 index ba9065a..0000000 --- a/app/src/main/java/eu/thedarken/rootvalidator/ui/ShareDialog.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Project Root Validator - * - * @link https://github.com/d4rken/rootvalidator - * @license https://github.com/d4rken/rootvalidator/blob/master/LICENSE GPLv3 - */ - -package eu.thedarken.rootvalidator.ui; - -import android.app.Dialog; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.Button; -import android.widget.TextView; - -import eu.thedarken.rootvalidator.R; - - -public class ShareDialog extends DialogFragment { - private Button mSimple, mExtended; - private TextView mTitle, mMessage; - - public static ShareDialog instantiate(Fragment parent) { - ShareDialog dialog = new ShareDialog(); - dialog.setTargetFragment(parent, 0); - return dialog; - } - - public void showDialog(FragmentActivity activity) { - show(activity.getSupportFragmentManager(), ShareDialog.class.getName()); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - setCancelable(true); - dialog.setCanceledOnTouchOutside(true); - return dialog; - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View dialogLayout = inflater.inflate(R.layout.fragment_dialog_share, container); - mTitle = (TextView) dialogLayout.findViewById(R.id.tv_title); - mMessage = (TextView) dialogLayout.findViewById(R.id.tv_message); - mSimple = (Button) dialogLayout.findViewById(R.id.bt_simple); - mSimple.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (getTargetFragment() instanceof ShareCallback) { - ((ShareCallback) getTargetFragment()).onShare(false); - } - } - }); - mExtended = (Button) dialogLayout.findViewById(R.id.bt_extended); - mExtended.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (getTargetFragment() instanceof ShareCallback) { - ((ShareCallback) getTargetFragment()).onShare(true); - } - } - }); - return dialogLayout; - } - - public interface ShareCallback { - void onShare(boolean extended); - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_stars_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_stars_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c59afb5a8cdacd3651cc414e3a79d0d152606183 GIT binary patch literal 619 zcmV-x0+juUP)3%7Y9szPFB=rkOkEj+v62`qZ{F_snL%xIEd%0S2+9L1x_7n!a@`6N1GktDdL}rw zArmDNw5;&vbM*FHOR5U$QgHqWszH=~b1lvlR6^UAYX^q0hl+t7;RqU24AdsW*cQdH z!%l)k(tA*FcnN}r@#6QjVx9`g1gXB^fW-_a6n!+x1etfrJGi0}u?tqgB z{MQb8Y5A8ZeH?)c@@ceLnV?e(K|LN8&pz;Bdd9I#klpT@201?v5~P{1vaC$dn`Eum zL4gY~g0l^(mbkJ~s&Q7zLLvGEGV z!MV-#m}(1~)cvzrw@v=04`S9#J(SWhNQheWcxHlL927ewfzGkCUCjLCC|yh`LFELS zrL7(I^J7DZ8hddTJE4baw5u}I8H#dHZ%%su--rIBpfA=ZMfdwoqw@d&002ovPDHLk FV1gY091H*e literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_stars_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_stars_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..58db554867963a6b92ac80cbfdb5e1344a2b8f1d GIT binary patch literal 406 zcmV;H0crk;P)!8i?7*wIC9RpZzBc9s_YGDHbpR@r?hZ zgDFtG1TDa4_(>o>MZglg7DN(oWehY<@PSZV7O(^HQ#=}9LL(HU?iGPJVZ&wtA(u5H zscj>mHX54+>yQ+@1LA8yd;o}7LBm!ONv#wRPXyv+K)eTtF9GpuB>k(gS?~}^!7d=? zBOyZxLLCDG_pw><3(cD#SIH1zxFQf=N7MV07z_SGBi#Ux;ik~Y|4)ns4{=1;aXc2B z#-ZmPHVf9`P&^5b1=DcoS%uAl7#xbjk<>UrF^nILLr)~GjP?YJVi{<%T!AFE9GYwu zu&95CE2DvM7?wyb0^(0t4EhMf1y~|Eh)`iS^*{MwBC*9aIhITywzx(Pt|vqo3<~o= za;r>sXi8ZF&HkVa^aGmCK}j(Jh*<}9rB5*i0Qt17Ze35I)&Kwi07*qoM6N<$g4j@+ A>Hq)$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_stars_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_stars_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2a6538c40ff4529bfce6c70acb3b15b192137e GIT binary patch literal 811 zcmV+`1JwM9P)rpa*OM+1VhV08WCh%G^GI0kC2w1hj%nw7OEOf%71rlz`pf zhjMRK&>5$IW^h@>Z~ATITnktT9;xK)E|~91K(k=-Pr);Rh@1-nl5NvX-;88Ci3IEt z4E_zw1@pjnfuBxW0jZp2LxO4pQv!cyO$8K$N$R?f&ryND z$)JoKBw$b&20MIwZWa3M=L*n8))&D*uR^wEMd0@%XyOQH7p~O45CI2-zBh6N^oB%( zm*4@o0#1Pv*aM1SJ$*abh_bst&lc&wCGw?xJ_KV_7x`HaM?lcW_7<#8ph;N+-UZCz z21h_8A|`K%aq=D1`~+EsL}3$MHZ7^Y*D6^5jF>MhNb@Vy| z9!EU4Lwc|^qGry9U7XD2Xr+_3JDft1n{*Z=EaJAy!(H#2NMbt;nUv_Zol`Z2iR-h} z?MYoR<>}EHkW002ovPDHLkV1mbxa$Nub literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_stars_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_stars_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..afbe663aed5031cb38457f8fd97f44341779729f GIT binary patch literal 1230 zcmV;<1Tp)GP)*n`wVw})N|>1J7KNKj!&7pu1ji87NecAjR0)~-2u6o zx^Z}Qplnb~e*iQGR0;aS@Qs5m2hH$DKq=z$Fw5#eiT(g+B&dv8XQx52b_KKy^p#m} zZJ=~J0SW<~VD_^@P>|Jt28&t6u2v<&3QGX-Z~ZZ|zdZm&n+=G+IZXte;X&hlB1jQt z0SY1ftX6392yZg#a_T}6LLg)U1 zMi~G!8q`VX$2uKAw+J1pkO(m-Kl55uK;fdwfMK1YQt@1*M|q4hMFLQQ$QD+K48l7^ z-+qHaRRGO3ILPu9_!ZDe&^}NOC{yI6C$g@J#sGqRaUf_2Xsn277J;%rJ3&W8@X4q0 zRs;P=Q315oVA!6->|6Mo2l{59A6rxal^A?QJ4A}YZg8?BX5bXU@7r8H00J#)rg&I(l1#n_C){LzxfOMwOXP|f!;fw*j(D1k2 zY(U)JgC?5*CsBkO%xpj|rf)w$3kl(5iq7d;t676|Z)>jg1aNjBE;p10=>~>txrlJK zA^WB@&6glMk&Or_7umN{R7(vRn}i4`0ok|BDu7ZE9r%vCVCBK+PelKcRRDz{rW9@v z4AQk$r#k{f?gqnl69B@hzTVWyDCQtJkxBq44av8)I)GvklJ@-iK@x#qrosP>LdaF~ zAtYM2>{FrX#6tsZOz;|0_-jAjz;#bGeY=cN7n`Z+#9<%z=5N*^*7HUM{&Wv8-y5Hf zY5F#mA(ftQYdEpEmwPS&y)|%si;sIOry87P0qK;V;L2K!(Y2Lb0D10~uV6IiF8G@65|i7?XWJhZ zh!yGEdZt$@{w3a)HB93ap&bEvS4((~I!~;s`IePepicrGuQKpjIWGtEUuxh*y-lFm stm`nJ^W!FVk^{j3AIlM>M>TP>I&V8TzdEWcL1%t8Yxz4%v-XG_j z`+i3Oz$gzr_#k)zyZ|0v055=t7r+bP;RW~)rXm&~)*&__nh|Y?9>j6PNyafm7h*qR zH{wOa{fKK3lf3}JQG~b!u@P|y>V4sj#m3@^Y?|>-?W=+WHY&5c3hEb6J21 zh_#3g(6e^LeTXx2NdR)T2JtJnV?8`)oStR@@(_0-egb#xd&HtN3Q)=`n55Xdh-qmN zU>V|fNS^(MSe#@5Xt}o;l5h2hu}Kr)T*N1k9{UhcoD>0O^JJDb`vy_sx&W6TIw8IG z1ES1T0cI-%|0Bdc1rbVI6JV;+u(JelhthCV?1})id;C}-2=r+y;1x=j!uvmvSRF|S z@RHJaTsw%vqe>j=v;|nE|D&R#%ZK51w<53aAVNJMiU1EQI{C{8P8K4L zD)L)nT7dD&4n7fpS0%@_trQfOZ1}kMXLi1@Shb0r5DZ z8gV;hdN(iS%~hN(yvq<3kSW|$^E(>&oh{74L{Dm)lmO>M$PPB~M6@0u(HxB+nm zXHkm))Ou2f>&z}?j>PpY#6yUu5U)U{?Y%c-EQ%!r2)q2G4bwdi1~@KOv2S6lLPCHC zB7Owdg9Gi~0?6li$VhG|2!Vze_9|F6!H!{s?|#f&>V9zqSleg2@F1UdcT(ln~&!;M(|4$TU;A8hkoH@|U2$ z{SpG41bM4*F!;3w{2Xvq0N~ZtX$}OQy6a3emkqc!1Z7(#Oi&E|M8tar{Sz62y1}e{ z0hZ2V{R=a=&S26+CgD~vZ*+3JMV6s^ETwNLcYn2o;Fnu0F71*KV57wx z`cex5EVt-S9VjN_qFAW~0m>};vsxn^9cLb}H3xKC@@c+=08=cHZL}!B8UBvq0; zXo2iXivp~*;7=lY&RJmQ908r8&od#wsoofA`z*x7Su;W&hko}6 z1V6$4l>vW3^+ZMK9{X9C@P+{Ir6DRzyZP5acA=cBk+d!d*-9B@@>(ZM`nF--MRkJh zF`m5*aRJynZd)BTo>7~X4hxdbR*WfNp(U2S{hUzDR{oqR=*PEFR%B7v&`X3!cl=Mj zzRkecOx3S>S>AU<==nECiDjIPOp^fVEim-Ljn^!s2wsJu7kz9Kc;=3%ag1A2?Ttx% z1rH6B9J6_vrH^yC*8sRc)dS$9+mYM~o;$-X8NnZj3PdnUO zO~=Pp0lY*gcVx@nij!V&QGmf&)WZ%3p7t^eZN^0F@biNpUs;P|bXH&$MDMUjiHAtlDXQ7fvTqw?!rA>H94Nol@x3w?oC5JZ=A7yqkf(aPxVPz4MK#VbrTx zr7_GTNq}q{x=JqC(5+p&xqi^epB6d>dV+sG!ukE3EFjey==Q=%F8k=eRM`vQ1@Q0! kcmX`T0A2tOFF@|$A7rfD#uM@&j{pDw07*qoM6N<$g0WQ*jsO4v literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_indicator_negative.xml b/app/src/main/res/drawable/ic_indicator_negative.xml new file mode 100644 index 0000000..f5520ae --- /dev/null +++ b/app/src/main/res/drawable/ic_indicator_negative.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_indicator_neutral.xml b/app/src/main/res/drawable/ic_indicator_neutral.xml new file mode 100644 index 0000000..39cc06f --- /dev/null +++ b/app/src/main/res/drawable/ic_indicator_neutral.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_indicator_positive.xml b/app/src/main/res/drawable/ic_indicator_positive.xml new file mode 100644 index 0000000..a059dd0 --- /dev/null +++ b/app/src/main/res/drawable/ic_indicator_positive.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_mainactivity_layout.xml b/app/src/main/res/layout/activity_mainactivity_layout.xml index e3d6cb5..9d532e7 100644 --- a/app/src/main/res/layout/activity_mainactivity_layout.xml +++ b/app/src/main/res/layout/activity_mainactivity_layout.xml @@ -1,28 +1,6 @@ - - - - - - - - - \ No newline at end of file + android:layout_height="match_parent"/> \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_info_line.xml b/app/src/main/res/layout/adapter_info_line.xml index f9faa16..14edc16 100644 --- a/app/src/main/res/layout/adapter_info_line.xml +++ b/app/src/main/res/layout/adapter_info_line.xml @@ -26,34 +26,33 @@ android:gravity="center_vertical" android:orientation="horizontal"> - + android:src="@drawable/ic_indicator_neutral"/> diff --git a/app/src/main/res/layout/fragment_dialog_about.xml b/app/src/main/res/layout/fragment_dialog_about.xml index 551214f..8190b8e 100644 --- a/app/src/main/res/layout/fragment_dialog_about.xml +++ b/app/src/main/res/layout/fragment_dialog_about.xml @@ -47,28 +47,6 @@ android:textColor="@color/textcolor_secondary" tools:ignore="HardcodedText"/> - - - - - - - - - - - - - - - - -