Compare commits

...

45 Commits

Author SHA1 Message Date
Thomas 64a2e223b3 Allow to analyze app from reports 2021-02-12 12:06:06 +01:00
Thomas 90848c678a Create flavor for fdroid & google as not the same permission is used. 2021-01-11 11:38:08 +01:00
Thomas 6b5631bb43 Merge branch 'master' of https://github.com/stom79/exodus-android-app 2021-01-08 09:01:59 +01:00
Thomas 2f531d7d24 Avoid to have a remaining refresh bar when going back to main screen. 2021-01-04 18:32:28 +01:00
Thomas e3f2325d00 Some fixes + Android 11 permission for scanning app. 2021-01-04 18:06:51 +01:00
Thomas 3d5b4fd04f Merge branch 'stom79_baseline' 2020-12-16 08:45:03 +01:00
Thomas 3d1320eeb9 Remove previous feature that copied id into clipboard. 2020-12-16 08:42:47 +01:00
Thomas 7d4d2533ac Remove previous feature that copied id into clipboard. 2020-12-16 08:42:09 +01:00
Thomas c40afdf348 Merge branch 'stom79_baseline'
# Conflicts:
#	app/src/main/java/org/eu/exodus_privacy/exodusprivacy/CheckAppActivity.java
2020-12-16 08:22:49 +01:00
Thomas ae020fe1cc Automatically paste the app id to the website form + finish activity when dismissing the dialog (Analysis from stores). 2020-12-16 08:19:33 +01:00
Thomas 20f2e8412f Allow the refresh for tracking list 2020-12-15 18:21:10 +01:00
Thomas 9d330bf07f Improve layout 2020-12-15 17:24:46 +01:00
Thomas cae158d489 Some improvements 2020-12-15 16:53:36 +01:00
Thomas efe9a88209 Allow to navigate through viewpager 2020-12-15 16:31:06 +01:00
Thomas 3bf596f1cf Prepare views + percent 2020-12-15 13:59:19 +01:00
Thomas 7e6e1fcff6 Menu + transitions 2020-12-15 10:00:26 +01:00
Thomas 2a091043c6 Prepare new layout with bottom menu + hide top bar when scrolling 2020-12-15 09:20:44 +01:00
Thomas e64e04398a Fix F-Droid regex + fix submit URL 2020-12-12 13:21:41 +01:00
Thomas e4b8dc9ad7 Allow to analyze apps by opening the submit page and copy app id in clipboard. 2020-12-12 11:12:01 +01:00
Thomas 8e4db1d284 Check for tracker 2020-12-11 11:09:35 +01:00
Thomas 55303f158e Check for tracker 2020-12-11 10:20:31 +01:00
Thomas 91682ed3b1 Uses SecureRandom - Fix issue #32 2020-12-09 16:20:49 +01:00
Thomas a9e72c0cb3 Accepts licenses with Travis 2020-12-09 09:00:47 +01:00
Thomas 7952449247 Fix travis issue with CI 2020-12-08 21:39:03 +01:00
Thomas 22e1cbb221 Filter apps 2020-12-08 19:06:56 +01:00
Thomas 501e0ae9f4 Merge branch 'stom79_baseline' into filters 2020-12-08 08:56:06 +01:00
Thomas a247cc997e Improve app remember position by using first visible item. 2020-12-08 08:55:26 +01:00
Thomas 53553940d3 prepare ordering 2020-12-07 17:00:45 +01:00
Thomas acfc183eec prepare filters 2020-12-07 16:51:02 +01:00
Thomas 8209559012 Some fixes 2020-12-07 15:48:20 +01:00
Thomas 891198b71f Improve searchview 2020-12-07 15:17:47 +01:00
Thomas 5ec97f3f81 search view 2020-12-07 14:55:11 +01:00
Thomas 85097ce51b Fix vector icon for exodus flavor 2020-12-07 14:16:30 +01:00
Thomas 80046985c0 Add flavors 2020-12-07 14:10:58 +01:00
Thomas 3b7343bc68 Fix issues #29 and #87 2020-12-07 11:41:38 +01:00
Thomas 883b623d6d clean 2020-12-07 10:50:48 +01:00
Thomas 91f5dea591 Fix issue #83 2020-12-07 10:48:48 +01:00
Thomas 18c19ace49 Upgrade compileSdkVersion 2020-12-07 09:43:38 +01:00
Thomas 62467a3fe1 Some cleaning 2020-12-07 09:04:44 +01:00
Thomas 555f108853 Merge branch 'master' into stom79_baseline
# Conflicts:
#	app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java
#	app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/AppListFragment.java
2020-10-04 18:07:20 +02:00
stom79 5723d9ea8d Merge branch 'filter_app' into stom79_baseline
# Conflicts:
#	app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java
2018-04-14 15:32:15 +02:00
stom79 d81078659a Merge branch 'filter_app' into stom79_baseline
# Conflicts:
#	app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java
#	app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/AppListFragment.java
2018-04-14 14:44:28 +02:00
stom79 b2fde810cd Merge branch 'expand_buttons' into stom79_baseline 2018-04-14 11:55:40 +02:00
stom79 956e0ba963 Merge branch 'animate_transitions' into stom79_baseline 2018-04-14 11:06:36 +02:00
stom79 341d0dafe3 Merge branch 'landscape_mode' into stom79_baseline 2018-04-14 11:06:26 +02:00
116 changed files with 2369 additions and 749 deletions

View File

@ -1,6 +1,10 @@
language: android
java: oraclejdk8
before_install:
- yes | sdkmanager "platforms;android-30"
- yes | sdkmanager "build-tools;30.0.2"
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
@ -15,8 +19,8 @@ android:
components:
- tools
- platform-tools
- build-tools-29.0.3
- android-29
- build-tools-30.0.2
- android-30
- extra-m2-repository
- sys-img-armeabi-v7a-android-28

View File

@ -1,16 +1,16 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "org.eu.exodus_privacy.exodusprivacy"
minSdkVersion 17
targetSdkVersion 29
targetSdkVersion 30
versionCode 10
versionName "2.1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
flavorDimensions "default"
buildTypes {
release {
minifyEnabled false
@ -20,6 +20,67 @@ android {
}
}
}
lintOptions {
disable 'MissingTranslation'
checkReleaseBuilds false
abortOnError false
}
productFlavors {
exodus_google {
applicationId "org.eu.exodus_privacy.exodusprivacy"
buildConfigField "boolean", "amal", "false"
}
amal_google {
applicationId "app.fedilab.amal"
buildConfigField "boolean", "amal", "true"
}
fedilab_google {
applicationId "app.fedilab.exodusprivacy"
buildConfigField "boolean", "amal", "false"
}
exodus_fdroid {
applicationId "org.eu.exodus_privacy.exodusprivacy"
buildConfigField "boolean", "amal", "false"
}
amal_fdroid {
applicationId "app.fedilab.amal"
buildConfigField "boolean", "amal", "true"
}
fedilab_fdroid {
applicationId "app.fedilab.exodusprivacy"
buildConfigField "boolean", "amal", "false"
}
}
sourceSets {
exodus_google {
res.srcDirs = ['src/main/res', 'src/exodus/res']
manifest.srcFile 'src/google/AndroidManifest.xml'
}
fedilab_google {
res.srcDirs = ['src/main/res', 'src/fedilab/res']
manifest.srcFile 'src/google/AndroidManifest.xml'
}
amal_google {
res.srcDirs = ['src/main/res', 'src/amal/res']
manifest.srcFile 'src/google/AndroidManifest.xml'
}
exodus_fdroid {
res.srcDirs = ['src/main/res', 'src/exodus/res']
manifest.srcFile 'src/fdroid/AndroidManifest.xml'
}
fedilab_fdroid {
res.srcDirs = ['src/main/res', 'src/fedilab/res']
manifest.srcFile 'src/fdroid/AndroidManifest.xml'
}
amal_fdroid {
res.srcDirs = ['src/main/res', 'src/amal/res']
manifest.srcFile 'src/fdroid/AndroidManifest.xml'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -31,13 +92,13 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'com.google.android.material:material:1.1.0'
testImplementation 'junit:junit:4.13'
implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.13.1'
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.24190092"
android:scaleY="0.24190092"
android:translateX="25.122473"
android:translateY="22.68">
<path
android:fillColor="#007ea5"
android:pathData="M59.689,30.169l59.032,-30.169l120.034,59.455l-61.001,29.469l-118.065,-58.755z" />
<path
android:fillColor="#00bedd"
android:pathData="M177.754,154.21l0,-65.286l-59.033,-29.469l0,60.162l59.033,34.593z" />
<path
android:fillColor="#03cfe5"
android:pathData="M118.721,189.589l59.033,-35.379l-59.033,-34.593l-59.032,34.593l59.032,35.379z" />
<path
android:fillColor="#0099bc"
android:pathData="M59.689,88.924l59.032,-29.469l0,60.162l-59.032,34.593l0,-65.286z" />
<path
android:fillColor="#005e8b"
android:pathData="M0,119.617l0,69.972l118.721,69.36l120.034,-69.36l0,-130.134l-61.001,29.469l0,65.286l-59.033,35.379l-59.032,-35.379l0,-65.286l-59.689,30.693z" />
</group>
</vector>

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="238.755dp"
android:height="258.949dp"
android:viewportWidth="238.755"
android:viewportHeight="258.949">
<path
android:fillColor="#007ea5"
android:pathData="M59.689,30.169l59.032,-30.169l120.034,59.455l-61.001,29.469l-118.065,-58.755z" />
<path
android:fillColor="#00bedd"
android:pathData="M177.754,154.21l0,-65.286l-59.033,-29.469l0,60.162l59.033,34.593z" />
<path
android:fillColor="#03cfe5"
android:pathData="M118.721,189.589l59.033,-35.379l-59.033,-34.593l-59.032,34.593l59.032,35.379z" />
<path
android:fillColor="#0099bc"
android:pathData="M59.689,88.924l59.032,-29.469l0,60.162l-59.032,34.593l0,-65.286z" />
<path
android:fillColor="#005e8b"
android:pathData="M0,119.617l0,69.972l118.721,69.36l120.034,-69.36l0,-130.134l-61.001,29.469l0,65.286l-59.033,35.379l-59.032,-35.379l0,-65.286l-59.689,30.693z" />
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#005e8b</color>
<color name="colorPrimaryDark">#005e8b</color>
<color name="colorAccent">#007bff</color>
<color name="percent">#77005e8b</color>
<color name="colorGreen">#6fc384</color>
<color name="colorLightRed">#e46772</color>
<color name="colorLightYellow">#ffdb66</color>
<color name="colorLightBlue">#17a2b8</color>
<color name="colorYellow">#ffc70f</color>
<color name="colorDarkOrange">#ff8c00</color>
<color name="colorRed">#e61718</color>
<color name="colorPurple">#684971</color>
<color name="textColorDark">#343A40</color>
<color name="textColorDarkLight">#6C757D</color>
<color name="textColorWhite">#FFFFFF</color>
<color name="textDetection">#E83E8C</color>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exodus">16f8c1a973ecb6622a606daaef35736dc3521e30</string>
<string name="app_title">AMAL</string>
<string name="app_name">AMAL</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="1.48"
android:scaleY="1.48"
android:translateX="14.04"
android:translateY="14.04">
<path
android:fillAlpha="1"
android:fillColor="#684971"
android:pathData="M2.1737,27a24.8263,24.5976 0,1 0,49.6526 0a24.8263,24.5976 0,1 0,-49.6526 0z"
android:strokeWidth="0.0297731"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="m22.6353,26.1025c-4.3263,-1.5231 -6.4894,-3.8295 -6.4894,-6.9192 -0,-2.4369 1.1474,-4.3843 3.4423,-5.8421 2.2949,-1.4578 5.1443,-2.1867 8.5482,-2.1867 3.0964,0 5.556,0.5059 7.3788,1.5176 1.8227,1.0118 2.7341,2.2248 2.7341,3.6391 -0,0.7398 -0.2855,1.3871 -0.8565,1.9419 -0.571,0.5549 -1.2298,0.8323 -1.9765,0.8323 -1.2298,0 -2.24,-0.8486 -3.0306,-2.5457 -1.098,-2.3499 -2.7451,-3.5248 -4.9412,-3.5249 -1.7349,0 -3.1624,0.5657 -4.2823,1.6972 -1.12,1.1315 -1.68,2.7089 -1.68,4.7325 -0,3.9818 2.0753,5.9727 6.2259,5.9727 0.4392,0 0.9443,-0.0435 1.5153,-0.1305 0.9882,-0.1305 1.7568,-0.1958 2.3059,-0.1958 1.3396,0 2.0094,0.3808 2.0094,1.1423 -0,0.8486 -0.6808,1.2729 -2.0424,1.2729 -0.4832,0 -1.2079,-0.0761 -2.1741,-0.2285 -0.7247,-0.1305 -1.2847,-0.1958 -1.68,-0.1958 -4.3922,0 -6.5882,2.2194 -6.5882,6.6581 -0,2.1541 0.5819,3.8893 1.7459,5.2057 1.1639,1.3164 2.789,1.9746 4.8753,1.9746 2.6133,0 4.3482,-1.3381 5.2047,-4.0144 0.4392,-1.4143 0.9278,-2.3934 1.4659,-2.9374 0.538,-0.544 1.2572,-0.8159 2.1576,-0.8159 0.7466,0 1.4219,0.2665 2.0259,0.7996 0.6039,0.5331 0.9059,1.213 0.9059,2.0399 -0,1.98 -1.12,3.6173 -3.36,4.912 -2.24,1.2946 -4.9522,1.9419 -8.1365,1.9419 -3.4918,0 -6.5937,-0.8268 -9.3059,-2.4805 -2.7122,-1.6536 -4.0682,-3.8621 -4.0682,-6.6254 -0,-3.4596 2.6902,-6.0053 8.0706,-7.6372z"
android:strokeWidth="0.03275258"
android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
</group>
</vector>

View File

@ -1,10 +1,21 @@
<vector android:height="100dp" android:viewportHeight="54"
android:viewportWidth="54" android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="#684971"
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="54"
android:viewportHeight="54">
<path
android:fillAlpha="1"
android:fillColor="#684971"
android:pathData="M2.1737,27a24.8263,24.5976 0,1 0,49.6526 0a24.8263,24.5976 0,1 0,-49.6526 0z"
android:strokeColor="#00000000" android:strokeWidth="0.0297731"/>
<path android:fillAlpha="1" android:fillColor="#ffffff"
android:strokeWidth="0.0297731"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="m22.6353,26.1025c-4.3263,-1.5231 -6.4894,-3.8295 -6.4894,-6.9192 -0,-2.4369 1.1474,-4.3843 3.4423,-5.8421 2.2949,-1.4578 5.1443,-2.1867 8.5482,-2.1867 3.0964,0 5.556,0.5059 7.3788,1.5176 1.8227,1.0118 2.7341,2.2248 2.7341,3.6391 -0,0.7398 -0.2855,1.3871 -0.8565,1.9419 -0.571,0.5549 -1.2298,0.8323 -1.9765,0.8323 -1.2298,0 -2.24,-0.8486 -3.0306,-2.5457 -1.098,-2.3499 -2.7451,-3.5248 -4.9412,-3.5249 -1.7349,0 -3.1624,0.5657 -4.2823,1.6972 -1.12,1.1315 -1.68,2.7089 -1.68,4.7325 -0,3.9818 2.0753,5.9727 6.2259,5.9727 0.4392,0 0.9443,-0.0435 1.5153,-0.1305 0.9882,-0.1305 1.7568,-0.1958 2.3059,-0.1958 1.3396,0 2.0094,0.3808 2.0094,1.1423 -0,0.8486 -0.6808,1.2729 -2.0424,1.2729 -0.4832,0 -1.2079,-0.0761 -2.1741,-0.2285 -0.7247,-0.1305 -1.2847,-0.1958 -1.68,-0.1958 -4.3922,0 -6.5882,2.2194 -6.5882,6.6581 -0,2.1541 0.5819,3.8893 1.7459,5.2057 1.1639,1.3164 2.789,1.9746 4.8753,1.9746 2.6133,0 4.3482,-1.3381 5.2047,-4.0144 0.4392,-1.4143 0.9278,-2.3934 1.4659,-2.9374 0.538,-0.544 1.2572,-0.8159 2.1576,-0.8159 0.7466,0 1.4219,0.2665 2.0259,0.7996 0.6039,0.5331 0.9059,1.213 0.9059,2.0399 -0,1.98 -1.12,3.6173 -3.36,4.912 -2.24,1.2946 -4.9522,1.9419 -8.1365,1.9419 -3.4918,0 -6.5937,-0.8268 -9.3059,-2.4805 -2.7122,-1.6536 -4.0682,-3.8621 -4.0682,-6.6254 -0,-3.4596 2.6902,-6.0053 8.0706,-7.6372z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="0.03275258"/>
android:strokeWidth="0.03275258"
android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -2,7 +2,8 @@
<resources>
<color name="colorPrimary">#684971</color>
<color name="colorPrimaryDark">#3d2b43</color>
<color name="colorAccent">#684971</color>
<color name="colorAccent">#3d2b43</color>
<color name="percent">#77684971</color>
<color name="colorGreen">#6fc384</color>
<color name="colorLightRed">#e46772</color>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#684971</color>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.eu.exodus_privacy.exodusprivacy">
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="1.48"
android:scaleY="1.48"
android:translateX="14.04"
android:translateY="14.04">
<path
android:fillAlpha="1"
android:fillColor="#1976D2"
android:pathData="M2.1737,27a24.8263,24.5976 0,1 0,49.6526 0a24.8263,24.5976 0,1 0,-49.6526 0z"
android:strokeWidth="0.0297731"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="m22.6353,26.1025c-4.3263,-1.5231 -6.4894,-3.8295 -6.4894,-6.9192 -0,-2.4369 1.1474,-4.3843 3.4423,-5.8421 2.2949,-1.4578 5.1443,-2.1867 8.5482,-2.1867 3.0964,0 5.556,0.5059 7.3788,1.5176 1.8227,1.0118 2.7341,2.2248 2.7341,3.6391 -0,0.7398 -0.2855,1.3871 -0.8565,1.9419 -0.571,0.5549 -1.2298,0.8323 -1.9765,0.8323 -1.2298,0 -2.24,-0.8486 -3.0306,-2.5457 -1.098,-2.3499 -2.7451,-3.5248 -4.9412,-3.5249 -1.7349,0 -3.1624,0.5657 -4.2823,1.6972 -1.12,1.1315 -1.68,2.7089 -1.68,4.7325 -0,3.9818 2.0753,5.9727 6.2259,5.9727 0.4392,0 0.9443,-0.0435 1.5153,-0.1305 0.9882,-0.1305 1.7568,-0.1958 2.3059,-0.1958 1.3396,0 2.0094,0.3808 2.0094,1.1423 -0,0.8486 -0.6808,1.2729 -2.0424,1.2729 -0.4832,0 -1.2079,-0.0761 -2.1741,-0.2285 -0.7247,-0.1305 -1.2847,-0.1958 -1.68,-0.1958 -4.3922,0 -6.5882,2.2194 -6.5882,6.6581 -0,2.1541 0.5819,3.8893 1.7459,5.2057 1.1639,1.3164 2.789,1.9746 4.8753,1.9746 2.6133,0 4.3482,-1.3381 5.2047,-4.0144 0.4392,-1.4143 0.9278,-2.3934 1.4659,-2.9374 0.538,-0.544 1.2572,-0.8159 2.1576,-0.8159 0.7466,0 1.4219,0.2665 2.0259,0.7996 0.6039,0.5331 0.9059,1.213 0.9059,2.0399 -0,1.98 -1.12,3.6173 -3.36,4.912 -2.24,1.2946 -4.9522,1.9419 -8.1365,1.9419 -3.4918,0 -6.5937,-0.8268 -9.3059,-2.4805 -2.7122,-1.6536 -4.0682,-3.8621 -4.0682,-6.6254 -0,-3.4596 2.6902,-6.0053 8.0706,-7.6372z"
android:strokeWidth="0.03275258"
android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
</group>
</vector>

View File

@ -1,9 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="54"
android:viewportWidth="54">
android:width="100dp"
android:height="100dp"
android:viewportWidth="54"
android:viewportHeight="54">
<path
android:fillColor="#fff"
android:pathData="m22.6353,26.1025c-4.3263,-1.5231 -6.4894,-3.8295 -6.4894,-6.9192 -0,-2.4369 1.1474,-4.3843 3.4423,-5.8421 2.2949,-1.4578 5.1443,-2.1867 8.5482,-2.1867 3.0964,0 5.556,0.5059 7.3788,1.5176 1.8227,1.0118 2.7341,2.2248 2.7341,3.6391 -0,0.7398 -0.2855,1.3871 -0.8565,1.9419 -0.571,0.5549 -1.2298,0.8323 -1.9765,0.8323 -1.2298,0 -2.24,-0.8486 -3.0306,-2.5457 -1.098,-2.3499 -2.7451,-3.5248 -4.9412,-3.5249 -1.7349,0 -3.1624,0.5657 -4.2823,1.6972 -1.12,1.1315 -1.68,2.7089 -1.68,4.7325 -0,3.9818 2.0753,5.9727 6.2259,5.9727 0.4392,0 0.9443,-0.0435 1.5153,-0.1305 0.9882,-0.1305 1.7568,-0.1958 2.3059,-0.1958 1.3396,0 2.0094,0.3808 2.0094,1.1423 -0,0.8486 -0.6808,1.2729 -2.0424,1.2729 -0.4832,0 -1.2079,-0.0761 -2.1741,-0.2285 -0.7247,-0.1305 -1.2847,-0.1958 -1.68,-0.1958 -4.3922,0 -6.5882,2.2194 -6.5882,6.6581 -0,2.1541 0.5819,3.8893 1.7459,5.2057 1.1639,1.3164 2.789,1.9746 4.8753,1.9746 2.6133,0 4.3482,-1.3381 5.2047,-4.0144 0.4392,-1.4143 0.9278,-2.3934 1.4659,-2.9374 0.538,-0.544 1.2572,-0.8159 2.1576,-0.8159 0.7466,0 1.4219,0.2665 2.0259,0.7996 0.6039,0.5331 0.9059,1.213 0.9059,2.0399 -0,1.98 -1.12,3.6173 -3.36,4.912 -2.24,1.2946 -4.9522,1.9419 -8.1365,1.9419 -3.4918,0 -6.5937,-0.8268 -9.3059,-2.4805 -2.7122,-1.6536 -4.0682,-3.8621 -4.0682,-6.6254 -0,-3.4596 2.6902,-6.0053 8.0706,-7.6372z" />
android:fillAlpha="1"
android:fillColor="#1976D2"
android:pathData="M2.1737,27a24.8263,24.5976 0,1 0,49.6526 0a24.8263,24.5976 0,1 0,-49.6526 0z"
android:strokeWidth="0.0297731"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="m22.6353,26.1025c-4.3263,-1.5231 -6.4894,-3.8295 -6.4894,-6.9192 -0,-2.4369 1.1474,-4.3843 3.4423,-5.8421 2.2949,-1.4578 5.1443,-2.1867 8.5482,-2.1867 3.0964,0 5.556,0.5059 7.3788,1.5176 1.8227,1.0118 2.7341,2.2248 2.7341,3.6391 -0,0.7398 -0.2855,1.3871 -0.8565,1.9419 -0.571,0.5549 -1.2298,0.8323 -1.9765,0.8323 -1.2298,0 -2.24,-0.8486 -3.0306,-2.5457 -1.098,-2.3499 -2.7451,-3.5248 -4.9412,-3.5249 -1.7349,0 -3.1624,0.5657 -4.2823,1.6972 -1.12,1.1315 -1.68,2.7089 -1.68,4.7325 -0,3.9818 2.0753,5.9727 6.2259,5.9727 0.4392,0 0.9443,-0.0435 1.5153,-0.1305 0.9882,-0.1305 1.7568,-0.1958 2.3059,-0.1958 1.3396,0 2.0094,0.3808 2.0094,1.1423 -0,0.8486 -0.6808,1.2729 -2.0424,1.2729 -0.4832,0 -1.2079,-0.0761 -2.1741,-0.2285 -0.7247,-0.1305 -1.2847,-0.1958 -1.68,-0.1958 -4.3922,0 -6.5882,2.2194 -6.5882,6.6581 -0,2.1541 0.5819,3.8893 1.7459,5.2057 1.1639,1.3164 2.789,1.9746 4.8753,1.9746 2.6133,0 4.3482,-1.3381 5.2047,-4.0144 0.4392,-1.4143 0.9278,-2.3934 1.4659,-2.9374 0.538,-0.544 1.2572,-0.8159 2.1576,-0.8159 0.7466,0 1.4219,0.2665 2.0259,0.7996 0.6039,0.5331 0.9059,1.213 0.9059,2.0399 -0,1.98 -1.12,3.6173 -3.36,4.912 -2.24,1.2946 -4.9522,1.9419 -8.1365,1.9419 -3.4918,0 -6.5937,-0.8268 -9.3059,-2.4805 -2.7122,-1.6536 -4.0682,-3.8621 -4.0682,-6.6254 -0,-3.4596 2.6902,-6.0053 8.0706,-7.6372z"
android:strokeWidth="0.03275258"
android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1976D2</color>
<color name="colorPrimaryDark">#0D47A1</color>
<color name="colorAccent">#64B5F6</color>
<color name="percent">#771976D2</color>
<color name="colorGreen">#6fc384</color>
<color name="colorLightRed">#e46772</color>
<color name="colorLightYellow">#ffdb66</color>
<color name="colorLightBlue">#17a2b8</color>
<color name="colorYellow">#ffc70f</color>
<color name="colorDarkOrange">#ff8c00</color>
<color name="colorRed">#e61718</color>
<color name="colorPurple">#684971</color>
<color name="textColorDark">#343A40</color>
<color name="textColorDarkLight">#6C757D</color>
<color name="textColorWhite">#FFFFFF</color>
<color name="textDetection">#E83E8C</color>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exodus">9c6106a229bc5f34b5802e5861bcb87d1626617d</string>
<string name="app_title">εxodus</string>
<string name="app_name">Exodus (test)</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1976D2</color>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eu.exodus_privacy.exodusprivacy">
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
</manifest>

View File

@ -3,8 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.eu.exodus_privacy.exodusprivacy">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
@ -13,16 +15,31 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"
android:largeHeap="true"
>
<activity android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
>
android:largeHeap="true">
<activity
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".CheckAppActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:noHistory="true"
android:theme="@style/AppTheme.Popup">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:host="play.google.com" />
<data android:host="f-droid.org" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,184 @@
package org.eu.exodus_privacy.exodusprivacy;
/*
* Copyright (C) 2020 Thomas Schneider
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.adapters.TrackerListAdapter;
import org.eu.exodus_privacy.exodusprivacy.databinding.AppCheckActivityBinding;
import org.eu.exodus_privacy.exodusprivacy.fragments.TrackerFragment;
import org.eu.exodus_privacy.exodusprivacy.fragments.Updatable;
import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import org.eu.exodus_privacy.exodusprivacy.manager.NetworkManager;
import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import org.eu.exodus_privacy.exodusprivacy.objects.Report;
import org.eu.exodus_privacy.exodusprivacy.objects.ReportDisplay;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CheckAppActivity extends AppCompatActivity implements NetworkListener, TrackerListAdapter.OnTrackerClickListener {
private static final Pattern fdroidRegex = Pattern.compile("https?://f-droid\\.org/([\\w-]+/)?packages/([\\w.-]+)");
private static final Pattern googleRegex = Pattern.compile("https?://play\\.google\\.com/store/apps/details\\?id=([\\w.-]+)");
ArrayList<Updatable> fragments;
AppCheckActivityBinding binding;
TrackerListAdapter.OnTrackerClickListener onTrackerClickListener = id -> {
TrackerFragment tracker = TrackerFragment.newInstance(id);
fragments.add(tracker);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right, R.anim.slide_in_left, R.anim.slide_out_left)
.replace(R.id.fragment_container, tracker)
.addToBackStack(null)
.commit();
};
private String app_id;
private TrackerListAdapter.OnTrackerClickListener trackerClickListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (extraText != null) {
Matcher matcher = fdroidRegex.matcher(extraText);
app_id = null;
while (matcher.find()) {
app_id = matcher.group(2);
}
if (app_id == null) {
matcher = googleRegex.matcher(extraText);
while (matcher.find()) {
app_id = matcher.group(1);
}
}
setOnTrackerClickListener(trackerClickListener);
fragments = new ArrayList<>();
NetworkManager.getInstance().getSingleReport(CheckAppActivity.this, this, app_id);
}
}
private void setOnTrackerClickListener(TrackerListAdapter.OnTrackerClickListener listener) {
trackerClickListener = listener;
}
@Override
public void onSuccess(Application application) {
runOnUiThread(() -> {
if (application == null) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(CheckAppActivity.this);
dialogBuilder.setTitle(getString(R.string.app_not_analyzed_title));
dialogBuilder.setMessage(getString(R.string.app_not_analyzed));
//noinspection RedundantSuppression
dialogBuilder.setPositiveButton(R.string.submit, (dialog, id) -> {
Uri uri;
//noinspection ConstantConditions
if (!BuildConfig.amal) {
uri = Uri.parse("https://reports.exodus-privacy.eu.org/analysis/submit/#" + app_id);
} else {
uri = Uri.parse("https://exodus.phm.education.gouv.fr/analysis/submit/#" + app_id);
}
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(browserIntent);
dialog.dismiss();
finish();
});
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> {
dialog.dismiss();
finish();
});
dialogBuilder.setOnDismissListener(dialogInterface -> finish());
AlertDialog alertDialog = dialogBuilder.create();
alertDialog.show();
return;
}
ApplicationViewModel applicationViewModel = new ApplicationViewModel();
applicationViewModel.packageName = application.packageName;
applicationViewModel.label = application.name;
Report reportToKeep = null;
long versionCode = -1;
for (Report report : application.reports) {
if (versionCode == -1) {
reportToKeep = report;
versionCode = report.versionCode;
} else if (report.versionCode > versionCode) {
reportToKeep = report;
versionCode = report.versionCode;
}
}
applicationViewModel.report = reportToKeep;
applicationViewModel.source = reportToKeep.source;
applicationViewModel.versionCode = (int) reportToKeep.versionCode;
applicationViewModel.versionName = reportToKeep.version;
applicationViewModel.trackers = DatabaseManager.getInstance(CheckAppActivity.this).getTrackers(reportToKeep.trackers);
ReportDisplay reportDisplay = ReportDisplay.buildReportDisplay(CheckAppActivity.this, applicationViewModel, null, null);
ReportViewModel viewModel = new ReportViewModel();
viewModel.setReportDisplay(reportDisplay);
TrackerListAdapter trackerAdapter = new TrackerListAdapter(reportDisplay.trackers, R.layout.tracker_item, onTrackerClickListener);
binding = AppCheckActivityBinding.inflate(getLayoutInflater());
binding.reportUrl.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://" + Utils.getDomain() + "/reports/" + reportDisplay.report.id + "/"));
startActivity(intent);
});
binding.setReportInfo(viewModel);
binding.trackers.setAdapter(trackerAdapter);
binding.trackers.setLayoutManager(new LinearLayoutManager(CheckAppActivity.this));
View viewRoot = binding.getRoot();
setContentView(viewRoot);
});
}
@Override
public void onError(String error) {
}
@Override
public void onProgress(int resourceId, int progress, int maxProgress) {
}
@Override
public void onTrackerClick(long trackerId) {
}
}

View File

@ -18,70 +18,96 @@
package org.eu.exodus_privacy.exodusprivacy;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.snackbar.Snackbar;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationListAdapter;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.adapters.TrackerListAdapter;
import org.eu.exodus_privacy.exodusprivacy.databinding.MainBinding;
import org.eu.exodus_privacy.exodusprivacy.fragments.ComputeAppList;
import org.eu.exodus_privacy.exodusprivacy.fragments.HomeFragment;
import org.eu.exodus_privacy.exodusprivacy.fragments.MyTrackersFragment;
import org.eu.exodus_privacy.exodusprivacy.fragments.ReportFragment;
import org.eu.exodus_privacy.exodusprivacy.fragments.TrackerFragment;
import org.eu.exodus_privacy.exodusprivacy.fragments.Updatable;
import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static ComputeAppList.order order = ComputeAppList.order.DEFAULT;
private List<Updatable> fragments;
private SearchView searchView;
private Menu toolbarMenu;
private MenuItem settingsMenuItem;
private String packageName;
private MainBinding binding;
private final BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= item -> {
int itemId = item.getItemId();
if (itemId == R.id.navigation_apps) {
binding.viewpager.setCurrentItem(0);
} else if (itemId == R.id.navigation_analytics) {
binding.viewpager.setCurrentItem(1);
}
return true;
};
private ApplicationListAdapter.OnAppClickListener onAppClickListener;
private TrackerListAdapter.OnTrackerClickListener onTrackerClickListener;
private String previousQuery = "";
private HomeFragment home;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.main);
binding = DataBindingUtil.setContentView(this, R.layout.main);
final MainBinding mainBinding = binding;
getSupportActionBar().setTitle(R.string.app_title);
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(R.string.app_title);
}
fragments = new ArrayList<>();
NetworkListener networkListener = new NetworkListener() {
@Override
public void onSuccess() {
public void onSuccess(Application application) {
runOnUiThread(() -> {
for(Updatable updatable : fragments){
if(updatable instanceof ReportFragment) {
for (Updatable updatable : fragments) {
if (updatable instanceof ReportFragment) {
ApplicationViewModel model = ((ReportFragment) updatable).getModel();
if(model.versionName == null)
if (model.versionName == null)
model.report = DatabaseManager.getInstance(MainActivity.this).getReportFor(model.packageName, model.versionCode, model.source);
else
model.report = DatabaseManager.getInstance(MainActivity.this).getReportFor(model.packageName,model.versionName,model.source);
if(model.report != null)
model.report = DatabaseManager.getInstance(MainActivity.this).getReportFor(model.packageName, model.versionName, model.source);
if (model.report != null)
model.trackers = DatabaseManager.getInstance(MainActivity.this).getTrackers(model.report.trackers);
}
updatable.onUpdateComplete();
@ -92,10 +118,10 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onError(String error) {
runOnUiThread(() -> {
for(Updatable updatable : fragments){
for (Updatable updatable : fragments) {
updatable.onUpdateComplete();
}
Snackbar bar = Snackbar.make(mainBinding.fragmentContainer,error,Snackbar.LENGTH_LONG);
Snackbar bar = Snackbar.make(mainBinding.viewpager, error, Snackbar.LENGTH_LONG);
bar.show();
});
}
@ -106,55 +132,86 @@ public class MainActivity extends AppCompatActivity {
}
};
TrackerListAdapter.OnTrackerClickListener onTrackerClickListener = id -> {
setSupportActionBar(binding.toolbar);
binding.navView.inflateMenu(R.menu.bottom_nav_menu);
binding.navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
binding.viewpager.setOffscreenPageLimit(2);
binding.viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
MenuItem item = binding.navView.getMenu().getItem(position);
binding.navView.setSelectedItemId(item.getItemId());
if (binding.fragmentContainer.getVisibility() == View.VISIBLE) {
while (fragments.size() > 0) {
getSupportFragmentManager().popBackStack();
fragments.remove(fragments.size() - 1);
}
binding.fragmentContainer.setVisibility(View.GONE);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
onTrackerClickListener = id -> {
TrackerFragment tracker = TrackerFragment.newInstance(id);
tracker.setOnAppClickListener(onAppClickListener);
fragments.add(tracker);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
binding.fragmentContainer.setVisibility(View.VISIBLE);
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right, R.anim.slide_in_left, R.anim.slide_out_left)
.replace(R.id.fragment_container,tracker)
.replace(R.id.fragment_container, tracker)
.addToBackStack(null)
.commit();
};
onAppClickListener = vm -> {
onAppClickListener = (vm) -> {
try {
PackageManager pm = getPackageManager();
PackageInfo packageInfo = pm.getPackageInfo(vm.packageName, PackageManager.GET_PERMISSIONS);
ReportFragment report = ReportFragment.newInstance(pm,vm,packageInfo,onTrackerClickListener);
ReportFragment report = ReportFragment.newInstance(pm, vm, packageInfo, onTrackerClickListener);
fragments.add(report);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right, R.anim.slide_in_left, R.anim.slide_out_left)
.replace(R.id.fragment_container,report)
.replace(R.id.fragment_container, report)
.addToBackStack(null)
.commit();
binding.fragmentContainer.setVisibility(View.VISIBLE);
packageName = packageInfo.packageName;
searchView.clearFocus();
if (toolbarMenu != null)
(toolbarMenu.findItem(R.id.action_filter)).collapseActionView();
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
assert imm != null;
imm.hideSoftInputFromWindow(mainBinding.fragmentContainer.getWindowToken(), 0);
imm.hideSoftInputFromWindow(mainBinding.viewpager.getWindowToken(), 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
};
HomeFragment home = new HomeFragment();
home = new HomeFragment();
fragments.add(home);
home.setNetworkListener(networkListener);
home.setOnAppClickListener(onAppClickListener);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_container,home)
.commit();
PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
binding.viewpager.setAdapter(mPagerAdapter);
home.startRefresh();
}
@ -164,56 +221,145 @@ public class MainActivity extends AppCompatActivity {
finish();
else {
getSupportFragmentManager().popBackStack();
fragments.remove(fragments.size()-1);
fragments.remove(fragments.size() - 1);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
toolbarMenu = menu;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
searchView = (SearchView) menu.findItem(R.id.action_filter).getActionView();
searchView.setIconifiedByDefault(false);
MenuItem actionFilterItem = menu.findItem(R.id.action_filter);
searchView = (SearchView) actionFilterItem.getActionView();
searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
searchView.setQuery(previousQuery, false);
}
});
ImageView searchClose = searchView.findViewById(androidx.appcompat.R.id.search_close_btn);
if (searchClose != null) {
searchClose.setOnClickListener(v -> {
previousQuery = "";
searchView.setQuery("", true);
});
}
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
previousQuery = query.trim();
if (!searchView.isIconified()) {
searchView.setIconified(true);
}
menu.findItem(R.id.action_filter).collapseActionView();
HomeFragment home = (HomeFragment) fragments.get(0);
home.filter(query);
return true;
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
HomeFragment home = (HomeFragment) fragments.get(0);
home.filter(newText);
return true;
return false;
}
});
settingsMenuItem = menu.findItem(R.id.action_settings);
Updatable fragment = fragments.get(fragments.size()-1);
if (fragment instanceof ReportFragment)
settingsMenuItem.setVisible(true);
else
MenuItem settingsMenuItem = menu.findItem(R.id.action_settings);
if (fragments.size() > 0) {
Updatable fragment = fragments.get(fragments.size() - 1);
settingsMenuItem.setVisible(fragment instanceof ReportFragment);
} else {
settingsMenuItem.setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.action_settings) {
if (item.getItemId() == R.id.action_settings) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",packageName,null));
intent.setData(Uri.fromParts("package", packageName, null));
try {
startActivity(intent);
} catch(android.content.ActivityNotFoundException e) {
Snackbar bar = Snackbar.make(binding.fragmentContainer,R.string.no_settings,Snackbar.LENGTH_LONG);
} catch (android.content.ActivityNotFoundException e) {
Snackbar bar = Snackbar.make(binding.viewpager, R.string.no_settings, Snackbar.LENGTH_LONG);
bar.show();
}
return true;
} else if (item.getItemId() == R.id.action_filter_options) {
View menuItemView = findViewById(R.id.action_filter_options);
PopupMenu popup = new PopupMenu(binding.viewpager.getContext(), menuItemView);
popup.getMenuInflater()
.inflate(R.menu.popup_menu_filter, popup.getMenu());
MenuItem filterByNameMI = popup.getMenu().findItem(R.id.filter_by_name);
MenuItem lessTrackersMI = popup.getMenu().findItem(R.id.having_less_trackers);
MenuItem mostTrackersMI = popup.getMenu().findItem(R.id.having_most_trackers);
MenuItem lessPermissionsMI = popup.getMenu().findItem(R.id.having_less_permissions);
MenuItem mostPermissionsMI = popup.getMenu().findItem(R.id.having_most_permissions);
switch (order) {
case LESS_TRACKERS:
lessTrackersMI.setChecked(true);
break;
case MOST_TRACKERS:
mostTrackersMI.setChecked(true);
break;
case LESS_PERMISSIONS:
lessPermissionsMI.setChecked(true);
break;
case MOST_PERMISSIONS:
mostPermissionsMI.setChecked(true);
break;
default:
filterByNameMI.setChecked(true);
}
popup.setOnMenuItemClickListener(filter_item -> {
if (filter_item.getItemId() == R.id.filter_by_name) {
order = ComputeAppList.order.DEFAULT;
} else if (filter_item.getItemId() == R.id.having_less_trackers) {
order = ComputeAppList.order.LESS_TRACKERS;
} else if (filter_item.getItemId() == R.id.having_most_trackers) {
order = ComputeAppList.order.MOST_TRACKERS;
} else if (filter_item.getItemId() == R.id.having_most_permissions) {
order = ComputeAppList.order.MOST_PERMISSIONS;
} else if (filter_item.getItemId() == R.id.having_less_permissions) {
order = ComputeAppList.order.LESS_PERMISSIONS;
}
if (fragments != null && fragments.size() > 0 && fragments.get(0) instanceof HomeFragment) {
HomeFragment home = (HomeFragment) fragments.get(0);
home.displayAppListAsync(order);
}
return false;
});
popup.show();
}
return false;
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@NonNull
@Override
public Fragment getItem(final int position) {
//noinspection SwitchStatementWithTooFewBranches
switch (position) {
case 1:
MyTrackersFragment myTrackersFragment = new MyTrackersFragment();
myTrackersFragment.setOnTrackerClickListener(onTrackerClickListener);
return myTrackersFragment;
default:
return home;
}
}
@Override
public int getCount() {
return 2;
}
}
}

View File

@ -2,7 +2,6 @@ package org.eu.exodus_privacy.exodusprivacy;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
@ -17,7 +16,7 @@ import java.text.SimpleDateFormat;
public class ReportViewModel extends BaseObservable {
private ReportDisplay reportDisplay;
public void setReportDisplay(ReportDisplay report){
public void setReportDisplay(ReportDisplay report) {
this.reportDisplay = report;
notifyChange();
}
@ -54,8 +53,8 @@ public class ReportViewModel extends BaseObservable {
@Bindable
public boolean getHasPermissionDangerous() {
for(Permission perm : reportDisplay.permissions) {
if(perm.dangerous)
for (Permission perm : reportDisplay.permissions) {
if (perm.dangerous)
return true;
}
return false;
@ -85,7 +84,7 @@ public class ReportViewModel extends BaseObservable {
String creator = reportDisplay.creator != null ? reportDisplay.creator : "";
if (reportDisplay.report != null && !reportDisplay.report.downloads.isEmpty()) {
String download = reportDisplay.report.downloads;
download = download.replace("downloads",context.getString(R.string.downloads));
download = download.replace("downloads", context.getString(R.string.downloads));
creator += " (" + download + ")";
}
return creator;
@ -103,7 +102,7 @@ public class ReportViewModel extends BaseObservable {
@Bindable
public String getReportVersion() {
if(reportDisplay.report != null) {
if (reportDisplay.report != null) {
if (reportDisplay.versionName != null && !reportDisplay.report.version.equals(reportDisplay.versionName)) {
return reportDisplay.report.version;
} else if (reportDisplay.versionName == null && reportDisplay.report.versionCode != reportDisplay.versionCode) {
@ -125,30 +124,30 @@ public class ReportViewModel extends BaseObservable {
public String getReportDate(Context context) {
String reportDate = "";
if(reportDisplay.report == null)
if (reportDisplay.report == null)
return reportDate;
DateFormat dateFormat = SimpleDateFormat.getDateInstance(DateFormat.LONG);
reportDate = context.getString(R.string.created_date)+" "+dateFormat.format(reportDisplay.report.creationDate.getTime());
if (reportDisplay.report.creationDate.getTime().compareTo(reportDisplay.report.updateDate.getTime())!=0)
reportDate += " "+context.getString(R.string.and_updated)+" "+dateFormat.format(reportDisplay.report.updateDate.getTime())+".";
reportDate = context.getString(R.string.created_date) + " " + dateFormat.format(reportDisplay.report.creationDate.getTime());
if (reportDisplay.report.creationDate.getTime().compareTo(reportDisplay.report.updateDate.getTime()) != 0)
reportDate += " " + context.getString(R.string.and_updated) + " " + dateFormat.format(reportDisplay.report.updateDate.getTime()) + ".";
return reportDate;
}
public String getCodeSignatureInfo(Context context) {
if(reportDisplay.trackers != null && reportDisplay.trackers.size() > 0)
return context.getString(R.string.code_signature_found);
else if(reportDisplay.trackers != null)
return context.getString(R.string.code_signature_not_found);
else
return "";
if (reportDisplay.trackers != null && reportDisplay.trackers.size() > 0)
return context.getString(R.string.code_signature_found);
else if (reportDisplay.trackers != null)
return context.getString(R.string.code_signature_not_found);
else
return "";
}
public String getCodePermissionInfo(Context context) {
if(reportDisplay.permissions != null && reportDisplay.permissions.size() > 0)
if (reportDisplay.permissions != null && reportDisplay.permissions.size() > 0)
return context.getString(R.string.code_permission_found);
else if(reportDisplay.permissions != null)
else if (reportDisplay.permissions != null)
return context.getString(R.string.code_permission_not_found);
else
return "";
@ -158,7 +157,7 @@ public class ReportViewModel extends BaseObservable {
private int getColor(int number) {
if (number == 0)
return R.drawable.square_green;
else if(number < 5)
else if (number < 5)
return R.drawable.square_light_yellow;
else
return R.drawable.square_light_red;
@ -174,5 +173,4 @@ public class ReportViewModel extends BaseObservable {
}
}

View File

@ -1,9 +1,11 @@
package org.eu.exodus_privacy.exodusprivacy;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@ -12,12 +14,25 @@ import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Utils {
@SuppressWarnings({"unused", "RedundantSuppression"})
public static final String TAG = "Exodus_privacy";
public static final String APP_PREFS = "app_prefs";
public static final String LAST_REFRESH = "last_refresh";
public static String getDomain() {
return BuildConfig.amal ? "exodus.phm.education.gouv.fr" : "reports.exodus-privacy.eu.org";
}
@SuppressLint("PackageManagerGetSignatures")
public static String getCertificateSHA1Fingerprint(PackageManager pm, String packageName) {
int flags = PackageManager.GET_SIGNATURES;
@ -34,7 +49,7 @@ public class Utils {
builder.append(packageName);
for(Signature signature: signatures) {
for (Signature signature : signatures) {
InputStream input = new ByteArrayInputStream(signature.toByteArray());
CertificateFactory cf = null;
try {
@ -83,6 +98,47 @@ public class Utils {
return str.toString();
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
*
* @param date Date
* @return String
*/
public static String dateToString(Date date) {
if (date == null)
return null;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
return dateFormat.format(date);
}
/**
* Convert String date from db to Date Object
*
* @param stringDate date to convert
* @return Date
*/
public static Date stringToDate(Context context, String stringDate) {
if (stringDate == null)
return null;
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", userLocale);
Date date = null;
try {
date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) {
}
return date;
}
/*
Simple and not complete markdownToHtml converter
*/
@ -92,18 +148,18 @@ public class Utils {
ArrayList<String> listStarter = new ArrayList<>();
ArrayList<String> formatStarter = new ArrayList<>();
ArrayList<String> closeTags = new ArrayList<>();
for(String line : lines) {
for (String line : lines) {
if (line.matches("^#{1,5} .*")) {
int nb = line.indexOf(" ");
String hx = "<h"+nb+">";
String endhx = "</h"+nb+">";
String hx = "<h" + nb + ">";
String endhx = "</h" + nb + ">";
builder.append(hx);
closeTags.add(endhx);
line = line.substring(line.indexOf(" ")+1);
line = line.substring(line.indexOf(" ") + 1);
} else if (line.matches("^ *[+\\-*] .*")) {
String starter="";
if (listStarter.size() > 0 && line.startsWith(listStarter.get(listStarter.size()-1))) {
starter = listStarter.get(listStarter.size()-1);
String starter = "";
if (listStarter.size() > 0 && line.startsWith(listStarter.get(listStarter.size() - 1))) {
starter = listStarter.get(listStarter.size() - 1);
} else {
Pattern pattern = Pattern.compile("^( *[+\\-*] )");
Matcher matcher = pattern.matcher(line);
@ -115,19 +171,22 @@ public class Utils {
}
}
builder.append("<li> ");
int beginIndex = line.indexOf(starter)+starter.length();
int beginIndex = 0;
if (starter != null) {
beginIndex = line.indexOf(starter) + starter.length();
}
line = line.substring(beginIndex);
closeTags.add("</li>");
} else {
while(!listStarter.isEmpty()) {
while (!listStarter.isEmpty()) {
listStarter.remove(listStarter.size() - 1);
builder.append("</ul>\n");
}
builder.append("<p>");
closeTags.add("</p>");
}
while(!line.isEmpty()){
Pattern pattern = Pattern.compile("^\\[(.+?)(?=\\]\\()\\]\\((http.+?)(?=\\))\\)");
while (!line.isEmpty()) {
Pattern pattern = Pattern.compile("^\\[(.+?)(?=]\\()]\\((http.+?)(?=\\))\\)");
//Pattern pattern = Pattern.compile("^\\[(.*)\\]\\((http.*)\\)");
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
@ -136,7 +195,7 @@ public class Utils {
builder.append("\">");
builder.append(matcher.group(1));
builder.append("</a>");
line = line.substring(line.indexOf(")")+1);
line = line.substring(line.indexOf(")") + 1);
continue;
}
pattern = Pattern.compile("^(http.*)");
@ -147,44 +206,45 @@ public class Utils {
builder.append("\">");
builder.append(matcher.group(1));
builder.append("</a>");
line = line.substring(matcher.group(1).length());
String sub = matcher.group(1);
if (sub != null) {
line = line.substring(sub.length());
}
continue;
}
pattern = Pattern.compile("^[*_]{2}(.+)[*_]{2}");
matcher = pattern.matcher(line);
if (matcher.find()) {
if(line.startsWith("*")) {
if (line.startsWith("*")) {
line = line.replaceFirst("\\*\\*", "<b>");
formatStarter.add("**");
}
else {
} else {
line = line.replaceFirst("__", "<b>");
formatStarter.add("__");
}
continue;
}
pattern = Pattern.compile("^[*_]{1}(.+)");
pattern = Pattern.compile("^[*_](.+)");
matcher = pattern.matcher(line);
if (matcher.find()) {
if(line.startsWith("*")) {
if (line.startsWith("*")) {
line = line.replaceFirst("\\*", "<i>");
formatStarter.add("*");
}
else {
} else {
line = line.replaceFirst("_", "<i>");
formatStarter.add("_");
}
continue;
}
if(formatStarter.size() > 0) {
if (formatStarter.size() > 0) {
String checkFormat;
if(line.contains(" "))
checkFormat = line.substring(0,line.indexOf(" "));
if (line.contains(" "))
checkFormat = line.substring(0, line.indexOf(" "));
else
checkFormat = line;
String lastFormat = formatStarter.get(formatStarter.size()-1);
String lastFormat = formatStarter.get(formatStarter.size() - 1);
if (checkFormat.contains(lastFormat)) {
if(lastFormat.length()==2) {
if (lastFormat.length() == 2) {
if (lastFormat.contains("*"))
line = line.replaceFirst("\\*\\*", "</b>");
else
@ -195,12 +255,12 @@ public class Utils {
else
line = line.replaceFirst("_", "</i>");
}
formatStarter.remove(formatStarter.size()-1);
formatStarter.remove(formatStarter.size() - 1);
continue;
}
}
if(line.contains(" ")) {
if (line.contains(" ")) {
builder.append(line.substring(0, line.indexOf(" ") + 1));
line = line.substring(line.indexOf(" ") + 1);
} else {
@ -209,13 +269,13 @@ public class Utils {
}
}
//close all unclosed tags starting at the end
while(!closeTags.isEmpty()) {
builder.append(closeTags.remove(closeTags.size()-1));
while (!closeTags.isEmpty()) {
builder.append(closeTags.remove(closeTags.size() - 1));
}
builder.append("\n");
}
while(!listStarter.isEmpty()) {
while (!listStarter.isEmpty()) {
listStarter.remove(listStarter.size() - 1);
builder.append("</ul>\n");
}

View File

@ -34,50 +34,34 @@ import org.eu.exodus_privacy.exodusprivacy.objects.Report;
import org.eu.exodus_privacy.exodusprivacy.objects.Tracker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class ApplicationListAdapter extends RecyclerView.Adapter {
public class ApplicationListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<ApplicationViewModel> applicationViewModels;
private OnAppClickListener onAppClickListener;
private Object filter = "";
private AppListFragment.Type filterType = AppListFragment.Type.NAME;
private final OnAppClickListener onAppClickListener;
private final int HIDDEN_APP = 0;
private final int DISPLAYED_APP = 1;
private List<ApplicationViewModel> applicationViewModels;
private Object filter = "";
private AppListFragment.Type filterType = AppListFragment.Type.NAME;
private int displayedApp = 0;
private Comparator<ApplicationViewModel> alphaPackageComparator = new Comparator<ApplicationViewModel>() {
@Override
public int compare(ApplicationViewModel app1, ApplicationViewModel app2) {
if(app1.label != null && app2.label != null)
return app1.label.toString().compareToIgnoreCase(app2.label.toString());
else if(app2.label != null)
return -1;
else if(app1.label != null)
return 1;
else
return 0;
}
};
public ApplicationListAdapter(Context context, OnAppClickListener listener) {
public ApplicationListAdapter(OnAppClickListener listener) {
applicationViewModels = new ArrayList<>();
onAppClickListener = listener;
}
@Override
public int getItemViewType(int position){
public int getItemViewType(int position) {
return applicationViewModels.get(position).isVisible ? DISPLAYED_APP : HIDDEN_APP;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if( viewType == HIDDEN_APP)
if (viewType == HIDDEN_APP)
return new ApplicationEmptyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.app_item_empty, parent, false));
else
return new ApplicationListViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.app_item, parent, false));
@ -86,18 +70,13 @@ public class ApplicationListAdapter extends RecyclerView.Adapter {
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if( viewHolder.getItemViewType() == DISPLAYED_APP) {
if (viewHolder.getItemViewType() == DISPLAYED_APP) {
final ApplicationListViewHolder holder = (ApplicationListViewHolder) viewHolder;
ApplicationViewModel vm = applicationViewModels.get(position);
holder.setViewModel(vm);
//noinspection Convert2Lambda
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onAppClickListener.onAppClick(vm);
}
});
}else {
holder.itemView.setOnClickListener(v -> onAppClickListener.onAppClick(vm));
} else //noinspection RedundantSuppression
{
//noinspection unused
final ApplicationEmptyViewHolder holder = (ApplicationEmptyViewHolder) viewHolder;
//If something should be done for app that are hidden, it's here
@ -111,15 +90,52 @@ public class ApplicationListAdapter extends RecyclerView.Adapter {
public void displayAppList(List<ApplicationViewModel> applications) {
applicationViewModels = applications;
Collections.sort(applicationViewModels, alphaPackageComparator);
filter(filterType,filter);
filter(filterType, filter);
}
public int getDisplayedApps() {
return displayedApp;
}
static class ApplicationEmptyViewHolder extends RecyclerView.ViewHolder{
public void filter(AppListFragment.Type type, Object filterObject) {
displayedApp = 0;
if (type.equals(AppListFragment.Type.NAME)) {
filter = filterObject;
filterType = type;
String filterStr = (String) filterObject;
Pattern p = Pattern.compile(Pattern.quote(filterStr.trim()), Pattern.CASE_INSENSITIVE);
for (ApplicationViewModel app : applicationViewModels) {
app.isVisible = p.matcher(app.label).find();
if (app.isVisible)
displayedApp++;
}
} else if (type.equals(AppListFragment.Type.TRACKER)) {
filter = filterObject;
filterType = type;
Long filterLng = (Long) filterObject;
for (ApplicationViewModel app : applicationViewModels) {
app.isVisible = false;
if (app.trackers != null) {
for (Tracker tracker : app.trackers) {
if (tracker.id == filterLng) {
app.isVisible = true;
displayedApp++;
break;
}
}
}
}
}
notifyDataSetChanged();
}
public interface OnAppClickListener {
void onAppClick(ApplicationViewModel vm);
}
static class ApplicationEmptyViewHolder extends RecyclerView.ViewHolder {
ApplicationEmptyViewHolder(View itemView) {
super(itemView);
}
@ -152,11 +168,11 @@ public class ApplicationListAdapter extends RecyclerView.Adapter {
appItemBinding.appLogo.setImageDrawable(viewModel.icon);
appItemBinding.appName.setText(viewModel.label);
appItemBinding.source.setText(context.getString(R.string.source,viewModel.source));
appItemBinding.source.setText(context.getString(R.string.source, viewModel.source));
long size = viewModel.requestedPermissions != null ? viewModel.requestedPermissions.length : 0;
appItemBinding.appPermissionNb.setText(String.valueOf(size));
if(size == 0)
if (size == 0)
appItemBinding.appPermissionNb.setBackgroundResource(R.drawable.square_green);
else if (size < 5)
appItemBinding.appPermissionNb.setBackgroundResource(R.drawable.square_light_yellow);
@ -164,24 +180,24 @@ public class ApplicationListAdapter extends RecyclerView.Adapter {
appItemBinding.appPermissionNb.setBackgroundResource(R.drawable.square_light_red);
Report report = viewModel.report;
if(report != null) {
if (report != null) {
Set<Tracker> trackers = viewModel.trackers;
size = trackers.size();
appItemBinding.appTrackerNb.setText(String.valueOf(size));
if(size == 0)
if (size == 0)
appItemBinding.appTrackerNb.setBackgroundResource(R.drawable.square_green);
else if (size < 5)
appItemBinding.appTrackerNb.setBackgroundResource(R.drawable.square_light_yellow);
else
appItemBinding.appTrackerNb.setBackgroundResource(R.drawable.square_light_red);
if(versionName != null && !report.version.equals(viewModel.versionName)) {
String string = context.getString(R.string.tested,versionName, report.version);
if (versionName != null && !report.version.equals(viewModel.versionName)) {
String string = context.getString(R.string.tested, versionName, report.version);
appItemBinding.otherVersion.setText(string);
appItemBinding.otherVersion.setVisibility(View.VISIBLE);
} else if (versionName == null && report.versionCode != versionCode) {
String string = context.getString(R.string.tested,String.valueOf(versionCode),String.valueOf(report.versionCode));
String string = context.getString(R.string.tested, String.valueOf(versionCode), String.valueOf(report.versionCode));
appItemBinding.otherVersion.setText(string);
appItemBinding.otherVersion.setVisibility(View.VISIBLE);
}
@ -192,42 +208,4 @@ public class ApplicationListAdapter extends RecyclerView.Adapter {
}
}
}
public interface OnAppClickListener {
void onAppClick(ApplicationViewModel vm);
}
public void filter(AppListFragment.Type type, Object filterObject) {
displayedApp = 0;
if (type.equals(AppListFragment.Type.NAME)) {
filter = filterObject;
filterType = type;
String filterStr = (String) filterObject;
Pattern p = Pattern.compile(Pattern.quote(filterStr.trim()), Pattern.CASE_INSENSITIVE);
for (ApplicationViewModel app : applicationViewModels) {
app.isVisible = p.matcher(app.label).find();
if(app.isVisible)
displayedApp++;
}
} else if(type.equals(AppListFragment.Type.TRACKER)) {
filter = filterObject;
filterType = type;
Long filterLng = (Long) filterObject;
for (ApplicationViewModel app : applicationViewModels) {
app.isVisible = false;
if (app.trackers != null) {
for (Tracker tracker : app.trackers) {
if (tracker.id == filterLng) {
app.isVisible = true;
displayedApp++;
break;
}
}
}
}
}
notifyDataSetChanged();
}
}

View File

@ -0,0 +1,109 @@
package org.eu.exodus_privacy.exodusprivacy.adapters;
/*
* Copyright (C) 2020 Thomas Schneider
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.databinding.MyTrackerItemBinding;
import org.eu.exodus_privacy.exodusprivacy.objects.MyTracker;
import java.util.List;
public class MyTrackersListAdapter extends RecyclerView.Adapter<MyTrackersListAdapter.TrackerListViewHolder> {
private final TrackerClickListener trackerClickListener;
private final List<MyTracker> myTrackers;
private final int max, installedApps;
private int viewWidth = 0;
public MyTrackersListAdapter(List<MyTracker> mTrackers, TrackerClickListener listener, int maxValue, int appInstalled) {
myTrackers = mTrackers;
trackerClickListener = listener;
max = maxValue;
installedApps = appInstalled;
}
@NonNull
@Override
public MyTrackersListAdapter.TrackerListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
MyTrackerItemBinding itemBinding = MyTrackerItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new TrackerListViewHolder(itemBinding);
}
@Override
public void onBindViewHolder(@NonNull MyTrackersListAdapter.TrackerListViewHolder holder, int position) {
if (myTrackers != null) {
MyTracker myTracker = myTrackers.get(position);
holder.viewDataBinding.trackerName.setText(myTracker.tracker.name);
holder.viewDataBinding.trackerCount.setText(holder.viewDataBinding.trackerCount.getContext().getString(R.string.apps, String.valueOf(myTracker.number)));
holder.viewDataBinding.getRoot().setOnClickListener(v -> trackerClickListener.onTrackerClick(myTracker.tracker.id));
float percent = (float) myTracker.number / (float) max;
int percentApp = myTracker.number * 100 / installedApps;
holder.viewDataBinding.percent.getLayoutParams().width = (int) (viewWidth * percent);
holder.viewDataBinding.percentVal.setText(String.format("%s %%", percentApp));
if (percentApp >= 50)
holder.viewDataBinding.trackerCount.setBackgroundResource(R.drawable.square_red);
else if (percentApp >= 33)
holder.viewDataBinding.trackerCount.setBackgroundResource(R.drawable.square_dark_orange);
else if (percentApp >= 20)
holder.viewDataBinding.trackerCount.setBackgroundResource(R.drawable.square_yellow);
else
holder.viewDataBinding.trackerCount.setBackgroundResource(R.drawable.square_light_blue);
holder.viewDataBinding.percent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
holder.viewDataBinding.percent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (viewWidth == 0) {
viewWidth = holder.viewDataBinding.percent.getWidth();
notifyDataSetChanged();
}
}
});
} else
holder.viewDataBinding.trackerName.setText(R.string.no_trackers);
}
@Override
public int getItemCount() {
return myTrackers.size();
}
public interface TrackerClickListener {
void onTrackerClick(long trackerId);
}
static class TrackerListViewHolder extends RecyclerView.ViewHolder {
MyTrackerItemBinding viewDataBinding;
TrackerListViewHolder(MyTrackerItemBinding dataBinding) {
super(dataBinding.getRoot());
viewDataBinding = dataBinding;
}
}
}

View File

@ -14,7 +14,7 @@ import org.eu.exodus_privacy.exodusprivacy.objects.Permission;
import java.util.List;
public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.TrackerListViewHolder>{
public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.TrackerListViewHolder> {
private List<Permission> permissionList;
@ -25,13 +25,13 @@ public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAd
@NonNull
@Override
public PermissionListAdapter.TrackerListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
PermissionItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),R.layout.permission_item,parent,false);
PermissionItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.permission_item, parent, false);
return new TrackerListViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull PermissionListAdapter.TrackerListViewHolder holder, int position) {
if(permissionList == null || permissionList.size() == 0)
if (permissionList == null || permissionList.size() == 0)
holder.setupData(null);
else
holder.setupData(permissionList.get(position));
@ -39,7 +39,7 @@ public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAd
@Override
public int getItemCount() {
if(permissionList == null || permissionList.size() == 0)
if (permissionList == null || permissionList.size() == 0)
return 0;
else
return permissionList.size();
@ -50,7 +50,7 @@ public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAd
}
class TrackerListViewHolder extends RecyclerView.ViewHolder {
static class TrackerListViewHolder extends RecyclerView.ViewHolder {
PermissionItemBinding permissionItemBinding;
@ -60,32 +60,30 @@ public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAd
}
void setupData(Permission permission) {
if(permission != null) {
if(permission.name != null) {
if (permission != null) {
if (permission.name != null) {
permissionItemBinding.permissionShort.setText(permission.name);
permissionItemBinding.permissionShort.setVisibility(View.VISIBLE);
}
else
} else
permissionItemBinding.permissionShort.setVisibility(View.GONE);
permissionItemBinding.permissionName.setText(permission.fullName.substring(permission.fullName.lastIndexOf(".")+1));
permissionItemBinding.permissionName.setText(permission.fullName.substring(permission.fullName.lastIndexOf(".") + 1));
permissionItemBinding.permissionDescription.setText(permission.description);
if(permission.icon != null)
if (permission.icon != null)
permissionItemBinding.icon.setImageDrawable(permission.icon);
if(!permission.dangerous)
if (!permission.dangerous)
permissionItemBinding.dangerous.setVisibility(View.GONE);
else
permissionItemBinding.dangerous.setVisibility(View.VISIBLE);
manageExpanded(permission);
permissionItemBinding.mainLayout.setOnClickListener((View.OnClickListener) v -> {
if( permission.description != null && permission.description.trim().length() > 0) {
permissionItemBinding.mainLayout.setOnClickListener(v -> {
if (permission.description != null && permission.description.trim().length() > 0) {
permission.expanded = !permission.expanded;
manageExpanded(permission);
}
});
}
else {
} else {
permissionItemBinding.permissionName.setText(R.string.no_permissions);
permissionItemBinding.arrow.setText(" ");
permissionItemBinding.permissionShort.setVisibility(View.GONE);
@ -96,11 +94,11 @@ public class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAd
}
void manageExpanded(Permission permission) {
if(permission.expanded) {
if (permission.expanded) {
permissionItemBinding.arrow.setText("");
permissionItemBinding.permissionDescription.setVisibility(View.VISIBLE);
} else {
if( permission.description != null && permission.description.trim().length() > 0 )
if (permission.description != null && permission.description.trim().length() > 0)
permissionItemBinding.arrow.setText("");
else
permissionItemBinding.arrow.setText("");

View File

@ -1,7 +1,6 @@
package org.eu.exodus_privacy.exodusprivacy.adapters;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@ -20,11 +19,12 @@ import java.util.Comparator;
import java.util.List;
import java.util.Set;
public class TrackerListAdapter extends RecyclerView.Adapter<TrackerListAdapter.TrackerListViewHolder>{
public class TrackerListAdapter extends RecyclerView.Adapter<TrackerListAdapter.TrackerListViewHolder> {
private final OnTrackerClickListener trackerClickListener;
private final int layout;
private final Comparator<Tracker> alphaTrackerComparator = (track1, track2) -> track1.name.compareToIgnoreCase(track2.name);
private List<Tracker> trackersList;
private OnTrackerClickListener trackerClickListener;
private int layout;
public TrackerListAdapter(Set<Tracker> trackerList, int resource, OnTrackerClickListener listener) {
setTrackers(trackerList);
@ -35,13 +35,13 @@ public class TrackerListAdapter extends RecyclerView.Adapter<TrackerListAdapter.
@NonNull
@Override
public TrackerListAdapter.TrackerListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),layout,parent,false);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layout, parent, false);
return new TrackerListViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull TrackerListAdapter.TrackerListViewHolder holder, int position) {
if(trackersList == null || trackersList.size() == 0)
if (trackersList == null || trackersList.size() == 0)
holder.setupData(null);
else
holder.setupData(trackersList.get(position));
@ -49,21 +49,23 @@ public class TrackerListAdapter extends RecyclerView.Adapter<TrackerListAdapter.
@Override
public int getItemCount() {
if(trackersList == null || trackersList.size() == 0)
if (trackersList == null || trackersList.size() == 0)
return 1;
else
return trackersList.size();
}
private Comparator<Tracker> alphaTrackerComparator = (track1, track2) -> track1.name.compareToIgnoreCase(track2.name);
public void setTrackers(Set<Tracker> trackers) {
if(trackers != null) {
if (trackers != null) {
trackersList = new ArrayList<>(trackers);
Collections.sort(trackersList, alphaTrackerComparator);
}
}
public interface OnTrackerClickListener {
void onTrackerClick(long trackerId);
}
class TrackerListViewHolder extends RecyclerView.ViewHolder {
ViewDataBinding viewDataBinding;
@ -74,23 +76,16 @@ public class TrackerListAdapter extends RecyclerView.Adapter<TrackerListAdapter.
}
void setupData(Tracker tracker) {
if(viewDataBinding instanceof TrackerItemBinding) {
if (viewDataBinding instanceof TrackerItemBinding) {
TrackerItemBinding binding = (TrackerItemBinding) viewDataBinding;
if(tracker != null) {
binding.trackerName.setText(tracker.name + "");
binding.getRoot().setOnClickListener(v -> {
trackerClickListener.onTrackerClick(tracker.id);
});
}
else
if (tracker != null) {
binding.trackerName.setText(String.format("%s ➤", tracker.name));
binding.getRoot().setOnClickListener(v -> trackerClickListener.onTrackerClick(tracker.id));
} else
binding.trackerName.setText(R.string.no_trackers);
}
}
}
public interface OnTrackerClickListener{
public void onTrackerClick(long trackerId);
}
}

View File

@ -21,6 +21,7 @@ import java.util.List;
public class AppListFragment extends Fragment {
private ApplistBinding applistBinding;
private List<ApplicationViewModel> applications;
private ApplicationListAdapter adapter;
@ -32,36 +33,38 @@ public class AppListFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//create binding
applistBinding = DataBindingUtil.inflate(inflater, R.layout.applist,container,false);
applistBinding = DataBindingUtil.inflate(inflater, R.layout.applist, container, false);
//init variables
if (applications == null)
applications = new ArrayList<>();
Context context = applistBinding.getRoot().getContext();
//configure list
applistBinding.appList.setLayoutManager(new LinearLayoutManager(context));
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
applistBinding.appList.setLayoutManager(linearLayoutManager);
applistBinding.appList.setVerticalScrollBarEnabled(scrollbarEnabled);
adapter = new ApplicationListAdapter(context, onAppClickListener);
adapter = new ApplicationListAdapter(onAppClickListener);
adapter.displayAppList(applications);
adapter.filter(filterType,filterObject);
adapter.filter(filterType, filterObject);
applistBinding.appList.setAdapter(adapter);
return applistBinding.getRoot();
}
public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener listener){
public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener listener) {
onAppClickListener = listener;
}
public void setApplications(List<ApplicationViewModel> applicationList){
public void setApplications(List<ApplicationViewModel> applicationList) {
applications = applicationList;
if(adapter != null)
if (adapter != null)
adapter.displayAppList(applications);
}
public void setFilter(Type type, Object filter){
public void setFilter(Type type, Object filter) {
filterType = type;
filterObject = filter;
if(adapter != null)
adapter.filter(type,filterObject);
if (adapter != null)
adapter.filter(type, filterObject);
}
public void disableScrollBar() {
@ -70,6 +73,7 @@ public class AppListFragment extends Fragment {
applistBinding.appList.setVerticalScrollBarEnabled(false);
}
@SuppressWarnings({"unused", "RedundantSuppression"})
public void enableScrollBar() {
scrollbarEnabled = true;
if (applistBinding != null)
@ -88,17 +92,9 @@ public class AppListFragment extends Fragment {
return adapter.getDisplayedApps();
}
public enum Type {
NAME,
TRACKER
}
public enum Order {
NAME_ASC,
NAME_DSC,
TRACKER_ASC,
TRACKER_DSC,
PERMISSIONS_ASC,
PERMISSIONS_DSC
}
}

View File

@ -0,0 +1,153 @@
package org.eu.exodus_privacy.exodusprivacy.fragments;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import org.eu.exodus_privacy.exodusprivacy.Utils;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ComputeAppList {
private static final String gStore = "com.android.vending";
private static final String fdroid = "org.fdroid.fdroid";
public static List<ApplicationViewModel> compute(PackageManager packageManager,
DatabaseManager databaseManager, order userOrderChoice) {
List<ApplicationViewModel> vms = new ArrayList<>();
if (packageManager != null && databaseManager != null) {
List<PackageInfo> installedPackages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
vms = applyStoreFilter(installedPackages, databaseManager, packageManager);
convertPackagesToViewModels(vms, databaseManager, packageManager);
}
//Reordering should done here
if (userOrderChoice == null) {
userOrderChoice = order.DEFAULT;
}
order(vms, userOrderChoice);
return vms;
}
/**
* Filter List<ApplicationViewModel> with one of criteria of order
*
* @param vms List<ApplicationViewModel>
* @param orderChoice order
*/
private static void order(List<ApplicationViewModel> vms, order orderChoice) {
if (orderChoice == order.LESS_TRACKERS) {
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0, obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0));
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.trackers != null ? obj1.trackers.size() : 0, obj2.trackers != null ? obj2.trackers.size() : 0));
} else if (orderChoice == order.MOST_TRACKERS) {
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0, obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0));
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.trackers != null ? obj2.trackers.size() : 0, obj1.trackers != null ? obj1.trackers.size() : 0));
} else if (orderChoice == order.LESS_PERMISSIONS) {
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.trackers != null ? obj1.trackers.size() : 0, obj2.trackers != null ? obj2.trackers.size() : 0));
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0, obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0));
} else if (orderChoice == order.MOST_PERMISSIONS) {
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.trackers != null ? obj2.trackers.size() : 0, obj1.trackers != null ? obj1.trackers.size() : 0));
Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0, obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0));
} else {
Collections.sort(vms, (obj1, obj2) -> String.valueOf(obj1.label).compareToIgnoreCase(String.valueOf(obj2.label)));
}
}
private static void convertPackagesToViewModels(List<ApplicationViewModel> appsToBuild,
DatabaseManager databaseManager,
PackageManager packageManager) {
for (ApplicationViewModel vm : appsToBuild) {
try {
PackageInfo pi = packageManager.getPackageInfo(vm.packageName, PackageManager.GET_PERMISSIONS);
buildViewModelFromPackageInfo(vm, pi, databaseManager, packageManager);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
public static void buildViewModelFromPackageInfo(ApplicationViewModel vm, PackageInfo pi,
DatabaseManager databaseManager,
PackageManager packageManager) {
vm.versionName = pi.versionName;
vm.packageName = pi.packageName;
vm.versionCode = pi.versionCode;
vm.requestedPermissions = pi.requestedPermissions;
if (vm.versionName != null)
vm.report = databaseManager.getReportFor(vm.packageName, vm.versionName, vm.source);
else {
vm.report = databaseManager.getReportFor(vm.packageName, vm.versionCode, vm.source);
}
if (vm.report != null) {
vm.trackers = databaseManager.getTrackers(vm.report.trackers);
}
try {
vm.icon = packageManager.getApplicationIcon(vm.packageName);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
vm.label = packageManager.getApplicationLabel(pi.applicationInfo);
vm.installerPackageName = packageManager.getInstallerPackageName(vm.packageName);
vm.isVisible = true;
}
private static List<ApplicationViewModel> applyStoreFilter(List<PackageInfo> packageInfos,
DatabaseManager databaseManager,
PackageManager packageManager) {
List<ApplicationViewModel> result = new ArrayList<>();
for (PackageInfo packageInfo : packageInfos) {
String packageName = packageInfo.packageName;
String installerPackageName = packageManager.getInstallerPackageName(packageName);
ApplicationViewModel vm = new ApplicationViewModel();
vm.packageName = packageName;
if (!gStore.equals(installerPackageName) && !fdroid.equals(installerPackageName)) {
String auid = Utils.getCertificateSHA1Fingerprint(packageManager, packageName);
Map<String, String> sources = databaseManager.getSources(packageName);
for (Map.Entry<String, String> entry : sources.entrySet()) {
if (entry.getValue().equalsIgnoreCase(auid)) {
vm.source = entry.getKey();
break;
}
}
} else if (gStore.equals(installerPackageName)) {
vm.source = "google";
} else {
vm.source = "fdroid";
}
ApplicationInfo appInfo = null;
try {
appInfo = packageManager.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (vm.source != null && appInfo != null && appInfo.enabled)
result.add(vm);
}
return result;
}
public enum order {
DEFAULT,
MOST_TRACKERS,
LESS_TRACKERS,
MOST_PERMISSIONS,
LESS_PERMISSIONS,
}
interface Listener {
void onAppsComputed(List<ApplicationViewModel> apps);
}
}

View File

@ -1,138 +0,0 @@
package org.eu.exodus_privacy.exodusprivacy.fragments;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import org.eu.exodus_privacy.exodusprivacy.Utils;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class ComputeAppListTask extends AsyncTask<Void, Void, List<ApplicationViewModel>> {
interface Listener {
void onAppsComputed(List<ApplicationViewModel> apps);
}
private static final String gStore = "com.android.vending";
private static final String fdroid = "ord.fdroid.fdroid";
private WeakReference<PackageManager> packageManagerRef;
private WeakReference<DatabaseManager> databaseManagerRef;
private WeakReference<Listener> listenerRef;
ComputeAppListTask(WeakReference<PackageManager> packageManagerRef,
WeakReference<DatabaseManager> databaseManagerRef,
WeakReference<Listener> listenerRef) {
this.packageManagerRef = packageManagerRef;
this.databaseManagerRef = databaseManagerRef;
this.listenerRef = listenerRef;
}
protected List<ApplicationViewModel> doInBackground(Void... params) {
PackageManager packageManager = packageManagerRef.get();
DatabaseManager databaseManager = databaseManagerRef.get();
List<ApplicationViewModel> vms = new ArrayList<>();
if(packageManager != null && databaseManager != null) {
List<PackageInfo> installedPackages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
vms = applyStoreFilter(installedPackages, databaseManager, packageManager);
convertPackagesToViewModels(vms, databaseManager, packageManager);
}
return vms;
}
@Override
protected void onPostExecute(List<ApplicationViewModel> vms) {
Listener listener = listenerRef.get();
if(listener != null) {
listener.onAppsComputed(vms);
}
}
private void convertPackagesToViewModels(List<ApplicationViewModel> appsToBuild,
DatabaseManager databaseManager,
PackageManager packageManager) {
for (ApplicationViewModel vm : appsToBuild) {
try {
PackageInfo pi = packageManager.getPackageInfo(vm.packageName, PackageManager.GET_PERMISSIONS);
buildViewModelFromPackageInfo(vm, pi, databaseManager, packageManager);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
private void buildViewModelFromPackageInfo(ApplicationViewModel vm, PackageInfo pi,
DatabaseManager databaseManager,
PackageManager packageManager) {
vm.versionName = pi.versionName;
vm.packageName = pi.packageName;
vm.versionCode = pi.versionCode;
vm.requestedPermissions = pi.requestedPermissions;
if (vm.versionName != null)
vm.report = databaseManager.getReportFor(vm.packageName, vm.versionName, vm.source);
else {
vm.report = databaseManager.getReportFor(vm.packageName, vm.versionCode, vm.source);
}
if (vm.report != null) {
vm.trackers = databaseManager.getTrackers(vm.report.trackers);
}
try {
vm.icon = packageManager.getApplicationIcon(vm.packageName);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
vm.label = packageManager.getApplicationLabel(pi.applicationInfo);
vm.installerPackageName = packageManager.getInstallerPackageName(vm.packageName);
vm.isVisible = true;
}
private List<ApplicationViewModel> applyStoreFilter(List<PackageInfo> packageInfos,
DatabaseManager databaseManager,
PackageManager packageManager) {
List<ApplicationViewModel> result = new ArrayList<>();
for (PackageInfo packageInfo : packageInfos) {
String packageName = packageInfo.packageName;
String installerPackageName = packageManager.getInstallerPackageName(packageName);
ApplicationViewModel vm = new ApplicationViewModel();
vm.packageName = packageName;
if (!gStore.equals(installerPackageName) && !fdroid.equals(installerPackageName)) {
String auid = Utils.getCertificateSHA1Fingerprint(packageManager, packageName);
Map<String,String> sources = databaseManager.getSources(packageName);
for(Map.Entry<String,String> entry : sources.entrySet()) {
if(entry.getValue().equalsIgnoreCase(auid)) {
vm.source = entry.getKey();
break;
}
}
} else if (gStore.equals(installerPackageName)) {
vm.source = "google";
} else {
vm.source = "fdroid";
}
ApplicationInfo appInfo = null;
try {
appInfo = packageManager.getApplicationInfo(packageName,0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if(vm.source != null && appInfo != null && appInfo.enabled)
result.add(vm);
}
return result;
}
}

View File

@ -1,10 +1,15 @@
package org.eu.exodus_privacy.exodusprivacy.fragments;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -17,18 +22,24 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.Utils;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationListAdapter;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.databinding.HomeBinding;
import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import org.eu.exodus_privacy.exodusprivacy.manager.NetworkManager;
import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class HomeFragment extends Fragment implements ComputeAppListTask.Listener, Updatable {
import static android.content.Context.MODE_PRIVATE;
public class HomeFragment extends Fragment implements ComputeAppList.Listener, Updatable {
private @Nullable
PackageManager packageManager;
@ -41,48 +52,56 @@ public class HomeFragment extends Fragment implements ComputeAppListTask.Listene
private boolean startRefreshAsked;
private boolean refreshInProgress;
private int lastResource=0;
private int lastProgress=0;
private int lastMaxProgress=0;
private int lastResource = 0;
private int lastProgress = 0;
private int lastMaxProgress = 0;
private String last_refresh;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(applications == null)
if (applications == null)
applications = new ArrayList<>();
homeBinding = DataBindingUtil.inflate(inflater, R.layout.home,container,false);
homeBinding = DataBindingUtil.inflate(inflater, R.layout.home, container, false);
appListFragment = new AppListFragment();
appListFragment.setOnAppClickListener(onAppClickListener);
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.app_list_container,appListFragment);
transaction.replace(R.id.app_list_container, appListFragment);
transaction.commit();
Context context = homeBinding.getRoot().getContext();
packageManager = context.getPackageManager();
homeBinding.swipeRefresh.setOnRefreshListener(this::startRefresh);
if(packageManager != null) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Utils.APP_PREFS, MODE_PRIVATE);
last_refresh = sharedPreferences.getString(Utils.LAST_REFRESH, null);
if (packageManager != null) {
homeBinding.noPackageManager.setVisibility(View.GONE);
onAppsComputed(applications);
if(applications.isEmpty())
displayAppListAsync();
if(startRefreshAsked)
if (applications.isEmpty())
displayAppListAsync(null);
if (startRefreshAsked && last_refresh == null)
startRefresh();
else if (refreshInProgress) {
homeBinding.layoutProgress.setVisibility(View.VISIBLE);
homeBinding.swipeRefresh.setRefreshing(true);
updateProgress(lastResource,lastProgress,lastMaxProgress);
updateProgress(lastResource, lastProgress, lastMaxProgress);
}
} else {
homeBinding.noPackageManager.setVisibility(View.VISIBLE);
}
return homeBinding.getRoot();
}
public void startRefresh(){
if(packageManager != null) {
public void startRefresh() {
if (packageManager != null) {
refreshInProgress = true;
homeBinding.layoutProgress.setVisibility(View.VISIBLE);
homeBinding.swipeRefresh.setRefreshing(true);
List<PackageInfo> packageInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
@SuppressLint("QueryPermissionsNeeded") List<PackageInfo> packageInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
ArrayList<String> packageList = new ArrayList<>();
for (PackageInfo pkgInfo : packageInstalled)
packageList.add(pkgInfo.packageName);
@ -94,19 +113,28 @@ public class HomeFragment extends Fragment implements ComputeAppListTask.Listene
}
}
@Override
public void onResume() {
super.onResume();
if (!refreshInProgress && homeBinding.layoutProgress.getVisibility() == View.VISIBLE) {
onUpdateComplete();
}
}
@Override
public void onUpdateComplete() {
refreshInProgress = false;
homeBinding.layoutProgress.setVisibility(View.GONE);
homeBinding.swipeRefresh.setRefreshing(false);
displayAppListAsync();
displayAppListAsync(null);
}
public void setNetworkListener(NetworkListener listener) {
this.networkListener = new NetworkListener() {
@Override
public void onSuccess() {
listener.onSuccess();
public void onSuccess(Application application) {
listener.onSuccess(application);
}
@Override
@ -124,16 +152,16 @@ public class HomeFragment extends Fragment implements ComputeAppListTask.Listene
lastResource = resourceId;
lastProgress = progress;
lastMaxProgress = maxProgress;
if(lastResource == 0)
if (lastResource == 0)
return;
Activity activity = getActivity();
if(activity == null)
if (activity == null)
return;
activity.runOnUiThread(() -> {
if (homeBinding == null)
return;
if(maxProgress > 0)
homeBinding.statusProgress.setText(activity.getString(resourceId)+" "+progress+"/"+maxProgress);//fixme
if (maxProgress > 0)
homeBinding.statusProgress.setText(String.format(Locale.getDefault(), "%s %d/%d", activity.getString(resourceId), progress, maxProgress));
else
homeBinding.statusProgress.setText(activity.getString(resourceId));
homeBinding.progress.setMax(maxProgress);
@ -144,26 +172,27 @@ public class HomeFragment extends Fragment implements ComputeAppListTask.Listene
public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener onAppClickListener) {
this.onAppClickListener = onAppClickListener;
if(appListFragment != null)
if (appListFragment != null)
appListFragment.setOnAppClickListener(onAppClickListener);
}
public void filter(String filter){
appListFragment.setFilter(AppListFragment.Type.NAME,filter);
public void filter(String filter) {
appListFragment.setFilter(AppListFragment.Type.NAME, filter);
}
private void displayAppListAsync() {
public void displayAppListAsync(ComputeAppList.order orderList) {
homeBinding.noAppFound.setVisibility(View.GONE);
if (applications.isEmpty()) {
homeBinding.retrieveApp.setVisibility(View.VISIBLE);
homeBinding.logo.setVisibility(View.VISIBLE);
}
new ComputeAppListTask(
new WeakReference<>(packageManager),
new WeakReference<>(DatabaseManager.getInstance(getActivity())),
new WeakReference<>(this)
).execute();
new Thread(() -> {
List<ApplicationViewModel> vms = ComputeAppList.compute(packageManager, DatabaseManager.getInstance(getActivity()), orderList);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> onAppsComputed(vms);
mainHandler.post(myRunnable);
}).start();
}
@Override
@ -173,9 +202,29 @@ public class HomeFragment extends Fragment implements ComputeAppListTask.Listene
homeBinding.logo.setVisibility(View.GONE);
homeBinding.noAppFound.setVisibility(apps.isEmpty() ? View.VISIBLE : View.GONE);
appListFragment.setApplications(apps);
if(!apps.isEmpty()) {
if(startupRefresh) {
startRefresh();
if (!apps.isEmpty()) {
if (startupRefresh) {
Calendar cal = Calendar.getInstance();
if (last_refresh != null) {
cal.setTime(Utils.stringToDate(getContext(), last_refresh));
cal.add(Calendar.DAY_OF_YEAR, 1);
}
Date refreshAfter = cal.getTime();
Date currentDate = new Date();
if (last_refresh != null && !refreshInProgress && currentDate.after(refreshAfter)) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
dialogBuilder.setMessage(getString(R.string.refresh_needed_message, last_refresh));
dialogBuilder.setPositiveButton(R.string.refresh, (dialog, id) -> {
startRefresh();
dialog.dismiss();
});
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
AlertDialog alertDialog = dialogBuilder.create();
alertDialog.show();
} else if (last_refresh == null) {
startRefresh();
}
startupRefresh = false;
}
}

View File

@ -0,0 +1,172 @@
package org.eu.exodus_privacy.exodusprivacy.fragments;
/*
* Copyright (C) 2020 Thomas Schneider
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.adapters.MyTrackersListAdapter;
import org.eu.exodus_privacy.exodusprivacy.adapters.TrackerListAdapter;
import org.eu.exodus_privacy.exodusprivacy.databinding.MyTrackersBinding;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import org.eu.exodus_privacy.exodusprivacy.objects.MyTracker;
import org.eu.exodus_privacy.exodusprivacy.objects.Report;
import org.eu.exodus_privacy.exodusprivacy.objects.Tracker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class MyTrackersFragment extends Fragment implements MyTrackersListAdapter.TrackerClickListener {
private Context context;
private MyTrackersBinding trackerBinding;
private TrackerListAdapter.OnTrackerClickListener onTrackerClickListener;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
context = getContext();
trackerBinding = MyTrackersBinding.inflate(LayoutInflater.from(context));
return trackerBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
trackerBinding.loader.setVisibility(View.VISIBLE);
trackerBinding.trackers.setVisibility(View.GONE);
trackerBinding.swipeRefresh.setOnRefreshListener(this::refresh);
trackerBinding.refresh.setOnClickListener(v -> refresh());
refresh();
}
private void refresh() {
new Thread(() -> {
DatabaseManager databaseManager = DatabaseManager.getInstance(getActivity());
PackageManager packageManager = context.getPackageManager();
List<PackageInfo> packageInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
List<MyTracker> myTrackers = new ArrayList<>();
List<String> added = new ArrayList<>();
int maxValue = 0;
List<ApplicationViewModel> vms = ComputeAppList.compute(packageManager, DatabaseManager.getInstance(getActivity()), null);
int appInstalled = vms.size();
for (PackageInfo pkgInfo : packageInstalled) {
Report report;
if (pkgInfo.versionName != null)
report = databaseManager.getReportFor(pkgInfo.packageName, pkgInfo.versionName, null);
else {
report = databaseManager.getReportFor(pkgInfo.packageName, pkgInfo.versionCode, null);
}
if (report != null) {
Set<Tracker> trackersApp = databaseManager.getTrackers(report.trackers);
for (Tracker tracker : trackersApp) {
if (added.contains(tracker.codeSignature)) {
for (MyTracker myTracker : myTrackers) {
if (myTracker.signature.compareTo(tracker.codeSignature) == 0) {
myTracker.number += 1;
}
}
} else {
MyTracker myTracker = new MyTracker();
myTracker.signature = tracker.codeSignature;
myTracker.number = 1;
myTracker.tracker = tracker;
myTrackers.add(myTracker);
added.add(myTracker.signature);
}
}
}
}
for (MyTracker myTracker : myTrackers) {
if (myTracker.number > maxValue)
maxValue = myTracker.number;
}
Handler mainHandler = new Handler(Looper.getMainLooper());
int finalMaxValue = maxValue;
Runnable myRunnable = () -> {
if (myTrackers.size() > 0) {
Collections.sort(myTrackers, (obj1, obj2) -> Integer.compare(obj2.number, obj1.number));
MyTrackersListAdapter myTrackersListAdapter = new MyTrackersListAdapter(myTrackers, MyTrackersFragment.this, finalMaxValue, appInstalled);
trackerBinding.trackers.setAdapter(myTrackersListAdapter);
trackerBinding.trackers.setLayoutManager(new LinearLayoutManager(context));
trackerBinding.trackers.setVisibility(View.VISIBLE);
trackerBinding.loader.setVisibility(View.GONE);
trackerBinding.refresh.setVisibility(View.GONE);
} else {
trackerBinding.refresh.setVisibility(View.VISIBLE);
trackerBinding.trackers.setVisibility(View.GONE);
trackerBinding.loader.setVisibility(View.GONE);
}
trackerBinding.swipeRefresh.setRefreshing(false);
};
mainHandler.post(myRunnable);
}).start();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.context = context;
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.action_filter).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_filter_options).setVisible(false);
}
public void setOnTrackerClickListener(TrackerListAdapter.OnTrackerClickListener listener) {
onTrackerClickListener = listener;
}
@Override
public void onTrackerClick(long trackerId) {
onTrackerClickListener.onTrackerClick(trackerId);
}
}

View File

@ -28,7 +28,6 @@ import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -37,14 +36,17 @@ import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.eu.exodus_privacy.exodusprivacy.BuildConfig;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.ReportViewModel;
import org.eu.exodus_privacy.exodusprivacy.Utils;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
import org.eu.exodus_privacy.exodusprivacy.adapters.PermissionListAdapter;
import org.eu.exodus_privacy.exodusprivacy.adapters.TrackerListAdapter;
import org.eu.exodus_privacy.exodusprivacy.databinding.ReportBinding;
import org.eu.exodus_privacy.exodusprivacy.objects.ReportDisplay;
public class ReportFragment extends Fragment implements Updatable {
public class ReportFragment extends Fragment implements Updatable {
private PackageManager packageManager;
private PackageInfo packageInfo = null;
@ -52,7 +54,7 @@ public class ReportFragment extends Fragment implements Updatable {
private TrackerListAdapter.OnTrackerClickListener trackerClickListener;
private ApplicationViewModel model;
public static ReportFragment newInstance(PackageManager packageManager,ApplicationViewModel model, PackageInfo packageInfo, TrackerListAdapter.OnTrackerClickListener trackerClickListener) {
public static ReportFragment newInstance(PackageManager packageManager, ApplicationViewModel model, PackageInfo packageInfo, TrackerListAdapter.OnTrackerClickListener trackerClickListener) {
ReportFragment fragment = new ReportFragment();
fragment.setPackageManager(packageManager);
fragment.setPackageInfo(packageInfo);
@ -68,7 +70,7 @@ public class ReportFragment extends Fragment implements Updatable {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null && packageInfo == null) {
if (savedInstanceState != null && packageInfo == null) {
packageInfo = savedInstanceState.getParcelable("PackageInfo");
}
setHasOptionsMenu(true);
@ -81,22 +83,22 @@ public class ReportFragment extends Fragment implements Updatable {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
reportBinding = DataBindingUtil.inflate(inflater,R.layout.report,container,false);
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
reportBinding = DataBindingUtil.inflate(inflater, R.layout.report, container, false);
onUpdateComplete();
return reportBinding.getRoot();
}
@Override
public void onUpdateComplete() {
if(model != null)
if (model != null)
onUpdateComplete(model);
}
public void onUpdateComplete(ApplicationViewModel model) {
Context context = reportBinding.getRoot().getContext();
ReportDisplay reportDisplay = ReportDisplay.buildReportDisplay(context,model,packageManager,packageInfo);
ReportDisplay reportDisplay = ReportDisplay.buildReportDisplay(context, model, packageManager, packageInfo);
ReportViewModel viewModel = new ReportViewModel();
viewModel.setReportDisplay(reportDisplay);
reportBinding.setReportInfo(viewModel);
@ -110,7 +112,7 @@ public class ReportFragment extends Fragment implements Updatable {
//setup trackers lists
reportBinding.trackers.setLayoutManager(new LinearLayoutManager(context));
TrackerListAdapter trackerAdapter = new TrackerListAdapter(reportDisplay.trackers,R.layout.tracker_item, trackerClickListener);
TrackerListAdapter trackerAdapter = new TrackerListAdapter(reportDisplay.trackers, R.layout.tracker_item, trackerClickListener);
reportBinding.trackers.setNestedScrollingEnabled(false);
reportBinding.trackers.setAdapter(trackerAdapter);
@ -133,17 +135,28 @@ public class ReportFragment extends Fragment implements Updatable {
reportBinding.viewStore.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
if(reportDisplay.source.contains("google"))
intent.setData(Uri.parse("https://play.google.com/store/apps/details?id="+reportDisplay.packageName));
if (reportDisplay.source.contains("google"))
intent.setData(Uri.parse("https://play.google.com/store/apps/details?id=" + reportDisplay.packageName));
else
intent.setData(Uri.parse("https://f-droid.org/packages/"+reportDisplay.packageName));
intent.setData(Uri.parse("https://f-droid.org/packages/" + reportDisplay.packageName));
startActivity(intent);
});
if(reportDisplay.report != null) {
reportBinding.analyseApp.setOnClickListener(v -> {
Uri uri;
if (!BuildConfig.amal) {
uri = Uri.parse("https://reports.exodus-privacy.eu.org/analysis/submit/#" + reportDisplay.packageName);
} else {
uri = Uri.parse("https://exodus.phm.education.gouv.fr/analysis/submit/#" + reportDisplay.packageName);
}
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(browserIntent);
});
if (reportDisplay.report != null) {
reportBinding.reportUrl.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://reports.exodus-privacy.eu.org/reports/" + reportDisplay.report.id + "/"));
intent.setData(Uri.parse("https://" + Utils.getDomain() + "/reports/" + reportDisplay.report.id + "/"));
startActivity(intent);
});
}
@ -163,8 +176,9 @@ public class ReportFragment extends Fragment implements Updatable {
@Override
public void onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(R.id.action_filter);
item.setVisible(false);
menu.findItem(R.id.action_filter).setVisible(false);
menu.findItem(R.id.action_filter_options).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(true);
}
public ApplicationViewModel getModel() {

View File

@ -5,14 +5,16 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
@ -27,11 +29,10 @@ import org.eu.exodus_privacy.exodusprivacy.databinding.TrackerBinding;
import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager;
import org.eu.exodus_privacy.exodusprivacy.objects.Tracker;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class TrackerFragment extends Fragment implements ComputeAppListTask.Listener, Updatable {
public class TrackerFragment extends Fragment implements ComputeAppList.Listener, Updatable {
private TrackerBinding trackerBinding;
private long trackerId;
@ -75,17 +76,17 @@ public class TrackerFragment extends Fragment implements ComputeAppListTask.List
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
trackerBinding = DataBindingUtil.inflate(inflater, R.layout.tracker,container,false);
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
trackerBinding = DataBindingUtil.inflate(inflater, R.layout.tracker, container, false);
if (applications == null)
applications = new ArrayList<>();
appListFragment = new AppListFragment();
appListFragment.setFilter(AppListFragment.Type.TRACKER,trackerId);
appListFragment.setFilter(AppListFragment.Type.TRACKER, trackerId);
appListFragment.disableScrollBar();
appListFragment.setOnAppClickListener(onAppClickListener);
FragmentManager manager = getChildFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.applications,appListFragment);
transaction.replace(R.id.applications, appListFragment);
transaction.commit();
Context context = trackerBinding.getRoot().getContext();
packageManager = context.getPackageManager();
@ -96,11 +97,9 @@ public class TrackerFragment extends Fragment implements ComputeAppListTask.List
@Override
public void onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(R.id.action_filter);
item.setVisible(false);
item = menu.findItem(R.id.action_settings);
item.setVisible(false);
menu.findItem(R.id.action_filter).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_filter_options).setVisible(false);
}
private void displayAppListAsync() {
@ -114,14 +113,14 @@ public class TrackerFragment extends Fragment implements ComputeAppListTask.List
trackerBinding.retrieveApp.setVisibility(View.VISIBLE);
}
new ComputeAppListTask(
new WeakReference<>(packageManager),
new WeakReference<>(DatabaseManager.getInstance(getActivity())),
new WeakReference<>(this)
).execute();
new Thread(() -> {
List<ApplicationViewModel> vms = ComputeAppList.compute(packageManager, DatabaseManager.getInstance(getActivity()), null);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> onAppsComputed(vms);
mainHandler.post(myRunnable);
}).start();
}
@Override
public void onAppsComputed(List<ApplicationViewModel> apps) {
this.applications = apps;
trackerBinding.retrieveApp.setVisibility(View.GONE);
@ -132,19 +131,19 @@ public class TrackerFragment extends Fragment implements ComputeAppListTask.List
appListFragment.setApplications(apps);
int total = appListFragment.getTotalApps();
int displayedApps = appListFragment.getDisplayedApps();
int percent = displayedApps*100/total;
if(percent >=50)
int percent = displayedApps * 100 / total;
if (percent >= 50)
trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_red);
else if(percent >=33)
else if (percent >= 33)
trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_dark_orange);
else if(percent >=20)
else if (percent >= 20)
trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_yellow);
else
trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_light_blue);
trackerBinding.trackerPresenceNb.setText(percent+"%");
trackerBinding.trackerPresenceNb.setText(String.format("%s%%", percent));
Context context = trackerBinding.getRoot().getContext();
String presence = context.getResources().getString(R.string.tracker_presence,displayedApps);
String presence = context.getResources().getString(R.string.tracker_presence, displayedApps);
trackerBinding.trackerPresence.setText(presence);
trackerBinding.trackerPresenceTitle.setText(R.string.tracker_presence_in);
}

View File

@ -18,13 +18,18 @@
package org.eu.exodus_privacy.exodusprivacy.listener;
import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import java.util.EventListener;
/*
Listener working with the NetworkManager to handle events
*/
public interface NetworkListener extends EventListener{
void onSuccess();
public interface NetworkListener extends EventListener {
void onSuccess(Application application);
void onError(String error);
void onProgress(int resourceId, int progress, int maxProgress);
}

View File

@ -28,6 +28,7 @@ import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import org.eu.exodus_privacy.exodusprivacy.objects.Report;
import org.eu.exodus_privacy.exodusprivacy.objects.Tracker;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
@ -45,8 +46,8 @@ public class DatabaseManager extends SQLiteOpenHelper {
public static DatabaseManager getInstance(Context context) {
if(instance == null)
instance = new DatabaseManager(context,"Exodus.db",null,3);
if (instance == null)
instance = new DatabaseManager(context, "Exodus.db", null, 3);
return instance;
}
@ -61,7 +62,7 @@ public class DatabaseManager extends SQLiteOpenHelper {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(oldVersion <= 1) {
if (oldVersion <= 1) {
db.execSQL("Alter Table applications add column auid TEXT");
}
if (oldVersion <= 2) {
@ -71,15 +72,15 @@ public class DatabaseManager extends SQLiteOpenHelper {
db.execSQL("Alter Table applications rename to old_apps");
db.execSQL("Create Table if not exists applications (id INTEGER primary key autoincrement, package TEXT, name TEXT, creator TEXT, sources TEXT);");
Cursor cursor = db.query("old_apps",null,null,null,null,null,null);
while (cursor.moveToNext()){
Cursor cursor = db.query("old_apps", null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
values.put("package",cursor.getString(1));
values.put("name",cursor.getString(2));
values.put("creator",cursor.getString(3));
String sources = "unknown:"+cursor.getString(4)+"|";
values.put("sources",sources);
db.insert("applications",null,values);
values.put("package", cursor.getString(1));
values.put("name", cursor.getString(2));
values.put("creator", cursor.getString(3));
String sources = "unknown:" + cursor.getString(4) + "|";
values.put("sources", sources);
db.insert("applications", null, values);
}
cursor.close();
db.execSQL("Drop Table old_apps");
@ -94,53 +95,52 @@ public class DatabaseManager extends SQLiteOpenHelper {
}
private boolean existReport(SQLiteDatabase db, long reportId) {
return exist(db,"reports",reportId);
return exist(db, "reports", reportId);
}
private boolean existApplication(SQLiteDatabase db, String packageName) {
String[] columns = {"package"};
String where = "package = ?";
String[] whereArgs = {packageName};
Cursor cursor = db.query("applications",columns,where,whereArgs,null,null,null);
Cursor cursor = db.query("applications", columns, where, whereArgs, null, null, null);
boolean exist = cursor.getCount() != 0;
cursor.close();
return exist;
}
private boolean existTracker(SQLiteDatabase db, long trackerId) {
return exist(db,"trackers",trackerId);
return exist(db, "trackers", trackerId);
}
private boolean exist(SQLiteDatabase db, String table, long id) {
if(id == -1)
if (id == -1)
return false;
String[] columns = {"id"};
String where = "id = ?";
String[] whereArgs = {String.valueOf(id)};
Cursor cursor = db.query(table,columns,where,whereArgs,null,null,null);
Cursor cursor = db.query(table, columns, where, whereArgs, null, null, null);
boolean exist = cursor.getCount() != 0;
cursor.close();
return exist;
}
private void insertOrUpdateTracker(SQLiteDatabase db,Tracker tracker) {
private void insertOrUpdateTracker(SQLiteDatabase db, Tracker tracker) {
ContentValues values = new ContentValues();
values.put("name",tracker.name);
values.put("code_signature",tracker.codeSignature);
values.put("network_signature",tracker.networkSignature);
values.put("website",tracker.website);
values.put("description",tracker.description);
values.put("creation_date",tracker.creationDate.getTimeInMillis());
values.put("name", tracker.name);
values.put("code_signature", tracker.codeSignature);
values.put("network_signature", tracker.networkSignature);
values.put("website", tracker.website);
values.put("description", tracker.description);
values.put("creation_date", tracker.creationDate.getTimeInMillis());
if(!existTracker(db,tracker.id)) {
values.put("id",tracker.id);
if (!existTracker(db, tracker.id)) {
values.put("id", tracker.id);
db.insert("trackers", null, values);
}
else {
} else {
String where = "id = ?";
String[] whereArgs = {String.valueOf(tracker.id)};
db.update("trackers",values,where,whereArgs);
db.update("trackers", values, where, whereArgs);
}
}
@ -148,54 +148,53 @@ public class DatabaseManager extends SQLiteOpenHelper {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("package", application.packageName);
values.put("name",application.name);
values.put("creator",application.creator);
values.put("sources",buildSourcesStr(application.sources));
values.put("name", application.name);
values.put("creator", application.creator);
values.put("sources", buildSourcesStr(application.sources));
if(!existApplication(db, application.packageName)) {
if (!existApplication(db, application.packageName)) {
db.insert("applications", null, values);
} else {
String where = "package = ?";
String[] whereArgs = {application.packageName};
db.update("applications",values,where,whereArgs);
db.update("applications", values, where, whereArgs);
}
String[] columns = {"id"};
String where = "package = ?";
String[] whereArgs = {application.packageName};
Cursor cursor = db.query("applications",columns,where,whereArgs,null,null,null);
if(cursor.moveToFirst()) {
Cursor cursor = db.query("applications", columns, where, whereArgs, null, null, null);
if (cursor.moveToFirst()) {
application.id = cursor.getLong(0);
}
cursor.close();
for(Report report : application.reports) {
insertOrUpdateReport(db,report,application.id);
for (Report report : application.reports) {
insertOrUpdateReport(db, report, application.id);
}
}
private void insertOrUpdateReport(SQLiteDatabase db, Report report, long appId) {
ContentValues values = new ContentValues();
values.put("creation",report.creationDate.getTimeInMillis());
values.put("updateat",report.updateDate.getTimeInMillis());
values.put("downloads",report.downloads);
values.put("version",report.version);
values.put("version_code",report.versionCode);
values.put("app_id",appId);
values.put("source",report.source);
values.put("creation", report.creationDate.getTimeInMillis());
values.put("updateat", report.updateDate.getTimeInMillis());
values.put("downloads", report.downloads);
values.put("version", report.version);
values.put("version_code", report.versionCode);
values.put("app_id", appId);
values.put("source", report.source);
if(!existReport(db,report.id)) {
values.put("id",report.id);
if (!existReport(db, report.id)) {
values.put("id", report.id);
db.insert("reports", null, values);
}
else {
} else {
String where = "id = ?";
String[] whereArgs = {String.valueOf(report.id)};
db.update("reports",values,where,whereArgs);
db.update("reports", values, where, whereArgs);
}
removeTrackers(report.id);
for(Long tracker : report.trackers) {
for (Long tracker : report.trackers) {
insertTrackerReport(db, tracker, report.id);
}
}
@ -203,14 +202,14 @@ public class DatabaseManager extends SQLiteOpenHelper {
private void removeTrackers(long reportId) {
String where = "report_id = ?";
String[] whereArgs = {String.valueOf(reportId)};
getWritableDatabase().delete("trackers_reports",where,whereArgs);
getWritableDatabase().delete("trackers_reports", where, whereArgs);
}
private void insertTrackerReport(SQLiteDatabase db, long trackerId, long reportId) {
ContentValues values = new ContentValues();
values.put("report_id",reportId);
values.put("tracker_id",trackerId);
db.insert("trackers_reports",null,values);
values.put("report_id", reportId);
values.put("tracker_id", trackerId);
db.insert("trackers_reports", null, values);
}
public Report getReportFor(String packageName, String version, String source) {
@ -218,34 +217,48 @@ public class DatabaseManager extends SQLiteOpenHelper {
String[] columns = {"id"};
String where = "package = ?";
String[] whereArgs = {packageName};
Cursor cursor = db.query("applications",columns,where,whereArgs,null,null,null);
if(cursor.moveToFirst()) {
Cursor cursor = db.query("applications", columns, where, whereArgs, null, null, null);
if (cursor.moveToFirst()) {
long appId = cursor.getLong(0);
cursor.close();
where = "app_id = ? and version = ? and source = ?";
whereArgs = new String[3];
whereArgs[0] = String.valueOf(appId);
whereArgs[1] = version;
whereArgs[2] = source;
if (source != null) {
where = "app_id = ? and version = ? and source = ?";
whereArgs = new String[3];
whereArgs[0] = String.valueOf(appId);
whereArgs[1] = version;
whereArgs[2] = source;
} else {
where = "app_id = ? and version = ?";
whereArgs = new String[2];
whereArgs[0] = String.valueOf(appId);
whereArgs[1] = version;
}
String order = "id ASC";
cursor = db.query("reports",columns,where,whereArgs,null,null,order);
cursor = db.query("reports", columns, where, whereArgs, null, null, order);
long reportId;
if(cursor.moveToFirst()) {
if (cursor.moveToFirst()) {
reportId = cursor.getLong(0);
cursor.close();
} else {
cursor.close();
columns = new String[2];
columns[0] = "id";
columns[1] = "creation";
where = "app_id = ? and source = ?";
whereArgs = new String[2];
whereArgs[0] = String.valueOf(appId);
whereArgs[1] = source;
if (source != null) {
where = "app_id = ? and source = ?";
whereArgs = new String[2];
whereArgs[0] = String.valueOf(appId);
whereArgs[1] = source;
} else {
where = "app_id = ?";
whereArgs = new String[1];
whereArgs[0] = String.valueOf(appId);
}
order = "creation DESC";
//search a recent reports
cursor = db.query("reports",columns,where,whereArgs,null,null,order);
if(cursor.moveToFirst()) {
cursor = db.query("reports", columns, where, whereArgs, null, null, order);
if (cursor.moveToFirst()) {
reportId = cursor.getLong(0);
cursor.close();
} else {
@ -266,8 +279,8 @@ public class DatabaseManager extends SQLiteOpenHelper {
String[] columns = {"id"};
String where = "package = ?";
String[] whereArgs = {packageName};
Cursor cursor = db.query("applications",columns,where,whereArgs,null,null,null);
if(cursor.moveToFirst()) {
Cursor cursor = db.query("applications", columns, where, whereArgs, null, null, null);
if (cursor.moveToFirst()) {
long appId = cursor.getLong(0);
cursor.close();
where = "app_id = ? and version_code = ? and source = ?";
@ -276,9 +289,9 @@ public class DatabaseManager extends SQLiteOpenHelper {
whereArgs[1] = String.valueOf(version);
whereArgs[2] = source;
String order = "id ASC";
cursor = db.query("reports",columns,where,whereArgs,null,null,order);
cursor = db.query("reports", columns, where, whereArgs, null, null, order);
long reportId;
if(cursor.moveToFirst()) {
if (cursor.moveToFirst()) {
reportId = cursor.getLong(0);
cursor.close();
} else {
@ -292,8 +305,8 @@ public class DatabaseManager extends SQLiteOpenHelper {
whereArgs[1] = source;
order = "creation DESC";
//search a recent reports
cursor = db.query("reports",columns,where,whereArgs,null,null,order);
if(cursor.moveToFirst()) {
cursor = db.query("reports", columns, where, whereArgs, null, null, order);
if (cursor.moveToFirst()) {
reportId = cursor.getLong(0);
cursor.close();
} else {
@ -313,9 +326,9 @@ public class DatabaseManager extends SQLiteOpenHelper {
SQLiteDatabase db = getReadableDatabase();
String where = "id = ?";
String[] whereArgs = {String.valueOf(reportId)};
Cursor cursor = db.query("reports",null,where,whereArgs,null,null,null);
Cursor cursor = db.query("reports", null, where, whereArgs, null, null, null);
//get report
if(!cursor.moveToFirst()) {
if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
@ -326,11 +339,11 @@ public class DatabaseManager extends SQLiteOpenHelper {
long creation = cursor.getLong(col++);
report.creationDate = Calendar.getInstance();
report.creationDate.setTimeInMillis(creation);
report.creationDate.set(Calendar.MILLISECOND,0);
report.creationDate.set(Calendar.MILLISECOND, 0);
long update = cursor.getLong(col++);
report.updateDate = Calendar.getInstance();
report.updateDate.setTimeInMillis(update);
report.updateDate.set(Calendar.MILLISECOND,0);
report.updateDate.set(Calendar.MILLISECOND, 0);
report.downloads = cursor.getString(col++);
report.version = cursor.getString(col++);
report.versionCode = cursor.getLong(col++);
@ -342,7 +355,7 @@ public class DatabaseManager extends SQLiteOpenHelper {
where = "report_id = ?";
String[] columns = {"tracker_id"};
String order = "tracker_id DESC";
cursor = db.query("trackers_reports",columns,where,whereArgs,null,null,order);
cursor = db.query("trackers_reports", columns, where, whereArgs, null, null, order);
//get trackersIds
while (cursor.moveToNext()) {
report.trackers.add(cursor.getLong(0));
@ -355,9 +368,9 @@ public class DatabaseManager extends SQLiteOpenHelper {
String[] columns = {"creator"};
String where = "id = ?";
String[] whereArgs = {String.valueOf(applicationId)};
Cursor cursor = getReadableDatabase().query("applications",columns,where,whereArgs,null,null,null);
Cursor cursor = getReadableDatabase().query("applications", columns, where, whereArgs, null, null, null);
String creator;
if(cursor.moveToFirst())
if (cursor.moveToFirst())
creator = cursor.getString(0);
else
creator = "";
@ -365,14 +378,40 @@ public class DatabaseManager extends SQLiteOpenHelper {
return creator;
}
public List<Tracker> getTrackers() {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query("trackers", null, null, null, null, null, null, null);
List<Tracker> trackers = new ArrayList<>();
if (cursor.getCount() == 0) {
cursor.close();
return null;
}
while (cursor.moveToNext()) {
Tracker tracker = new Tracker();
int col = 0;
tracker.id = cursor.getLong(col++);
tracker.name = cursor.getString(col++);
long creation = cursor.getLong(col++);
tracker.creationDate = Calendar.getInstance();
tracker.creationDate.setTimeInMillis(creation);
tracker.codeSignature = cursor.getString(col++);
tracker.networkSignature = cursor.getString(col++);
tracker.website = cursor.getString(col++);
tracker.description = cursor.getString(col);
trackers.add(tracker);
}
cursor.close();
return trackers;
}
public Tracker getTracker(long trackerId) {
SQLiteDatabase db = getReadableDatabase();
SQLiteDatabase db = getReadableDatabase();
String where = "id = ?";
String[] whereArgs = {String.valueOf(trackerId)};
Cursor cursor = db.query("trackers",null,where,whereArgs,null,null,null,null);
Cursor cursor = db.query("trackers", null, where, whereArgs, null, null, null, null);
Tracker tracker = null;
if(cursor.moveToFirst())
{
if (cursor.moveToFirst()) {
tracker = new Tracker();
int col = 0;
tracker.id = cursor.getLong(col++);
@ -392,7 +431,7 @@ public class DatabaseManager extends SQLiteOpenHelper {
public Set<Tracker> getTrackers(Set<Long> trackers_id) {
Set<Tracker> trackers = new HashSet<>();
for(Long trackerId : trackers_id) {
for (Long trackerId : trackers_id) {
Tracker tracker = getTracker(trackerId);
trackers.add(tracker);
}
@ -401,40 +440,39 @@ public class DatabaseManager extends SQLiteOpenHelper {
void insertOrUpdateTrackers(List<Tracker> trackersList) {
SQLiteDatabase db = getWritableDatabase();
for(Tracker tracker : trackersList) {
insertOrUpdateTracker(db,tracker);
for (Tracker tracker : trackersList) {
insertOrUpdateTracker(db, tracker);
}
}
public Map<String,String> getSources(String packageName) {
public Map<String, String> getSources(String packageName) {
String where = "package = ?";
String[] whereArgs = {packageName};
String[] columns = {"sources"};
Cursor cursor = getReadableDatabase().query("applications",columns,where,whereArgs,null,null,null,null);
String sourcesStr="";
if(cursor.moveToFirst())
{
Cursor cursor = getReadableDatabase().query("applications", columns, where, whereArgs, null, null, null, null);
String sourcesStr = "";
if (cursor.moveToFirst()) {
sourcesStr = cursor.getString(0);
}
cursor.close();
return extractSources(sourcesStr);
}
private String buildSourcesStr(Map<String,String> sources) {
private String buildSourcesStr(Map<String, String> sources) {
StringBuilder sourceStr = new StringBuilder();
for(Map.Entry<String,String> entry : sources.entrySet()) {
for (Map.Entry<String, String> entry : sources.entrySet()) {
sourceStr.append(entry.getKey()).append(":").append(entry.getValue()).append("|");
}
return sourceStr.toString();
}
private Map<String, String> extractSources(String sourcesStr) {
Map<String,String> sources = new HashMap<>();
Map<String, String> sources = new HashMap<>();
String[] sourceList = sourcesStr.split("\\|");
for(String sourceItem : sourceList){
if(!sourceItem.isEmpty()) {
for (String sourceItem : sourceList) {
if (!sourceItem.isEmpty()) {
String[] data = sourceItem.split(":");
if(data.length == 2)
if (data.length == 2)
sources.put(data[0], data[1]);
}
}

View File

@ -19,9 +19,17 @@
package org.eu.exodus_privacy.exodusprivacy.manager;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.Utils;
import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener;
import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import org.eu.exodus_privacy.exodusprivacy.objects.Report;
import org.eu.exodus_privacy.exodusprivacy.objects.Tracker;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -32,30 +40,23 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Semaphore;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener;
import org.eu.exodus_privacy.exodusprivacy.objects.Application;
import org.eu.exodus_privacy.exodusprivacy.objects.Report;
import org.eu.exodus_privacy.exodusprivacy.objects.Tracker;
import static android.content.Context.MODE_PRIVATE;
/*
Singleton that handle all network connection
@ -74,34 +75,47 @@ public class NetworkManager {
return instance;
}
public void getSingleReport(Context context, NetworkListener listener, String packageName) {
Message mes = new Message();
mes.type = Message_Type.GET_SINGLE_REPORT;
mes.context = context;
mes.listener = listener;
mes.args = new Bundle();
mes.args.putString("package", packageName);
addMessageToQueue(mes);
}
public void getReports(Context context, NetworkListener listener, ArrayList<String> packageList) {
Message mes = new Message();
mes.type = Message_Type.GET_REPORTS;
mes.context = context;
mes.listener = listener;
mes.args = new Bundle();
mes.args.putStringArrayList("packages",packageList);
mes.args.putStringArrayList("packages", packageList);
addMessageToQueue(mes);
}
private void addMessageToQueue(Message mes) {
if (thread == null || thread.getState() == Thread.State.TERMINATED || !thread.isRunning)
thread = new NetworkProcessingThread();
thread.queueMessage(mes);
if (thread.getState() == Thread.State.NEW)
thread.start();
}
private enum Message_Type {
GET_REPORTS,
GET_SINGLE_REPORT,
UNKNOWN
}
private void addMessageToQueue(Message mes){
if(thread == null || thread.getState() == Thread.State.TERMINATED || !thread.isRunning)
thread = new NetworkProcessingThread();
thread.queueMessage(mes);
if(thread.getState() == Thread.State.NEW)
thread.start();
}
private class NetworkProcessingThread extends Thread {
private List<Message> messageQueue;
private Semaphore sem;
private static class NetworkProcessingThread extends Thread {
private final String domain = Utils.getDomain();
private final String apiUrl = "https://" + domain + "/api/";
private final List<Message> messageQueue;
private final Semaphore sem;
boolean isRunning;
private final String apiUrl = "https://reports.exodus-privacy.eu.org/api/";
NetworkProcessingThread() {
messageQueue = new ArrayList<>();
@ -116,14 +130,24 @@ public class NetworkManager {
@Override
public void run() {
isRunning = true;
Message mes;
while (isRunning) {
try {
sem.acquire();
Message mes = messageQueue.remove(0);
mes = messageQueue.remove(0);
switch (mes.type) {
case GET_REPORTS:
getTrackers(mes);
getApplications(mes);
SharedPreferences sharedPreferences = mes.context.getSharedPreferences(Utils.APP_PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(Utils.LAST_REFRESH, Utils.dateToString(new Date()));
editor.apply();
break;
case GET_SINGLE_REPORT:
String packageName = mes.args.getString("package");
Application application = getSingleReport(mes, packageName);
mes.listener.onSuccess(application);
break;
default:
break;
@ -135,6 +159,7 @@ public class NetworkManager {
}
private JSONObject makeDataRequest(Context context, NetworkListener listener, URL url) {
if (!isConnectedToInternet(context)) {
listener.onError(context.getString(R.string.not_connected));
return null;
@ -147,31 +172,32 @@ public class NetworkManager {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestProperty("Authorization","Token "+context.getString(R.string.exodus));
urlConnection.setRequestProperty("Authorization", "Token " + context.getString(R.string.exodus));
urlConnection.setDoInput(true);
} catch (Exception e) {
e.printStackTrace();
return null;
}
try {
inStream = urlConnection.getInputStream();
} catch (Exception e) {
e.printStackTrace();
success = false;
inStream = urlConnection.getErrorStream();
}
JSONObject object = null;
if(success) {
if (success) {
String jsonStr = getJSON(inStream);
try {
object = new JSONObject(jsonStr);
} catch (Exception e)
{
} catch (Exception e) {
e.printStackTrace();
}
}
try {
if(inStream != null)
if (inStream != null)
inStream.close();
} catch (IOException e) {
e.printStackTrace();
@ -181,33 +207,36 @@ public class NetworkManager {
}
private void getTrackers(Message mes) {
mes.listener.onProgress(R.string.get_trackers_connection,0,0);
mes.listener.onProgress(R.string.get_trackers_connection, 0, 0);
URL url;
try {
url = new URL(apiUrl+"trackers");
} catch (Exception e){
url = new URL(apiUrl + "trackers");
} catch (Exception e) {
e.printStackTrace();
return;
}
JSONObject object = makeDataRequest(mes.context,mes.listener,url);
mes.listener.onProgress(R.string.get_trackers,0,0);
JSONObject object = makeDataRequest(mes.context, mes.listener, url);
mes.listener.onProgress(R.string.get_trackers, 0, 0);
if(object != null) {
if (object != null) {
try {
JSONObject trackers = object.getJSONObject("trackers");
List<Tracker> trackersList = new ArrayList<>();
for(int i = 0; i<trackers.names().length(); i++) {
mes.listener.onProgress(R.string.parse_trackers,i+1,trackers.names().length());
String trackerId = trackers.names().get(i).toString();
JSONObject tracker = trackers.getJSONObject(trackerId);
Tracker track = parseTracker(tracker,trackerId);
trackersList.add(track);
if (trackersList.size() == 20) {
DatabaseManager.getInstance(mes.context).insertOrUpdateTrackers(trackersList);
trackersList.clear();
JSONArray trackerNames = trackers.names();
if (trackerNames != null) {
for (int i = 0; i < trackerNames.length(); i++) {
mes.listener.onProgress(R.string.parse_trackers, i + 1, trackerNames.length());
String trackerId = trackerNames.get(i).toString();
JSONObject tracker = trackers.getJSONObject(trackerId);
Tracker track = parseTracker(tracker, trackerId);
trackersList.add(track);
if (trackersList.size() == 20) {
DatabaseManager.getInstance(mes.context).insertOrUpdateTrackers(trackersList);
trackersList.clear();
}
}
}
if(!trackersList.isEmpty())
if (!trackersList.isEmpty())
DatabaseManager.getInstance(mes.context).insertOrUpdateTrackers(trackersList);
trackersList.clear();
} catch (JSONException e) {
@ -216,22 +245,22 @@ public class NetworkManager {
}
}
private void getApplications(Message mes) {
mes.listener.onProgress(R.string.get_reports_connection,0,0);
mes.listener.onProgress(R.string.get_reports_connection, 0, 0);
URL url;
try {
url = new URL(apiUrl+"applications?option=short");
} catch (Exception e){
url = new URL(apiUrl + "applications?option=short");
} catch (Exception e) {
e.printStackTrace();
return;
}
JSONObject object = makeDataRequest(mes.context,mes.listener,url);
mes.listener.onProgress(R.string.get_reports,0,0);
JSONObject object = makeDataRequest(mes.context, mes.listener, url);
mes.listener.onProgress(R.string.get_reports, 0, 0);
if(object != null) {
Map<String,Map<String,String>> handles = new HashMap<>();
if (object != null) {
Map<String, Map<String, String>> handles = new HashMap<>();
ArrayList<String> packages = mes.args.getStringArrayList("packages");
if (packages == null)
return;
@ -240,31 +269,31 @@ public class NetworkManager {
JSONArray applications = object.getJSONArray("applications");
//manage handles map (handle,UAID)
for(int i = 0; i<applications.length(); i++) {
for (int i = 0; i < applications.length(); i++) {
JSONObject app = applications.getJSONObject(i);
String handle = app.getString("handle");
String auid = app.getString("app_uid");
String source = app.getString("source");
Map<String,String> sources = handles.get(handle);
if(sources == null)
Map<String, String> sources = handles.get(handle);
if (sources == null)
sources = new HashMap<>();
sources.put(source,auid);
sources.put(source, auid);
if (packages.contains(handle))
handles.put(handle,sources);
handles.put(handle, sources);
}
//remove app not analyzed by Exodus
packages.retainAll(handles.keySet());
// Add some random packages to avoid tracking
Random rand = new Random(Thread.currentThread().getId());
SecureRandom rand = new SecureRandom();
int alea = rand.nextInt(120) % 10 + 11;
for(int i = 0 ; i < alea; i++) {
for (int i = 0; i < alea; i++) {
int val = rand.nextInt(applications.length());
JSONObject app = applications.getJSONObject(val);
String handle = app.getString("handle");
handles.put(handle,new HashMap<>());
handles.put(handle, new HashMap<>());
packages.add(handle);
}
//shuffle the list
@ -275,34 +304,33 @@ public class NetworkManager {
e.printStackTrace();
mes.listener.onError(mes.context.getString(R.string.json_error));
}
object = null;
getReports(mes,handles,packages);
getReports(mes, handles, packages);
}
mes.listener.onSuccess();
mes.listener.onSuccess(null);
}
private void getReports(Message mes, Map<String, Map<String,String>> handles, ArrayList<String> packages) {
for(int i = 0; i < packages.size(); i++) {
mes.listener.onProgress(R.string.parse_application,i+1,packages.size());
getReport(mes,packages.get(i),handles.get(packages.get(i)));
private void getReports(Message mes, Map<String, Map<String, String>> handles, ArrayList<String> packages) {
for (int i = 0; i < packages.size(); i++) {
mes.listener.onProgress(R.string.parse_application, i + 1, packages.size());
getReport(mes, packages.get(i), handles.get(packages.get(i)));
}
}
private void getReport(Message mes, String handle, Map<String,String> sources) {
private void getReport(Message mes, String handle, Map<String, String> sources) {
URL url;
try {
url = new URL(apiUrl+"search/"+handle);
} catch (Exception e){
url = new URL(apiUrl + "search/" + handle);
} catch (Exception e) {
e.printStackTrace();
return;
}
JSONObject object = makeDataRequest(mes.context,mes.listener,url);
JSONObject object = makeDataRequest(mes.context, mes.listener, url);
if(object != null) {
if (object != null) {
try {
JSONObject application = object.getJSONObject(handle);
ArrayList<String> packages = mes.args.getStringArrayList("packages");
if(packages != null && packages.contains(handle)) {
if (packages != null && packages.contains(handle)) {
Application app = parseApplication(application, handle);
app.sources = sources;
DatabaseManager.getInstance(mes.context).insertOrUpdateApplication(app);
@ -313,6 +341,29 @@ public class NetworkManager {
}
}
private Application getSingleReport(Message mes, String handle) {
URL url;
try {
url = new URL(apiUrl + "search/" + handle);
} catch (Exception e) {
e.printStackTrace();
return null;
}
JSONObject object = makeDataRequest(mes.context, mes.listener, url);
if (object != null) {
try {
if (handle != null) {
JSONObject application = object.getJSONObject(handle);
return parseApplication(application, handle);
}
} catch (JSONException e) {
mes.listener.onError(mes.context.getString(R.string.json_error));
}
}
return null;
}
private Application parseApplication(JSONObject object, String packageName) throws JSONException {
Application application = new Application();
application.packageName = packageName;
@ -322,7 +373,7 @@ public class NetworkManager {
//parse Report
application.reports = new HashSet<>();
JSONArray reports = object.getJSONArray("reports");
for(int i = 0; i < reports.length(); i++) {
for (int i = 0; i < reports.length(); i++) {
Report report = parseReport(reports.getJSONObject(i));
application.reports.add(report);
}
@ -335,25 +386,32 @@ public class NetworkManager {
report.downloads = object.getString("downloads");
report.version = object.getString("version");
report.source = object.getString("source");
if(!object.getString("version_code").isEmpty())
if (!object.getString("version_code").isEmpty())
report.versionCode = Long.parseLong(object.getString("version_code"));
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
try {
report.updateDate = Calendar.getInstance();
report.updateDate.setTimeZone(TimeZone.getTimeZone("UTC"));
report.updateDate.setTime(dateFormat.parse(object.getString("updated_at")));
report.updateDate.set(Calendar.MILLISECOND,0);
Date date = dateFormat.parse(object.getString("updated_at"));
if (date != null) {
report.updateDate.setTime(date);
}
report.updateDate.set(Calendar.MILLISECOND, 0);
report.creationDate = Calendar.getInstance();
report.creationDate.setTimeZone(TimeZone.getTimeZone("UTC"));
report.creationDate.setTime(dateFormat.parse(object.getString("creation_date")));
report.creationDate.set(Calendar.MILLISECOND,0);
report.creationDate.setTime(date!=null?date:new Date());
date = dateFormat.parse(object.getString("creation_date"));
if (date != null) {
report.creationDate.setTime(date);
}
report.creationDate.set(Calendar.MILLISECOND, 0);
} catch (ParseException e) {
e.printStackTrace();
}
JSONArray trackersArray = object.getJSONArray("trackers");
report.trackers = new HashSet<>();
for(int i = 0; i < trackersArray.length(); i++)
for (int i = 0; i < trackersArray.length(); i++)
report.trackers.add(trackersArray.getLong(i));
return report;
}
@ -370,7 +428,10 @@ public class NetworkManager {
try {
tracker.creationDate = Calendar.getInstance();
tracker.creationDate.setTimeZone(TimeZone.getTimeZone("UTC"));
tracker.creationDate.setTime(dateFormat.parse(object.getString("creation_date")));
Date date = dateFormat.parse(object.getString("creation_date"));
if (date != null) {
tracker.creationDate.setTime(date);
}
} catch (ParseException e) {
e.printStackTrace();
}
@ -402,23 +463,22 @@ public class NetworkManager {
private boolean isConnectedToInternet(Context context) {
//verify the connectivity
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager == null)
if (connectivityManager == null)
return false;
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null) {
NetworkInfo.State networkState = networkInfo.getState();
if (networkState.equals(NetworkInfo.State.CONNECTED)) {
return true;
}
return networkState.equals(NetworkInfo.State.CONNECTED);
}
return false;
}
}
private class Message{
private static class Message {
Message_Type type = Message_Type.UNKNOWN;
Bundle args = new Bundle();
NetworkListener listener;
Context context;
}
}

View File

@ -19,7 +19,6 @@
package org.eu.exodus_privacy.exodusprivacy.objects;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class Application {
@ -28,7 +27,7 @@ public class Application {
public String name;
public String creator;
public Set<Report> reports;
public Map<String,String> sources;
public Map<String, String> sources;
@Override
public boolean equals(Object o) {

View File

@ -0,0 +1,8 @@
package org.eu.exodus_privacy.exodusprivacy.objects;
public class MyTracker {
public String signature;
public Tracker tracker;
public int number;
}

View File

@ -7,7 +7,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import org.eu.exodus_privacy.exodusprivacy.R;
import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel;
@ -31,7 +30,7 @@ public class ReportDisplay {
public String viewOnStore;
private ReportDisplay(){
private ReportDisplay() {
}
@ -43,32 +42,32 @@ public class ReportDisplay {
reportDisplay.displayName = model.label.toString();
reportDisplay.report = model.report;
reportDisplay.source = context.getString(R.string.source,model.source);
reportDisplay.viewOnStore = context.getString(model.source.equals("google")? R.string.view_on_google_play : R.string.view_on_fdroid);
reportDisplay.source = context.getString(R.string.source, model.source);
reportDisplay.viewOnStore = context.getString(model.source.equals("google") ? R.string.view_on_google_play : R.string.view_on_fdroid);
reportDisplay.trackers = model.trackers;
if (reportDisplay.report != null)
reportDisplay.creator = DatabaseManager.getInstance(context).getCreator(reportDisplay.report.appId);
reportDisplay.creator = DatabaseManager.getInstance(context).getCreator(reportDisplay.report.appId);
List<Permission> requestedPermissions= new ArrayList<>();
if (info.requestedPermissions != null) {
for(int i = 0; i < info.requestedPermissions.length; i++) {
List<Permission> requestedPermissions = new ArrayList<>();
if (info != null && manager != null && info.requestedPermissions != null) {
for (int i = 0; i < info.requestedPermissions.length; i++) {
Permission permission = new Permission();
permission.fullName = info.requestedPermissions[i];
try {
PermissionInfo permissionInfo = manager.getPermissionInfo(permission.fullName,PackageManager.GET_META_DATA);
if(permissionInfo.loadDescription(manager) != null)
PermissionInfo permissionInfo = manager.getPermissionInfo(permission.fullName, PackageManager.GET_META_DATA);
if (permissionInfo.loadDescription(manager) != null)
permission.description = permissionInfo.loadDescription(manager).toString();
if(permissionInfo.loadLabel(manager) != null)
if (permissionInfo.loadLabel(manager) != null)
permission.name = permissionInfo.loadLabel(manager).toString();
permission.dangerous = permissionInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS;
if(permission.fullName.equals(Manifest.permission.WRITE_SETTINGS) || permission.fullName.equals(Manifest.permission.SYSTEM_ALERT_WINDOW)) //Special permissions
if (permission.fullName.equals(Manifest.permission.WRITE_SETTINGS) || permission.fullName.equals(Manifest.permission.SYSTEM_ALERT_WINDOW)) //Special permissions
permission.dangerous = true;
if (permissionInfo.group != null) {
PermissionGroupInfo permissionGroupInfo = manager.getPermissionGroupInfo(permissionInfo.group, PackageManager.GET_META_DATA);
if(permissionGroupInfo.loadIcon(manager) != null)
if (permissionGroupInfo.loadIcon(manager) != null)
permission.icon = permissionGroupInfo.loadIcon(manager);
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromYDelta="0%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toYDelta="100%" />
</set>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromYDelta="100%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toYDelta="0%" />
</set>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-5h2v5zM13,17h-2v-3h2v3zM13,12h-2v-2h2v2zM17,17h-2L15,7h2v10z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
</vector>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/percent" />
<stroke
android:width="2dp"
android:color="@color/colorPrimary" />
<corners android:radius="5dp" />
<padding
android:bottom="0dp"
android:left="0dp"
android:right="0dp"
android:top="0dp" />
</shape>

View File

@ -34,10 +34,9 @@
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#FF684971"
android:endColor="#FF3d2b43"
android:angle="270"
/>
android:endColor="@color/colorAccent"
android:startColor="@color/colorAccent"
android:angle="270" />
</shape>
</clip>
</item>

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="androidx.core.content.ContextCompat" />
<import type="android.view.View" />
<variable
name="reportInfo"
type="org.eu.exodus_privacy.exodusprivacy.ReportViewModel" />
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:text="@{reportInfo.name}"
android:textAlignment="center"
android:textColor="@color/colorPurple"
android:textSize="30sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/dummy"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="15dp"
app:layout_constraintTop_toBottomOf="@id/name" />
<TextView
android:id="@+id/trackers_nb"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="20dp"
android:layout_marginTop="15dp"
android:background="@{ContextCompat.getDrawable(context,reportInfo.trackerColor)}"
android:text="@{reportInfo.trackerNumberStr}"
android:textAlignment="center"
android:textColor="@color/textColorWhite"
android:textSize="22sp"
android:textStyle="bold"
android:visibility="@{reportInfo.trackerVisibility ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dummy" />
<TextView
android:id="@+id/trackers_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:text="@string/trackers"
android:textColor="@color/textColorDark"
android:textSize="22sp"
android:textStyle="bold"
android:visibility="@{reportInfo.trackerVisibility ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/trackers_nb"
app:layout_constraintTop_toTopOf="@id/trackers_nb" />
<TextView
android:id="@+id/installed_version"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:text="@string/report_version"
android:textColor="@color/textColorDark"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/trackers_title" />
<TextView
android:id="@+id/installed_version_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{reportInfo.installedVersion}"
android:textColor="@color/textColorDark"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/installed_version"
app:layout_constraintTop_toTopOf="@+id/installed_version" />
<TextView
android:id="@+id/report_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:textColor="@color/textColorDarkLight"
android:textSize="16sp"
android:visibility="@{reportInfo.reportVisibility ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/installed_version_value" />
<TextView
android:id="@+id/report_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:autoLink="web"
android:text="@string/view_on_exodus"
android:textAlignment="textEnd"
android:textColor="@color/colorPurple"
android:textSize="16sp"
android:visibility="@{reportInfo.reportVisibility ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/report_date" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/report_url">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/trackers"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/analysed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:text="@string/analysed"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:visibility="@{reportInfo.trackerVisibility ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/report_url" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</layout>

View File

@ -63,7 +63,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:src="@drawable/ic_logo_purple"
android:src="@drawable/ic_logo"
android:layout_width="200dp"
android:layout_height="200dp" />
<ProgressBar

View File

@ -1,20 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.eu.exodus_privacy.exodusprivacy.MainActivity"
>
<data/>
<FrameLayout
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:id="@+id/fragment_container"
tools:context="org.eu.exodus_privacy.exodusprivacy.MainActivity">
<data />
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?colorPrimary"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Dark" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="10dp"
android:layout_marginBottom="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.drawerlayout.widget.DrawerLayout>
</layout>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1">
<View
android:id="@+id/percent"
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginBottom="10dp"
android:background="@drawable/percent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/percent_val"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
app:layout_constraintBottom_toBottomOf="@+id/percent"
app:layout_constraintStart_toStartOf="@+id/percent"
app:layout_constraintTop_toTopOf="@+id/percent" />
<TextView
android:id="@+id/tracker_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/textColorDark"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tracker_count"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/percent" />
<TextView
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:id="@+id/tracker_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textColor="@color/textColorWhite"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tracker_name"
app:layout_constraintTop_toTopOf="@id/tracker_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/details"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_navigate_next_24"
android:contentDescription="@string/list_of_apps" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nothing_here"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/trackers"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<RelativeLayout
android:id="@+id/loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -11,6 +11,7 @@
type="org.eu.exodus_privacy.exodusprivacy.ReportViewModel" />
</data>
<androidx.core.widget.NestedScrollView
android:background="?android:attr/windowBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -262,7 +263,7 @@
android:text="@{reportInfo.trackerNumberStr}"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
app:layout_constraintTop_toBottomOf="@id/analysed"
app:layout_constraintTop_toBottomOf="@id/analyse_app"
app:layout_constraintStart_toStartOf="parent"
android:background="@{ContextCompat.getDrawable(context,reportInfo.trackerColor)}"
android:visibility="@{reportInfo.trackerVisibility ? View.VISIBLE : View.GONE}"
@ -281,7 +282,7 @@
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="30dp"
app:layout_constraintTop_toBottomOf="@id/analysed"
app:layout_constraintTop_toBottomOf="@id/analyse_app"
app:layout_constraintStart_toEndOf="@id/trackers_nb_list"
app:layout_constraintEnd_toEndOf="parent"
android:textStyle="bold"
@ -436,9 +437,21 @@
android:layout_marginEnd="20dp"
android:layout_marginTop="5dp"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
/>
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<Button
android:id="@+id/analyse_app"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:text="@string/analyse_app"
android:visibility="@{reportInfo.trackerVisibility ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/analysed" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</layout>

View File

@ -6,6 +6,7 @@
<data>
</data>
<androidx.core.widget.NestedScrollView
android:background="?android:attr/windowBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_apps"
android:icon="@drawable/ic_baseline_apps_24"
android:title="@string/title_apps" />
<item
android:id="@+id/navigation_analytics"
android:icon="@drawable/ic_baseline_analytics_24"
android:title="@string/title_trackers" />
</menu>

View File

@ -5,8 +5,13 @@
android:id="@+id/action_filter"
android:icon="@drawable/ic_search"
android:title="@string/menu_action_filter"
app:actionViewClass="android.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" />
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView" />
<item
android:id="@+id/action_filter_options"
android:icon="@drawable/ic_baseline_filter_list_24"
android:title="@string/menu_action_filter"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/filter_by_name_grp"
android:checkableBehavior="single">
<item
android:id="@+id/filter_by_name"
android:title="@string/filter_by_name" />
<item
android:id="@+id/having_less_trackers"
android:title="@string/having_less_trackers" />
<item
android:id="@+id/having_most_trackers"
android:title="@string/having_most_trackers" />
<item
android:id="@+id/having_less_permissions"
android:title="@string/having_less_permissions" />
<item
android:id="@+id/having_most_permissions"
android:title="@string/having_most_permissions" />
</group>
</menu>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="108dp"
android:layout_height="108dp">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground>
<inset
android:drawable="@mipmap/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Some files were not shown because too many files have changed in this diff Show More